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.