Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useState always is default value in itself

I'm using useState to manage the state, it's working fine. But when i return state inside itseft, it always value of initial

import react, {useState} from 'react'

const MyComponent = () => {
    const [myState, setMyState] = useState({
        value: 'initial value',
        setValue: (newValue) => {
            setMyState({...myState, value: newValue})
            console.log(myState.value) //<--always is 'initial value'
        }
    })

    return(
        <>
            <p>{myState.value}</p> //<-- it's working fine
            <input value={myState.value} onChange={(e) => myState.setValue(e.target.value)} /> //<--working fine too

        </>
    )
}

I expect the console.log is value of input, but the actual output always is initial value

like image 766
mikenlanggio Avatar asked May 13 '19 09:05

mikenlanggio


2 Answers

const [myState, setMyState] = useState({
    value: 'initial value',
    setValue: (newValue) => {
        setMyState({...myState, value: newValue})
        console.log(myState.value) //<--always is 'initial value'
    }
})

The first time your component function is run, the setValue function captures the initial value of myState. The second time it is run, you make a copy of the setValue function—but this is the function that has has captured the initial value of myState. It never updates.

Since the function never changes, you should not put it in useState() in the first place. You can define the function separately.

const [myState, setMyState] = useState({ value: 'initial value' })
const setValue = (newValue) => {
  setMyState({ ...myState, value: newValue })
}

Now, a new setValue copy is created every time the component function is run. When capturing variables, you can use useCallback() for optimization; if the values didn't change, React will reuse old copies of the function.

const [myState, setMyState] = useState({ value: 'initial value' })
const setValue = useCallback((newValue) => {
  setMyState({ ...myState, value: newValue })
}, [myState]) // ← this bit ensures that the value is always up to date inside the callback

As mentioned by Shubham Khatri, there is an even faster and better approach in this situation: using the functional form of setState.

const [myState, setMyState] = useState({ value: 'initial value' })
const setValue = useCallback((newValue) => {
  setMyState((prev) => ({ ...prev, value: newValue }))
}, []) // ← now we use an empty array, so React will never update this callback

Any of these three methods are fine to use, though; they will all work and perform good enough for most use cases.


Per comments, you're attempting to create an object that is passed down via context. A way to do this is by creating the context object in a separate step, similarly to how we created the callback function. This time, we use useMemo, which is similar to useCallback but works for any type of object.

// Per T.J. Crowder's answer, you can use separate `useState` calls if you need multiple values.
const [myState, setMyState] = useState('initial value')
const ctx = useMemo(() => ({
  value: myState,
  setValue: (newValue) => {
    setMyState(newValue)
  }
}), [myState])

return (
  <Provider value={ctx}>
    {children}
  </Provider>
)
like image 159
goto-bus-stop Avatar answered Nov 08 '22 21:11

goto-bus-stop


goto-bus-stop's answer explains why you're getting the problem you're getting. But there's another thing to address here:

From the code you've provided, it looks like you're using an object as your state value. In particular, this line:

setMyState({...myState, value: newValue})

..suggests you intend myState to have multiple things in it, with value just being one of them.

That's not how you do it with hooks. It's fine to have an object as a state value, but generally you do that when you're going to update (that is, replace) the entire object when the state changes. If you're updating individual parts of the state (as suggested by the above), you use individual useState calls instead of an object. See comments:

const {useState} = React;

const MyComponent = () => {
    // Separate `useState` calls for each state item
    const [value, setValue] = useState('initial value');
    const [anotherValue, setAnotherValue] = useState('another value');

    // No need to create your own update function, that's what `setValue` and
    // `setAnotherValue` are, just use them:

    return(
        <React.Fragment>
            <p>{value}</p>
            <input value={value} onChange={(e) => setValue(e.target.value)} />
            <p>{anotherValue}</p>
            <input value={anotherValue} onChange={(e) => setAnotherValue(e.target.value)} />
        </React.Fragment>
    );
}

ReactDOM.render(<MyComponent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

This separation is particularly useful if you have any side effects of state changes, because you can specify what state triggers the side effect. For instance, here's the component above which triggers a console.log when value changes but not when anotherValue changes, and another effect that occurs when either of them changes:

const {useState, useEffect} = React;

const MyComponent = () => {
    // Separate `useState` calls for each state item
    const [value, setValue] = useState('initial value');
    const [anotherValue, setAnotherValue] = useState('another value');
    // A counter for all changes; we use -1 because
    // our effect runs on initial mount
    const [changes, setChanges] = useState(-1);

    // No need to create your own update function, that's what `setValue` and
    // `setAnotherValue` are, just use them:
  
    // A side-effect for when `value` changes:
    useEffect(() => {
        console.log(`value changed to ${value}`);
    }, [value]); // <=== Notice that we declare what triggers this effect

    // A side-effect for when *either* `value` or `anotherValue` changes:
    useEffect(() => {
        setChanges(changes + 1);
    }, [value, anotherValue]);

    return(
        <React.Fragment>
            <p>Total changes: {changes}</p>
            <p>{value}</p>
            <input value={value} onChange={(e) => setValue(e.target.value)} />
            <p>{anotherValue}</p>
            <input value={anotherValue} onChange={(e) => setAnotherValue(e.target.value)} />
        </React.Fragment>
    );
}

ReactDOM.render(<MyComponent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
like image 30
T.J. Crowder Avatar answered Nov 08 '22 19:11

T.J. Crowder