2

I'm having a simple component which is supposed to return all messages in database.

const axios = require('axios');
const _ = require('lodash');

const ChatList = () => {
  const [messages, setMessages] = useState([]);
  const [err, setErr] = useState('');

  useEffect(() => {
    const fetchMessages = async () => {
      const allMessages = await axios.get('http://localhost:5000/chat');
      const messagesMapped = Object.keys(allMessages.data)
        .map((id) => allMessages.data[id])
        .map((message) => _.omit(message, '_id'))
        .map((message) => ({
          ...message,
          delete: (
            <Button
              label="Delete"
              className="p-button-outlined p-button-danger"
              onClick={() => handleDelete(message.id)}
            />
          ),
        }));
      setMessages(messagesMapped);
    };
    fetchMessages();
  }, []);

  const handleDelete = (id) => {
    axios
      .delete(`http://localhost:5000/chat/${id}`)
      .then((res) => alert(res.data[0]))
      .then(() =>
        setMessages((messages) =>
          messages.filter((message) => message.id !== id)
        )
      )
      .catch((err) => setErr(err.response.data));
  };
  console.log(messages);
  return (
    <div>
      <Menu />
      <div className="chat-list-div">
        {err && <h1>{err}</h1>}
        <DataTable value={messages} responsiveLayout="scroll">
          <Column field="id" header="Id of message" />
          <Column field="user" header="User" />
          <Column field="message" header="Message" />
          <Column field="delete" header="Operations" />
        </DataTable>
      </div>
    </div>
  );
};

export default ChatList;

I'm a bit confused, because if I write inside handleDelete in axios:

setMessages(messages.filter(message => message.id !== id)))

it seems to return empty array so my DataTable becomes empty.

It works fine if I write it like this:

setMessages(messages => messages.filter(message => message.id !== id)))

The problem is that I don't understand why it works with the 2nd option and does not with the 1st one. I've been using React for half a year and have always written the 1st option and it always WORKED. For example, I have another component which returns games, and the same logic works fine.

const [games,setGames] = useState('')
const handleDelete = (id) => {
        axios.delete(`http://localhost:5000/games/${id}`)
            .then((res) => alert(res.data[0]))
            .then(() => setGames(games.filter(game => game.id !== id)))
            .catch(err => setErr(err.response.data))
    }
...

{games && games.map(game => (
                    <p>{game.id}</p>
))}
                       

Is someone able to make me understand why in 2nd component it (making only setState(prevState.filter), not setState(prevState => prevState.filter) works and in 1st it doesn't?

//edit

Another component (GamesList):

import React, { useEffect, useState } from 'react';
import Menu from '../main/Menu';
import { Card } from 'primereact/card';
import { Button } from 'primereact/button';
const axios = require('axios');
const _ = require('lodash');

const GamesList = () => {
  const [games, setGames] = useState([]);
  useEffect(() => {
    const fetchGames = async () => {
      const allGames = await axios.get('http://localhost:5000/games');
      const gamesMapped = Object.keys(allGames.data)
        .map((id) => allGames.data[id])
        .map((game) => _.omit(game, '_id'));
      setGames(gamesMapped);
    };
    fetchGames();
  }, []);
  console.log(games);
  const [err, setErr] = useState('');
  const handleDelete = (id) => {
    console.log(games);
    axios
      .delete(`http://localhost:5000/games/${id}`)
      .then((res) => alert(res.data[0]))
      .then(() => setGames(games.filter((game) => game.id !== id)))
      .catch((err) => setErr(err.response.data));
  };

  const header = (
    <img
      alt="Card"
      src="images/usercard.png"
      onError={(e) =>
        (e.target.src =
          'https://www.primefaces.org/wp-content/uploads/2020/05/placeholder.png')
      }
    />
  );
  const footer = (gameId) => (
    <span>
      <Button
        label="Delete"
        icon="pi pi-times"
        className="p-button-outlined p-button-danger"
        onClick={() => handleDelete(gameId)}
      />
    </span>
  );

  return (
    <div className="gameslist-main">
      <Menu />
      <div className="gameslist-cards">
        {games &&
          games.map((game) => (
            <Card
              title={game.id}
              key={game.id}
              subTitle="Game"
              style={{ width: '25em' }}
              footer={footer(game.id)}
              header={header}
            >
              <h3>Winning numbers: {game.winningNumbers.toString()}</h3>
              <p>
                Players:{' '}
                {game.players.toString().length === 0 ? (
                  <span>NONE</span>
                ) : (
                  <ul>
                    {game.players.map((player) => (
                      <li key={player.login} className="gameslist-card-player">
                        <span className="gameslist-card-player-span">
                          {player.login}
                        </span>
                        <span className="gameslist-card-player-span">
                          ({player.numbers.toString()})
                        </span>
                        <span className="gameslist-card-player-span">
                          <span className="guessed-numbers">
                            {
                              game.winningNumbers
                                .toString()
                                .split(',')
                                .filter((num) =>
                                  player.numbers
                                    .toString()
                                    .split(',')
                                    .includes(num)
                                ).length
                            }
                          </span>
                        </span>
                        <span className="gameslist-card-player-span">
                          <span className="separator">/</span>
                        </span>
                        <span className="gameslist-card-player-span">
                          <span className="no-guessed-numbers">5</span>
                        </span>
                      </li>
                    ))}
                  </ul>
                )}
              </p>
              {err && <p>err</p>}
            </Card>
          ))}
      </div>
    </div>
  );
};

export default GamesList;
2
  • You can refer to the useState section of this official doc reactjs.org/docs/hooks-reference.html which explains it Commented Jan 8, 2022 at 22:46
  • I found out that in Games component (in the end of my post) if I console.log(games) inside handleDelete, it is visible, but when I console.log(messages) inside handleDelete in my ChatList component, it's empty array, why is that if they are almost the same files? Commented Jan 8, 2022 at 22:47

1 Answer 1

1

This is because of the closure that your useEffect creates when it passes for the first time.

The second solution that you use, where you send a callback, uses current array value and filters that at the moment of usage.

setMessages(messages => messages.filter(message => message.id !== id))

At the moment of the execution of that callback, messages has the current value set by the result of that axios.get method in initial useEffect

To reduce your confusion i changed messages, which is the name of your state, to currentMessages:

setMessages((currentMessages) =>
  currentMessages.filter((message) => message.id !== id)
);

In your first solution:

setMessages(messages.filter(message => message.id !== id)))

messages is an empty array since useEffect triggers only once, and at that moment messages is "closed" and is an empty array since initial value is set like that:

const [messages, setMessages] = useState([]);

This behavior has to do with functional closure, mentioned at the beginning, at it is "fixed" in react by using that callback in setMessages method.

In your other component GamesList where you don't need to use set state method with callback, you have footer method that renders part where you assign handleDelete to be called gets games's state new data every time games gets set. Because setting state re-renders your component, that part gets generated again and handleDelete, when called, will have new games value.

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you for that. I know that useEffect triggers only once, but it sets propely the messages, so it is not empty array, that's why I'm confused when going into handleDelete, why when I console.log(messages) inside handleDelete, it console.logs empty array? I can't understand why in my component games, it works well (it sees the games inside handleDelete) and it doesn't work inside messages...
Would it mean that the first solution „remembers” the messages from the initial state and doesn’t see that the useEffect updates it even though I use the function handleDelete after updating the state? I get your point, the only thing I don’t understand is that in another component it works fine and seems the games as updated state. It also turned out that if I make at the end of file a simple button which goes into handleDelete onClick, it also sees the messages updated. Might setting the button inside object be the problem there?
The problem is closure that remembers messages to be empty array. That useEffect locks everything as it is at the beginning. This means all callbacks that will be called later on such as handleDelete will use old messages. That is the nature of closure in JS functional part, not sure if that has to do with other functional languages. As for the other component, i cannot see where you use handleDelete. Can you fill in the rest of the code, and i will modify my answer to include that part too?
I've just edited my post :)
Great. I will add more explanation for that part. As i thought, the other component gives handleDelete new data on every render. So it will have new state every time it gets used.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.