Skip to content

Instantly share code, notes, and snippets.

@AZaviruha
Last active September 2, 2021 06:30
Show Gist options
  • Save AZaviruha/1d4702c5c5a190bbdbd1091d8aaaab93 to your computer and use it in GitHub Desktop.
Save AZaviruha/1d4702c5c5a190bbdbd1091d8aaaab93 to your computer and use it in GitHub Desktop.

Revisions

  1. AZaviruha revised this gist Sep 2, 2021. 1 changed file with 6 additions and 6 deletions.
    12 changes: 6 additions & 6 deletions React side-effects.md
    Original file line number Diff line number Diff line change
    @@ -12,7 +12,7 @@ function MyComponent () {

    const getData = useCallback(async (x, y) => {
    const data = await loadData({ x, y })
    setFoo(data)
    setFoo(data)
    }, [])

    useEffect(() => {
    @@ -33,7 +33,7 @@ function MyComponent () {

    const getData = useCallback(async (x, y) => {
    const data = await loadData({ x, y, z })
    setFoo(data)
    setFoo(data)
    }, [ /* z ?? */])

    useEffect(() => {
    @@ -55,7 +55,7 @@ function MyComponent () {

    const getData = useCallback(async (x, y) => {
    const data = await loadData({ x, y, z: z.current })
    setFoo(data)
    setFoo(data)
    }, [])

    useEffect(() => {
    @@ -77,7 +77,7 @@ function MyComponent () {

    const getData = useCallback(async (x, y) => {
    const data = await loadData({ x, y, z: z.current })
    setFoo(data)
    setFoo(data)
    }, [])

    const handleOnClick = useCallback(() => {
    @@ -110,12 +110,12 @@ function MyComponent () {

    const getData = useCallback(async (x, y) => {
    const data = await loadData({ x, y, z: z.current })
    setFoo(data)
    setFoo(data)
    }, [])

    const handleOnClick = useCallback(() => {
    z.current = Date.now() // Will not trigger re-render
    forceRender() // but this will
    forceRender() // but this will
    }, [])

    useEffect(() => {
  2. AZaviruha revised this gist Sep 2, 2021. 1 changed file with 7 additions and 9 deletions.
    16 changes: 7 additions & 9 deletions React side-effects.md
    Original file line number Diff line number Diff line change
    @@ -22,9 +22,7 @@ function MyComponent () {
    ```


    This all works well, until x and y is enough for `getData` to fetch data.
    But what if we need some additional argument (z) and at the same time we don't want to use this argument as a dependency in the `useEffect` (because
    changes of `z` can trigger unnecessary calls of this effect)?
    This all works well, until x and y is enough for `getData` to fetch data. But what if we need some additional argument (z) and at the same time we don't want to use this argument as a dependency in the `useEffect` (because changes of `z` can trigger unnecessary calls of this effect)?

    ```javascript
    function MyComponent () {
    @@ -34,13 +32,13 @@ function MyComponent () {
    const [foo, setFoo] = useState(null)

    const getData = useCallback(async (x, y) => {
    const data = await loadData({ x, y, z: z.current })
    const data = await loadData({ x, y, z })
    setFoo(data)
    }, [])
    }, [ /* z ?? */])

    useEffect(() => {
    getData(x, y)
    }, [x, y])
    }, [x, y, /* z ?? */])
    }
    ```

    @@ -94,7 +92,7 @@ function MyComponent () {
    <div>
    <button onClick={handleOnClick}>Update "z"</button>
    <AnotherComponent value={z.current} />
    </div>
    </div>
    )
    }
    ```
    @@ -128,12 +126,12 @@ function MyComponent () {
    <div>
    <button onClick={handleOnClick}>Update "z"</button>
    <AnotherComponent value={z.current} />
    </div>
    </div>
    )
    }
    ```

    Using a meaningless "setter" from the `useState` will force re-render, but at this moment your code looks like a combination of dirty hacks and workarounds. I don't like how it smells.
    Using a meaningless state setter we can force re-render, but at this moment your code will look like a combination of dirty hacks and workarounds. I don't like how it smells.

    It seems that right now using Redux with some middleware provides more straight-forward solution to the side-effect management.
    I should to admit it even despite the fact that I'm a big fan of using React without additional libraries.
  3. AZaviruha created this gist Sep 2, 2021.
    139 changes: 139 additions & 0 deletions React side-effects.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,139 @@
    It is possible to implement side-effects in the React component without any additional tool, like Redux's middlewares (saga, thunks, etc) .
    It can be achieved using the combination of `useEffect` and `async\await` functions, maybe wrapped inside `useCallback`
    This approach is workable, but it has some pitfalls.

    Let's look at this example:

    ```javascript
    function MyComponent () {
    const [x, setX] = useState(42)
    const [y, setY] = useState(43)
    const [foo, setFoo] = useState(null)

    const getData = useCallback(async (x, y) => {
    const data = await loadData({ x, y })
    setFoo(data)
    }, [])

    useEffect(() => {
    getData(x, y)
    }, [x, y])
    }
    ```


    This all works well, until x and y is enough for `getData` to fetch data.
    But what if we need some additional argument (z) and at the same time we don't want to use this argument as a dependency in the `useEffect` (because
    changes of `z` can trigger unnecessary calls of this effect)?

    ```javascript
    function MyComponent () {
    const [x, setX] = useState(42)
    const [y, setY] = useState(43)
    const [z, setZ] = useState(44)
    const [foo, setFoo] = useState(null)

    const getData = useCallback(async (x, y) => {
    const data = await loadData({ x, y, z: z.current })
    setFoo(data)
    }, [])

    useEffect(() => {
    getData(x, y)
    }, [x, y])
    }
    ```

    We can't add `z` as a dependency to the `useCallack` too, because in that case we will need to add the `getData` function itself to the effect's dependencies. Without this we have a chance to call an outdated `getData` (with old value of `z` inside it). But this is not what we want. We want only `x` and `y` to be the reason of `useEffect` calls.

    For now I discovered only one solution for this problem: refs.

    ```javascript
    function MyComponent () {
    const [x, setX] = useState(42)
    const [y, setY] = useState(43)
    const [foo, setFoo] = useState(null)
    const z = useRef(null)

    const getData = useCallback(async (x, y) => {
    const data = await loadData({ x, y, z: z.current })
    setFoo(data)
    }, [])

    useEffect(() => {
    getData(x, y)
    }, [x, y])
    }
    ```

    But to be honest, this looks more like a hack than a "proper solution". It becomes obvious when you need to use `z` as a property for some component:
    it will not work, because change of the ref does not trigger re-rendering of the component.
    So you can't do this:

    ```javascript
    function MyComponent () {
    const [x, setX] = useState(42)
    const [y, setY] = useState(43)
    const [foo, setFoo] = useState(null)
    const z = useRef(null)

    const getData = useCallback(async (x, y) => {
    const data = await loadData({ x, y, z: z.current })
    setFoo(data)
    }, [])

    const handleOnClick = useCallback(() => {
    z.current = Date.now() // Will not trigger re-render
    }, [])

    useEffect(() => {
    getData(x, y)
    }, [x, y])

    return (
    <div>
    <button onClick={handleOnClick}>Update "z"</button>
    <AnotherComponent value={z.current} />
    </div>
    )
    }
    ```

    You can solve this problem using another "dirty hack":


    ```javascript
    function MyComponent () {
    const [x, setX] = useState(42)
    const [y, setY] = useState(43)
    const [foo, setFoo] = useState(null)
    const z = useRef(null)
    const [, forceRender] = useState(null)

    const getData = useCallback(async (x, y) => {
    const data = await loadData({ x, y, z: z.current })
    setFoo(data)
    }, [])

    const handleOnClick = useCallback(() => {
    z.current = Date.now() // Will not trigger re-render
    forceRender() // but this will
    }, [])

    useEffect(() => {
    getData(x, y)
    }, [x, y])

    return (
    <div>
    <button onClick={handleOnClick}>Update "z"</button>
    <AnotherComponent value={z.current} />
    </div>
    )
    }
    ```

    Using a meaningless "setter" from the `useState` will force re-render, but at this moment your code looks like a combination of dirty hacks and workarounds. I don't like how it smells.

    It seems that right now using Redux with some middleware provides more straight-forward solution to the side-effect management.
    I should to admit it even despite the fact that I'm a big fan of using React without additional libraries.