sennetconsortium / portal-ui

0 stars 0 forks source link

Investigating React's state behavior #1337

Open libpitt opened 3 months ago

libpitt commented 3 months ago

Important: "Updating a React component’s state is asynchronous. It does not happen immediately."

Here you go @maxsibilla and @tjmadonna. Tuesday's assignment done. I was curious. :)

Say we have the following context and accompanying component (to save space didn't write any import or export statements):

const FooContext = createContext()
const FooProvider = ({}) => {
    const [a, setA] = useState(0)
    const [b, setB] = useState(null)
    const [c, setC] = useState(null)

    const someOtherOperation = () => {
        return b + 5
    }

    return (
        <Foo.Provider
            value={{
            a, setA,
            b, setB,
            c, setC,
            someOtherOperation
            }}>
        </Foo.Provider>
        )
}

const Foo = ({}) => {
    const {a, setA, b, setB, c, setC, someOtherOperation} = useContext(FooContext)

    useEffect(() => {

    }, [])

    const handleClick = () => {
        setA(a + 1)
        setB(a + 2)
        setC(someOtherOperation())
    }

    return <button onClick={handleClick}>Click me</button>
}

By calling setB and someOtherOperation, one could potentially end up with unexpected results, errors or dealing with old state values.

To avoid this, want to update the dependency array in useEffect, and run a callback whenever a state has changed.

Side note: I think many developers probably miss the ease of this with class based components. Which with the same component in class form would declare a callback with this.setState({ a: a + 1}, this.updateB)

const Foo({}) => {

    const {a, setA, b, setB, c, setC, someOtherOperation} = useContext(FooContext)

    const updateB = () => setB(a + 2)

    useEffect(() => {
        updateB()
    }, [a])

    const handleClick = () => {
        setA(a + 1)
        //setC(someOtherOperation())
    }

    return <button onClick={handleClick}>Click me</button>
}

Now one would think that just add b as another dependency. But we'd run into a problem. Everything inside the useEffect method is going to get run, how do we determine which callback we mean to run?

We could just add a new useEffect:

const Foo({}) => {
    const {a, setA, b, setB, c, setC, someOtherOperation} = useContext(FooContext)

    const updateB = () => setB(a + 2)

    useEffect(() => {
        updateB()
    }, [a])

    useEffect(() => {
        setC(someOtherOperation())
    }, [b])

    const handleClick = () => {
        setA(a + 1)
    }

    return <button onClick={handleClick}>Click me</button>
}

I think it is obvious how this could potentially get very messy with a large app. So maybe want to group related values together.

const FooProvider = ({}) => {
    const [values, setValues] = useState({a: 0, b: null, c: null, nextAction: null})

    const someOtherOperation = () => {
        return b + 5
    }

    return (
        <Foo.Provider
            value={{
            values, setValues
            someOtherOperation
            }}>
        </Foo.Provider>
        )
}

const Foo({}) => {
    const {values, setValues, someOtherOperation} = useContext(FooContext)

    const updateValues() = () => {
        switch(values.nextAction) {
            'updateB':
                setValues({... values, b: a + 2, nextAction: 'updateC'})
                break;
                        'updateC:
                                  setValues({... values, c: someOtherOperation())
                                  break;
            default:
                // Nothing here ...
        }
    }

    useEffect(() => {
        updateValues()
    }, [values])

    const handleClick = () => {
        setValues({... values, a: a + 1, nextAction: 'updateB'})
    }

    return <button onClick={handleClick}>Click me</button>
}

This is sort of mimicking reducers. It might work for small apps. When dealing with larger apps, that's where reducers come in very handy. Read more here: https://react.dev/reference/react/useReducer

Disclaimer: I didn't run any of the above code for syntax errors.

maxsibilla commented 3 months ago

@libpitt Thanks for this! I was aware of the dependency array in useEffect but wasn't aware of reducers