I am not Eminem.
So, I am not good at saying 7.6 words per second.
I am a React developer.
I barely say 0 words per second.
I do 7.6 state updates per second.
I make To-do apps.
This is how I made my handlers more readable with use-immer
.
This is my Todo component.
import React from 'react';
function Todo({ completed, onChange, onDelete, text }) {
return (
<div>
<input
checked={completed}
name="completed"
onChange={onChange}
type="checkbox"
/>
<input name="text" onChange={onChange} type="text" value={text} />
<button onClick={onDelete}>Delete</button>
</div>
);
}
export default Todo;
This is my App component.
import React, { useState } from 'react';
import Todo from './Todo';
function App() {
const [todos, setTodos] = useState([]);
// const [todos, setTodos] = useImmer([]);
// imagine handlers here
return (
<>
{todos.map(({ completed, text }, index) => (
<Todo
completed={completed}
key={index}
onChange={handleTodoChange(index)}
onDelete={handleTodoDelete(index)}
text={text}
/>
))}
<button onClick={handleTodoAdd}>Add todo</button>
</>
)
}
export default App;
I need three handlers for:
- Adding a new todo
- Deleting a todo
- Editing a todo (its status or text)
And I'm going to write three ways doing that:
- The immutable way
- Using
immer
'sproduce
- Using
useImmer
hook fromuse-immer
.
For people that are not familiar with immer
,produce
is a function that provides a draft for you to mutate and produces the next immutable state.
useImmer
is similar to useState
except that the updater function provides you the draft that can be mutated.
Adding a todo
The immutable way:
const handleTodoAdd = () => {
setTodos(prev => [...prev, { completed: false, text: "" }]);
}
Using produce
:
const handleTodoAdd = () => {
setTodos(prev =>
produce(prev, draft => {
draft.push({ completed: false, text: "" });
})
);
}
Using useImmer
:
const handleTodoAdd = () => {
setTodos(draft => {
draft.push({ completed: false, text: "" });
});
}
Deleting a todo
The immutable way:
const handleDeleteClick = i => () => {
setTodos(prev => prev.filter((_, j) => j !== i));
}
Using produce
:
const handleDeleteClick = i => () => {
setTodos(prev =>
produce(prev, draft => {
draft.splice(i, 1);
})
);
}
Using useImmer
:
const handleDeleteClick = i => () => {
setTodos(draft => {
draft.splice(i, 1);
});
}
Editing a todo
The immutable way:
const handleTodoChange = i => ({ target }) => {
const value = target.type === "checkbox" ? target.checked : target.value;
setTodos(prev =>
prev.map((todo, j) => {
if (j === i) {
return {
...todo,
[target.name]: value
};
}
return todo;
})
);
};
Using produce
:
const handleTodoChange = i => ({ target }) => {
const value = target.type === "checkbox" ? target.checked : target.value;
setTodos(prev =>
produce(prev, draft => {
draft[i][target.name] = value;
})
);
};
Using useImmer
:
const handleTodoChange = i => ({ target }) => {
const value = target.type === "checkbox" ? target.checked : target.value;
setTodos(draft => {
draft[i][target.name] = value;
});
};
Top comments (3)
Nice, as someone fairly inexperienced with react I can still follow what youโre doing and it looks quite similar to how the logic would be in say an angular component or something - may have to give this sort of thing a go some time :) one thing I would say is the
i
andj
notation is not very readable - I know theyโre a throw back to older languages and are quite well understood but it would be fairly trivial to make these a lot clearer especially to someone unfamiliar with the framework/library/context :)Thank you for your comment. I never use
i
andj
. I did that in order to make some functions appear shorter. In real projects I'd useindex
,todoIndex
etc.Help, how do I mute users from my feed?