I’m learning useReducer. I defined a Todo class and passed an instance to action.payload, but the toggle action did not work with immer in strict mode. The toggle action always be executed twice, so that the isComplete is changed from false to true to false. When I changed the class to a type alias, it worked.
My reduce function :
<code>export function todoReducer(todos:Todo[], action: Action<Todo>):Todo[] {
switch (action.type) {
case ActionTypes.ADD_TODO:
return [...todos, action.payload as Todo];
case ActionTypes.DELETE_TODO:
return todos.filter(todo => todo.id !== action.payload!.id);
case TodoActionTypes.TOGGLE_TODO:
return produce(todos, (draft:Todo[]) => {
const todo = draft.find((todo => todo.id === (action.payload as number)))!
todo.isComplete = !todo.isComplete;
console.log(todo.isComplete);
});
default:
return todos;
}
}
</code>
<code>export function todoReducer(todos:Todo[], action: Action<Todo>):Todo[] {
switch (action.type) {
case ActionTypes.ADD_TODO:
return [...todos, action.payload as Todo];
case ActionTypes.DELETE_TODO:
return todos.filter(todo => todo.id !== action.payload!.id);
case TodoActionTypes.TOGGLE_TODO:
return produce(todos, (draft:Todo[]) => {
const todo = draft.find((todo => todo.id === (action.payload as number)))!
todo.isComplete = !todo.isComplete;
console.log(todo.isComplete);
});
default:
return todos;
}
}
</code>
export function todoReducer(todos:Todo[], action: Action<Todo>):Todo[] {
switch (action.type) {
case ActionTypes.ADD_TODO:
return [...todos, action.payload as Todo];
case ActionTypes.DELETE_TODO:
return todos.filter(todo => todo.id !== action.payload!.id);
case TodoActionTypes.TOGGLE_TODO:
return produce(todos, (draft:Todo[]) => {
const todo = draft.find((todo => todo.id === (action.payload as number)))!
todo.isComplete = !todo.isComplete;
console.log(todo.isComplete);
});
default:
return todos;
}
}
The class definition:
<code>export default class Todo {
id: number;
title: string;
isComplete: boolean;
constructor(title: string) {
this.id = Date.now();
this.title = title;
this.isComplete = false;
}
}
</code>
<code>export default class Todo {
id: number;
title: string;
isComplete: boolean;
constructor(title: string) {
this.id = Date.now();
this.title = title;
this.isComplete = false;
}
}
</code>
export default class Todo {
id: number;
title: string;
isComplete: boolean;
constructor(title: string) {
this.id = Date.now();
this.title = title;
this.isComplete = false;
}
}
The type definition:
<code>export type Todo = {
id: number;
title: string;
isComplete: boolean;
};
</code>
<code>export type Todo = {
id: number;
title: string;
isComplete: boolean;
};
</code>
export type Todo = {
id: number;
title: string;
isComplete: boolean;
};
Compensation:
The component event handler and dispatch calling:
<code> const TodoReducer = () => {
const inputRef = createRef<HTMLInputElement>();
const [todos, dispatch] = useReducer(todoReducer, []);
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const title = inputRef.current?.value;
if (title) {
const newTodo: Todo = { id: Date.now(), title: title, isComplete: false };
dispatch({ type: ActionTypes.ADD_TODO, payload: newTodo });
inputRef.current.value = "";
}
};
const onDelete = (todo: Todo) => {
dispatch({ type: ActionTypes.DELETE_TODO, payload: todo });
};
const onToggle = (todo: Todo) => {
dispatch({ type: ActionTypes.TOGGLE_TODO, payload: todo });
};
return (
<>
<h1>useReducer Hook:</h1>
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Input todo title" ref={inputRef} />
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => (
<TodoCard
key={todo.id}
todo={todo}
onDelete={onDelete}
onToggle={onToggle}
/>
))}
</>
);
};
export default TodoReducer;
const TodoCard = ({ todo, onDelete, onToggle }: Props) => {
return (
<div>
<span
style={{ textDecoration: todo.isComplete ? "line-through" : "none" }}
>
{todo.title}
</span>
<button onClick={() => onToggle(todo)}>Toggle</button>
<button onClick={() => onDelete(todo)}>Delete</button>
</div>
);
};
</code>
<code> const TodoReducer = () => {
const inputRef = createRef<HTMLInputElement>();
const [todos, dispatch] = useReducer(todoReducer, []);
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const title = inputRef.current?.value;
if (title) {
const newTodo: Todo = { id: Date.now(), title: title, isComplete: false };
dispatch({ type: ActionTypes.ADD_TODO, payload: newTodo });
inputRef.current.value = "";
}
};
const onDelete = (todo: Todo) => {
dispatch({ type: ActionTypes.DELETE_TODO, payload: todo });
};
const onToggle = (todo: Todo) => {
dispatch({ type: ActionTypes.TOGGLE_TODO, payload: todo });
};
return (
<>
<h1>useReducer Hook:</h1>
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Input todo title" ref={inputRef} />
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => (
<TodoCard
key={todo.id}
todo={todo}
onDelete={onDelete}
onToggle={onToggle}
/>
))}
</>
);
};
export default TodoReducer;
const TodoCard = ({ todo, onDelete, onToggle }: Props) => {
return (
<div>
<span
style={{ textDecoration: todo.isComplete ? "line-through" : "none" }}
>
{todo.title}
</span>
<button onClick={() => onToggle(todo)}>Toggle</button>
<button onClick={() => onDelete(todo)}>Delete</button>
</div>
);
};
</code>
const TodoReducer = () => {
const inputRef = createRef<HTMLInputElement>();
const [todos, dispatch] = useReducer(todoReducer, []);
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const title = inputRef.current?.value;
if (title) {
const newTodo: Todo = { id: Date.now(), title: title, isComplete: false };
dispatch({ type: ActionTypes.ADD_TODO, payload: newTodo });
inputRef.current.value = "";
}
};
const onDelete = (todo: Todo) => {
dispatch({ type: ActionTypes.DELETE_TODO, payload: todo });
};
const onToggle = (todo: Todo) => {
dispatch({ type: ActionTypes.TOGGLE_TODO, payload: todo });
};
return (
<>
<h1>useReducer Hook:</h1>
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Input todo title" ref={inputRef} />
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => (
<TodoCard
key={todo.id}
todo={todo}
onDelete={onDelete}
onToggle={onToggle}
/>
))}
</>
);
};
export default TodoReducer;
const TodoCard = ({ todo, onDelete, onToggle }: Props) => {
return (
<div>
<span
style={{ textDecoration: todo.isComplete ? "line-through" : "none" }}
>
{todo.title}
</span>
<button onClick={() => onToggle(todo)}>Toggle</button>
<button onClick={() => onDelete(todo)}>Delete</button>
</div>
);
};
The class instance did work without immer
<code>case ActionTypes.TOGGLE_TODO:
return todos.map(todo => {
if (todo.id === action.payload!.id) {
return {...todo, isComplete:!action.payload!.isComplete}
}
return todo;
});
</code>
<code>case ActionTypes.TOGGLE_TODO:
return todos.map(todo => {
if (todo.id === action.payload!.id) {
return {...todo, isComplete:!action.payload!.isComplete}
}
return todo;
});
</code>
case ActionTypes.TOGGLE_TODO:
return todos.map(todo => {
if (todo.id === action.payload!.id) {
return {...todo, isComplete:!action.payload!.isComplete}
}
return todo;
});