Search code examples
reactjsoptimizationreact-hooksuse-staterenderer

Update one element of big list without re render others elements in react hooks?


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


Solution

  • 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 ;)