Reputation: 247
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;
Upvotes: 2
Views: 595
Reputation: 8332
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.
Upvotes: 1