When, inside your application, you already use Redux state management for centralized access to the data and for some slices of the data, there is no need to store it in the common store. For example, the case with the form, when it is handled in one determined component. It will be unuseful to store the data in a global state and change the data only from one piece of application that is already logically combined, I mean the form component and its children. In this scenario, hook will look pretty in its place. useReducer The hook is a way to keep and manage data in the store for the particular component and its children. So, in the basic scenario when there are no needs in the redux, I will only use the hook itself without heavy libraries just for the small feature. useReducer const [type, dispatch] = useReducer(reducer, initializer(editedType)); For each action, it will require writing the action type and the payload. It grabs quite a lot of lines of code in the file just for the action annotation and their invocation. interface ChangeNameAction { type: 'CHANGE_NAME'; name: string; } To keep all reducer staff in one place, I will create another hook and return back only functions that will dispatch required actions in it to change the state. And hook will return the State and functions to change the state. Then the function to change the name: const setName = (name: string) => { dispatch({ type: 'CHANGE_NAME', name }); }; The reducer function then will have a standard switch-case implementation; I wrapped it with function from the Immer lib. It’s to avoid mistakes while coping in full state. But yeah, in the very original reducer where the object of the state is without multiple nested objects, I will only use the spread operator. produce const reducer: Reducer<TypeDraft, TypeEditorAction> = produce( (draft: TypeDraft, action: CustomTypeEditorAction) => { switch (action.type) { case 'CHANGE_NAME': draft.name = action.name; break; ... default: } } ); And the new hook will be like this: export function useTypeEditor(editedType?: EditedType) { const [type, dispatch] = useReducer(reducer, initializer(editedType)); const setName = (name: string) => { dispatch({ type: 'CHANGE_NAME', name }); }; return { type, setName }; } Since the reducer is just a pure function, instead of creating a reducer manually and wrapping it in the produce function, it’s much more convenient to use RTK createReducer or createSlice function for the hook. Immer useReducer const reducer = createReducer(initialState, builder => builder .addCase(changeNameAction, (state, { payload }) => { state.name = payload; }) ); The actions also create from function instead of describing it through an interface. createAction const name = 'typeForm'; const changeNameAction = createAction<string>(`${name}/changeName`); It will be the same reducer as from the result of function or just pure reducer function but in a more decent way. I don’t require to keep in mind how to properly handle the action. And at the end, for the component, it will be another hook you can simply use. produce const TypeEditorForm: React.FC = () => { const editor = useTypeEditor(type); ... return (<>...</>) } By the way, and is here to properly set the initial state, e.g., if the type was already created, it needs to init the state from it. type initializer Photo by on Steve Johnson Unsplash