I'm trying to get the newest state
right after setting it. Note: The change is triggered by typing in the input
.
import { useReducer } from "react";
const App = () => {
const initialState = { value: "Initial state", elements: [] };
const [state, setState] = useReducer(
(state: any, updates: any) => ({ ...state, ...updates }),
initialState
);
function handleInputChange(event: any) {
// setState({
// value: event.target.value,
// elements: [{ text: event.target.value }]
// });
const newState = {
value: event.target.value,
elements: [{ text: event.target.value }]
};
setState((prevState: any) => {
return { ...prevState, ...newState };
});
console.log(state.value);
console.log(state.elements);
}
return (
<div>
<input
id="input"
type="text"
onChange={handleInputChange}
value={state.value}
/>
</div>
);
};
export default App;
With this:
setState({
value: event.target.value,
elements: [{ text: event.target.value }]
});
The console.log
's would log the old values. For example, if you typed a
, they would log: Initial state
and []
respectively. Instead of what I expected: a
and [Object]
.
So I tried to set state
with a function instead, hoping that I'd get the latest value of state
:
setState((prevState: any) => {
return { ...prevState, ...newState };
});
However, I'm getting Initial state
and []
again ... this time not matter how many times I type in the input
.
How can I get the latest value of state
?
Live code:
CodePudding user response:
The first approach
setState({
value: event.target.value,
elements: [{ text: event.target.value }]
});
console.log(state.value);
console.log(state.elements);
results in out-of-date values being logged because the call to a state setter doesn't result in the state being updated immediately. You must wait for the component to re-render before the new values are returned by useReducer
and subsequently put into the state
variable.
The second approach
setState((prevState: any) => {
return { ...prevState, ...newState };
});
doesn't work because you're now passing a function to the reducer, so in the reducer:
(state: any, updates: any) => ({ ...state, ...updates }),
the above resolves to
(state: any, updates: any) => ({ ...state, ...theFunctionYouPassed }),
But functions don't have enumerable own properties, so the returned object is no different from the initial state
.
useReducer
is not the same as useState
, and your use of the setState
variable name (rather than the more conventional dispatch
) may be throwing you off. If you want to use the functional form of state updates (though it wouldn't help anything in this particular situation), use useState
instead.
const { useState, useEffect } = React;
const App = () => {
const [state, setState] = useState({ value: "Initial state", elements: [] });
function handleInputChange(event) {
const newState = {
value: event.target.value,
elements: [{ text: event.target.value }]
};
setState((prevState) => {
return { ...prevState, ...newState };
});
}
useEffect(() => {
console.log(state.value);
console.log(state.elements);
}, [state]);
return (
<div>
<input
id="input"
type="text"
onChange={handleInputChange}
value={state.value}
/>
</div>
);
};
ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>
Or change your reducer to accept a function.
const { useReducer, useEffect } = React;
// An example of how you would type and use useReducer
// to accept a callback, like useState's does:
/*
type State = { value: string, elements: Array<{ text: string }>};
const reducer = (
state: State,
stateUpdater: (oldState: State) => State
) => stateUpdater(state)
*/
const reducer = (state, stateUpdater) => stateUpdater(state);
const App = () => {
const [state, dispatch] = useReducer(reducer, { value: "Initial state", elements: [] });
function handleInputChange(event) {
const newState = {
value: event.target.value,
elements: [{ text: event.target.value }]
};
dispatch((prevState) => {
return { ...prevState, ...newState };
});
}
useEffect(() => {
console.log(state.value);
console.log(state.elements);
}, [state]);
return (
<div>
<input
id="input"
type="text"
onChange={handleInputChange}
value={state.value}
/>
</div>
);
};
ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>
You also might consider avoiding any
, which defeats the purpose of TypeScript - typing things properly helps turn runtime errors into easier-to-solve compile-time errors.