No correct data in fetching in react hook

Viewed 35

Following is the code which is working fine on initial load time, but on clicking fetch data button its now fetching the data correctly. Let me know what I am doing wrong here.

Code -

const Sample = () => {
  const [todoCount, setTodoCount] = useState(3);
  const [todos, setTodos] = useState([]);
  useEffect(() => {
    Array(todoCount)
      .fill()
      .map((_x, i) => {
        return axios
          .get(`https://jsonplaceholder.typicode.com/todos/${i + 1}`)
          .then((res) => {
            const todo = res.data.title;
            setTodos((prevTodos) => {
              return [...prevTodos, todo];
            });
          });
      });
  }, [todoCount]);

  const fetchMore = (e) => {
    setTodoCount((prevCount) => prevCount + 1);
  };

  return (
    <div className="example">
      <h3>Fetch data in a loop</h3>
      <ul>
        {todos.map((x, i) => (
          <li key={i}>{x}</li>
        ))}
      </ul>

      <button onClick={fetchMore}>Fetch More</button>
    </div>
  );
};

export default Sample;

CodeSandBox - https://codesandbox.io/s/black-mountain-y8xpl

Expected Result -

  1. Initial Load - 3 Titles
  2. Click "Fetch More" button it is adding 4 to the list, making it 7 instead it should be 4(increment by 1 only).
2 Answers

If you run the setTodos inside the map function, it runs every single time accumulating the Todos. You can try this out:

const Sample = () => {
  const [todoCount, setTodoCount] = useState(3);
  const [todos, setTodos] = useState([]);
  useEffect(() => {
    const todoArray = Array(todoCount)
      .fill()
      .map((_x, i) => {
        return axios
          .get(`https://jsonplaceholder.typicode.com/todos/${i + 1}`)
          .then((res) => {
            const todo = res.data.title;
            return todo;
          });
      });
    Promise.all(todoArray).then((todos) => {
      console.log(todos);
      setTodos(todos);
    });
  }, [todoCount]);

Setting the todos at once will also make the app re-render only once every fetchMore click, instead for every todo in the outcome.

you face this behavior because once todoCount updates useEffect runs again. Now todoCount is 4 in total, this way it will fetch 4 more todos (which most will be repeated since only last i value should bring a new value).

given your code you could break into a initial count fetch on Mount, and other to fetch the subsequent ones:

const initialCount = 3
const baseUrl = 'https://jsonplaceholder.typicode.com/todos'

const Sample = () => {
  const [todoCount, setTodoCount] = useState(initialCount);
  const [todos, setTodos] = useState([]);

  useEffect(() => {
    Array(initialCount)
      .fill()
      .map((_x, i) => {
        return axios
          .get(`${baseUrl}/${i + 1}`)
          .then((res) => {
            const todo = res.data.title;
            setTodos((prevTodos) => {
              return [...prevTodos, todo];
            });
          });
      });
  }, []);

  useEffect(() => {
    if (todoCount === initialCount) return
    axios
      .get(`${baseUrl}/${todoCount}`)
      .then((res) => {
        const todo = res.data.title;
          setTodos((prevTodos) => {
            return [...prevTodos, todo];
          });
       })
  }, [todoCount])

though todoCount is derived from todos length which is a redundant state you can remove it. after that, you could change for only one useEffect and change fetch function to use todos.length as reference:

const initialCount = 3
const baseUrl = 'https://jsonplaceholder.typicode.com/todos'

const Sample = () => {
  const [todos, setTodos] = useState([]);
  useEffect(() => {
    Array(initialCount)
      .fill()
      .map((_x, i) => {
        return axios
          .get(`${baseUrl}/${i + 1}`)
          .then((res) => {
            const todo = res.data.title;
            setTodos((prevTodos) => {
              return [...prevTodos, todo];
            });
          });
      });
  }, []);

  const fetchMore = (e) => {
    axios
      .get(`${baseUrl}/${todos.length + 1}`)
      .then((res) => {
         const todo = res.data.title;
         setTodos((prevTodos) => {
           return [...prevTodos, todo];
         });
       });
  };
Related