Reputation: 265
i want to optimize my react App by testing with a large list of li
Its a simple todo List.
By exemple, when click on a li
, task will be line-through, and check icon will be green. This simple action is very slow with a large list because, the whole list is re render.
How to do this with React Hooks?
function App() {
const [list, setList] = useState([]);
const [input, setInput] = useState("");
const inputRef = useRef(null);
useEffect(() => inputRef.current.focus(), []);
//Pseudo Big List
useEffect(() => {
const test = [];
let done = false;
for (let i = 0; i < 5000; i++) {
test.push({ task: i, done });
done = !done;
}
setList(test);
}, []);
const handlerSubmit = (e) => {
e.preventDefault();
const newTask = { task: input, done: false };
const copy = [...list, newTask];
setList(copy);
setInput("");
};
const checkHandler = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy[index].done = !copy[index].done;
setList(copy);
};
const suppression = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy.splice(index, 1);
setList(copy);
};
const DisplayList = () => {
return (
<ul>
{list.map((task, index) => (
<Li
key={index}
task={task}
index={index}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
);
};
//JSX
return (
<div className='App'>
<h1>TODO JS-REACT</h1>
<form id='form' onSubmit={handlerSubmit}>
<input
type='text'
placeholder='Add task'
required
onChange={(e) => setInput(e.target.value)}
value={input}
ref={inputRef}
/>
<button type='submit'>
<i className='fas fa-plus'></i>
</button>
</form>
{list.length === 0 && <div id='noTask'>No tasks...</div>}
<DisplayList />
</div>
);
}
export default App;
Li component
import React from "react";
export default function Li(props) {
return (
<li
onClick={(e) => props.checkHandler(e, props.index)}
className={props.task.done ? "line-through" : undefined}
>
{props.task.task}
<span className='actions'>
<i className={`fas fa-check-circle ${props.task.done && "green"}`}></i>
<i
className='fas fa-times'
onClick={(e) => props.suppression(e, props.index)}
></i>
</span>
</li>
);
}
CodeSandbox here: https://codesandbox.io/s/sad-babbage-kp3md?file=/src/App.js
Upvotes: 0
Views: 2813
Reputation: 93
I had the same question, as @Dvir Hazout answered, I followed this article and made your code the changes you need:
function App() {
const [list, setList] = useState([]);
const { register, handleSubmit, reset } = useForm();
//Pseudo Big List
useEffect(() => {
const arr = [];
let done = false;
for (let i = 0; i < 20; i++) {
arr.push({ id: uuidv4(), task: randomWords(), done });
done = !done;
}
setList(arr);
}, []);
const submit = ({ inputTask }) => {
const newTask = { task: inputTask, done: false, id: uuidv4() };
setList([newTask, ...list]);
reset(); //clear input
};
const checkHandler = useCallback((id) => {
setList((list) =>
list.map((li) => (li.id !== id ? li : { ...li, done: !li.done }))
);
}, []);
const suppression = useCallback((id) => {
setList((list) => list.filter((li) => li.id !== id));
}, []);
//JSX
return (
<div className="App">
<h1>TODO JS-REACT</h1>
<form onSubmit={handleSubmit(submit)}>
<input type="text" {...register("inputTask", { required: true })} />
<button type="submit">
<i className="fas fa-plus"></i>
</button>
</form>
{list.length === 0 && <div id="noTask">No tasks...</div>}
<ul>
{list.map((task, index) => (
<Li
key={task.id}
task={task}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
</div>
);
}
Li component
import React, { memo } from "react";
const Li = memo(({ task, suppression, checkHandler }) => {
// console.log each time a Li component re-rendered
console.log(`li ${task.id} rendered.`);
return (
<li
onClick={(e) => checkHandler(task.id)}
className={task.done ? "line-through" : undefined}
>
{task.task}
<span className="actions">
<i className={`fas fa-check-circle ${task.done && "green"}`}></i>
<i className="fas fa-times" onClick={(e) => suppression(task.id)}></i>
</span>
</li>
);
});
export default Li;
You can check it live here I know it's probably late for your question, but may help others ;)
Upvotes: 1
Reputation: 261
You can use React.memo
and wrap the Li
component. This will cache the instances of the Li
component based on shallow comparison. Read more in the docs
Otherwise, if you don't need the state in the container, you can keep it locally in the Li
component and then it won't cause a whole list rerender.
Upvotes: 0