in my project I show a list where all the pokemons categories are.
When the user clicks on a certain category the list is updated.
My list is updating, but the problem is that my component is not re-rendering again with the new list items.
import React from "react";
import { types, pokemons } from "./data";
import Avatar from "./components/Avatar";
import List from "./components/List";
import "./styles.css";
const App = () => {
const [favorite, setFavorite] = React.useState("rock");
console.log(favorite);
const _data = [];
React.useMemo(
() =>
pokemons.map((pokemon, i) => {
if (pokemon.type.includes(favorite.toLowerCase())) {
_data.push(pokemon);
}
return _data;
}),
[_data, favorite]
);
const removeDup = [];
_data.reduce((acc, curr) => {
if (acc.indexOf(curr.name) === -1) {
acc.push(curr.name);
removeDup.push(curr);
}
return acc;
}, []);
return (
<div className="App">
<Avatar data={types} setFavorite={setFavorite} />
<List data={removeDup} />
</div>
);
};
export default App;
List
const List = ({ data }) => {
const [pokemonsState, setPokemonsState] = useState(data);
const [isAscSort, setIsAscSort] = useState(false);
const sortPokemon = () => {
if (isAscSort)
setPokemonsState(stableSort(data, getComparator("asc", "name")));
else setPokemonsState(stableSort(data, getComparator("desc", "name")));
setIsAscSort(!isAscSort);
};
return (
<TableContainer>
<Table sx={{ minWidth: 650 }}>
<TableHead>
<TableRow>
<TableCell>Pokémon</TableCell>
<TableCell name onClick={() => sortPokemon()} align="right">
Name
{!isAscSort ? <ArrowUpward /> : <ArrowDownward />}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{pokemonsState.map((pokemon, idx) => (
<TableRow
key={idx}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell component="th" scope="row" thumbnailImage>
<div className="thumb">
<img src={pokemon.thumbnailImage} alt="" />
</div>
</TableCell>
<TableCell align="left" component="th" scope="row" description>
{pokemon.name}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
};
Could you tell me what I'm doing wrong?
Thank you very much in advance!!!
CodePudding user response:
Issues
You've at least a couple issues.
- Using the mapped array index as the React key is generally an anti-pattern, especially if you are filtering, sorting, mutating the underlying array being mapped.
- The
List
component doesn't update itspokemonsState
state when thedata
prop updates.
Solution
Use a useEffect
with a dependency on the data
prop to update the local pokemonsState
state. use the pokemon.id
as the React key, assuming all pokemon have unique id
properties.
const List = ({ data }) => {
const [pokemonsState, setPokemonsState] = useState(data);
const [isAscSort, setIsAscSort] = useState(false);
// Update local state when prop updates
useEffect(() => {
setPokemonsState(data);
}, [data]);
const sortPokemon = () => {
if (isAscSort)
setPokemonsState(stableSort(data, getComparator("asc", "name")));
else setPokemonsState(stableSort(data, getComparator("desc", "name")));
setIsAscSort(!isAscSort);
};
return (
<TableContainer>
<Table sx={{ minWidth: 650 }}>
...
<TableBody>
{pokemonsState.map((pokemon, idx) => (
<TableRow
key={pokemon.id} // <-- use unique React key
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
...
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
};
CodePudding user response:
Your List component is trying to copy the data prop into its state. Copying props into state is usually a bad idea. If that prop changes, the List will ignore the change and continue using its state. Only once something changes the state (eg, clicking on the sort button) will you get back in sync.
I would recommend that you delete the state and instead compute the sorted list from the prop. This computation can be put inside of useMemo
to improve performance by skipping calculating if nothing has changed:
const List = ({ data }) => {
const [isAscSort, setIsAscSort] = useState(false);
const sortedPokemons = useMemo(() => {
if (isAscSort) {
return stableSort(data, getComparator("asc", "name"))
} else {
return stableSort(data, getComparator("desc", "name"))
}
}, [data, isAscSort]);
const sortPokemon = () => {
setIsAscSort(!isAscSort);
};
// ...
<TableBody>
{sortedPokemons.map((pokemon, idx) => (
// ...
)}
</TableBody>
}