I have following nested object as state.
interface name {
firstName: string;
lastName: string;
}
type NameType = name;
interface employer {
name: string;
state: string;
}
type EmployerType = employer;
interface person {
name: NameType;
age: number;
employer: EmployerType;
}
type PersonType = person;
const defaultPerson: PersonType = {
name: {
firstName: "The",
lastName: "Rock"
},
age: 25,
employer: {
name: "Noone",
state: "Nowhere"
}
};
To update the nested object in state when I use [...] spread operator at second level in case of useState hook, it works just as expected See this working code.
export default function App() {
const [person, setPerson] = useState<PersonType>(defaultPerson);
function handleInputChange(input: string) {
setPerson({
...person,
name: {
...person.name,
firstName: input
}
});
}
return (
<div className="App">
<input onChange={(e) => handleInputChange(e.target.value)} />
<h2>{JSON.stringify(person, null, 4)}</h2>
</div>
);
}
But if I do same thing with a reducer and useReducer hook, Typescript is not liking it and gives error that I am not able to understand. The type error can be seen in codesandbox. See this broken code.
interface action {
type: string;
fieldName: string;
value: string | number;
}
type ActionType = action;
function reducer(state: PersonType, action: ActionType) {
switch (action.type) {
case "firstName": {
return {
...state,
name: {
...state.name,
firstName: action.value
}
};
}
}
return state;
}
export default function App() {
const [person, dispatch] = useReducer(reducer, defaultPerson);
return (
<div className="App">
<input
onChange={(e) =>
dispatch({
type: "test",
fieldName: "firstName",
value: e.target.value
})
}
/>
<h2>{JSON.stringify(person, null, 4)}</h2>
</div>
);
}
Though I am able to cope the state to an entirely new object and update that new object instead, but that feels like a hack and not the right way.
const newState = {...state}
const newName = {...newState.name}
newName.firstName = action.value
newState.name = newName
return newState