React - Warning: Each child in a list should have a unique "key" prop - reactjs

In this simple React App, I don't understand why I get the following warning message:
Warning: Each child in a list should have a unique "key" prop.
To me it seems that I put the key at the right place, in form of key={item.login.uuid}
How can I get rid of the warning message?
Where would be the right place to put the key?
App.js
import UserList from './List'
const App = props => {
const [id, newID] = useState(null)
return (
<>
<UserList id={id} setID={newID} />
</>
)
}
export default App
List.js
const UserList = ({ id, setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me'
)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
const renderItem = (item, newID) => {
return (
<>
{newID ? (
// User view
<div key={item.login.uuid}>
<div>
<h2>
{item.name.first} {item.name.last}
</h2>
<p>
{item.phone}
<br />
{item.email}
</p>
<button onClick={() => setID(null)}>
Back to the list
</button>
</div>
</div>
) : (
// List view
<li key={item.login.uuid}>
<div>
<h2>
{item.name.first} {item.name.last}
</h2>
<button onClick={() => setID(item.login.uuid)}>
Details
</button>
</div>
</li>
)}
</>
)
}
const user = resources.find(user => user.login.uuid === id)
if (user) {
// User view
return <div>{renderItem(user, true)}</div>
} else {
// List view
return (
<ul>
{resources.map(user => renderItem(user, false))}
</ul>
)
}
}
export default UserList

The key needs to be on the root-level element within the loop. In your case, that's the fragment (<>).
To be able to do that, you'll need to write it out fully:
const renderItem = (item, newID) => {
return (
<Fragment key={item.login.uuid}>
{newID ? (
...
)}
</Fragment>
);
}
(You can add Fragment to your other imports from react).
Note that the fragment isn't actually needed in your example, you could drop it and keep the keys where they are since then the <div> and <li> would be the root element:
const renderItem = (item, newId) => {
return newID ? (
<div key={item.login.uuid}>
...
</div>
) : (
<li key={item.login.uuid}>
...
</li>
)
}

What if you create 2 separate components, one for the user view and one for the list item. That way you only need to pass the user prop. Also, use JSX and pass wht key from there.
const UserList = ({ id, setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me'
)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
const User = ({user}) => (
<div key={user.login.uuid}>
<div>
<h2>
{user.name.first} {user.name.last}
</h2>
<p>
{user.phone}
<br />
{user.email}
</p>
<button onClick={() => setID(null)}>
Back to the list
</button>
</div>
</div>
)
const ListItem = ({user}) => (
<li key={user.login.uuid}>
<div>
<h2>
{user.name.first} {user.name.last}
</h2>
<button onClick={() => setID(user.login.uuid)}>
Details
</button>
</div>
</li>
)
const user = resources.find(user => user.login.uuid === id)
if (user) {
// User view
return <User user={user}</div>
} else {
// List view
return (
<ul>
{resources.map((user, index) => <ListItem key={index} user={user} />)}
</ul>
)
}
}
export default UserList

Related

How i can refresh this function on started value

Hi! i have a problem with my state in React, I have two onMouse functions, the first one is to add an element and the second one is to delete, unfortunately the second one does not delete and the added element 'opacity' is rendered.
let menuItems = ['Tasks', 'Issues', 'Files', 'Raports']
const [item, setItem] = useState(menuItems)
const handleSpace = (id) => {
menuItems.splice(id, 0, 'opacity')
setItem([...item])
}
const restart = () => {
menuItems = ['Tasks', 'Issues', 'Files', 'Raports']
setItem([...item])
}
return (
<>
<div className="dashboard" style={slide.flag ? {left: '-105%'} : {left: '0%'}}>
<div className="dashboard__nav">
<ul className="dashboard__nav-list">
{item.map((item, id) => {
return <li className="dashboard__nav-item" key={id} onMouseOver={() => handleSpace(id)} onMouseLeave={restart}>{item}</li>
})}
</ul>
</div>
<div className="dashboard__array">
{tasks.map((task, id) => {
return (
<div className="dashboard__array-item" key={id}>
<div className="dashboard__array-item-header">
<p className="dashboard__array-item-header-title">{task}</p>
<button className="dashboard__array-item-header-cancel">
<FontAwesomeIcon icon={faCancel} />
</button>
</div>
<div className="dashboard__array-item-main">
<p className="dashboard__array-item-main-description">{descriptionTasks[id]}</p>
<p className="dashboard__array-item-main-button">Show More</p>
</div>
</div>
)
})}
</div>
</div>
</>
)
I already created setItem(menuItems), it removed the element 'opacity, but juz it didn't add it a second time
It seems that the two functions might be over complicating the handling of the item state.
Try handle setItem without changing another variable menuItems, so it can be used as a reset value at anytime.
Example:
const menuItems = ["Tasks", "Issues", "Files", "Raports"];
const [item, setItem] = useState(menuItems);
const handleSpace = (id) =>
setItem((prev) => {
const newItems = [...prev];
newItems.splice(id, 0, "opacity");
return newItems;
});
const restart = () => setItem(menuItems);
Hope this will help.

Why clicking Back to the list sends me pack to front page? Using hooks

I'm setting a value when you click on Back to List. As per logic it should not go back to the 1st page which appears when I load the browser. I specified a value
<a href="#" onClick={() => setPokemon(pokemon.name)}>
If I wanted to go back to the list page I should have passed null like this.
<a href="#" onClick={() => setPokemon(null)}>
Here is the full code.
import "./styles.css";
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
import axios from "axios";
import { useState } from "react";
const queryClient = new QueryClient();
function App() {
// useEffect(() => {
// queryClient.clear();
// }, []);
const [pokemon, setPokemon] = useState(null);
return (
<QueryClientProvider client={queryClient}>
<div className="App">
{pokemon ? (
<Pokemon pokemon={pokemon} setPokemon={setPokemon} />
) : (
<PokemonList setPokemon={setPokemon} />
)}
</div>
</QueryClientProvider>
);
}
export default App;
function usePokemonList() {
return useQuery("pokemon", async () => {
const { data } = await axios.get(
"https://pokeapi.co/api/v2/pokemon?offset=0&limit=50"
);
return data.results;
});
}
function usePokemon(name) {
return useQuery(["pokemon", name], async () => {
const { data } = await axios.get(
`https://pokeapi.co/api/v2/pokemon/${name}`
);
return data;
});
}
function Pokemon({ pokemon, setPokemon }) {
const { isLoading, data } = usePokemon(pokemon);
return (
<div>
<a href="#" onClick={() => setPokemon(pokemon.name)}>
Back to the list
</a>
{isLoading ? (
<p>loading...</p>
) : (
<div>
<h1>{pokemon}</h1>
<img src={data.sprites.front_default} alt={pokemon} />
</div>
)}
</div>
);
}
function PokemonList({ setPokemon }) {
const { isLoading, data } = usePokemonList();
return (
<div>
{isLoading ? (
<p>loading...</p>
) : (
<ul>
{data.map((pokemon) => (
<li key={pokemon.name}>
<a onClick={() => setPokemon(pokemon.name)} href="#">
{pokemon.name}
</a>
</li>
))}
</ul>
)}
</div>
);
}
The pokemon variable only holds a string value and is not an object with a name property on it. So in your Pokemon component when you pass pokemon.name in setPokemon function you are essentially passing an undefined value because the property name doesn't exist on the string.
And based on the conditional rendering you are doing in the App component since undefined is a falsy value, it renders PokemonList component instead of retaining the Pokemon component. To retain the Pokemon component, use setPokemon(pokemon) instead of setPokemon(pokemon.name).
function Pokemon({ pokemon, setPokemon }) {
//pokemon variable is of string type.
console.log(pokemon) //Prints the selected pokemon
const { isLoading, data } = usePokemon(pokemon);
return (
<div>
<a href="#" onClick={() => setPokemon(pokemon.name)}> {/* Passing pokemon.name is passing a property value that doesn't exist on the object*/}
Back to the list
</a>
To avoid going back to the list, set the pokemon to its actual name or a non-falsy value.
<a href="#" onClick={() => setPokemon(pokemon)}>
Back to the list
</a>

unable to select another option after selection with react select

Updated code: Im trying to first display carsList and only when selectedMake is selected, I would update the state with the result from filter and show another array. I tried storing carsList in updatedCarsList so it has all the cars on page load but Im missing something here.
CarOffers.jsx
const CarOffers = () => {
const [carsList, setCarsList] = useState([]);
const [updatedCarsList, setUpdatedCarsList] = useState([]);
const [selectedMake, setSelectedMake] = useState(undefined);
const getCars = () => {
axios.get(url)
.then((response) => {
return setCarsList(response.data)
})
}
const handleMakeChange = (select) => {
setSelectedMake(select.value)
}
const applyFilters = () => {
let updatedCarsList = carsList
if(selectedMake) {
updatedCarsList = carsList.filter(car => car.make === selectedMake)
setUpdatedCarsList(updatedCarsList);
} else {
setUpdatedCarsList(carsList)
}
}
useEffect(() => {
getCars()
applyFilters()
}, [ selectedMake ]);
return (
<div className="mka__wrapper-car-offers">
<div className="mka__container">
<div className="mka__content-car-offers">
<div className="mka__content-grid-offers">
<div className="item1">
< CarSlider/>
<div className="mka-responsive-item">
< DisplayCars/>
< SortingCars/>
< CarAlignment/>
</div>
</div>
<div className="item2">
<div className="mka__side-bar-divider">
< Search
carsList={carsList}/>
</div>
<div>
< FilterSideBar
carsList={carsList}
handleMakeChange={handleMakeChange} />
</div>
</div>
<div className="item3">
<Cars updatedCarsList={updatedCarsList}/>
</div>
</div>
</div>
</div>
</div>
)
}
export default CarOffers;
Cars.jsx
const Cars = ({ updatedCarsList }) => {
return (
<div className='mka__cars-grid'>
{updatedCarsList.map(car =>
<CarsItem key={car.id} car={car}/>)}
</div>
)
}
export default Cars
CarItem.jsx
const CarsItem = ({car: {year,month,transmission,mileage,price,title,link}}) => {
return (
<Fragment>
<div className="cars-item_wrapper">
<div className="cars-item_image">
<img src={link} alt="car" />
</div>
<div>
<a
className="cars-item_car-title"
href="/"
>
{title}
</a>
</div>
<div className=" cars-item_separator"></div>
<p className="cars-item_car-text">{price}</p>
</div>
</Fragment>
)
}
export default CarsItem
Move your applyFilters above getCars
Does Select need to be in <>
distinctBy... urgh.. use Set const unique = [...new Set(data.map(item => item.value))]
applyFilters... axios is async, but your setting a value so state doesn't update so no re-render? Maybe.
selectedMake - don't use null as a default, use undefined.
Hope that helps, feels like a state management issue.
... think its this ....
You are using carsList as your list of cars, however you are setting the value of carsList with setCarsList(updatedCarsList)... updatedCarsList is a filtered list of cars... only car => car.make === selectedMake so once you've selected a make your carList is only cars with the selected make.
Solution is to
Either separate the list from the filtered list
or preferably keep list, but pass the filtered state to the component that needs it... but not update state of the original list by calling setCarsList(updatedCarsList);
if (selectedMake){
updatedCarsList = updatedCarsList.filter(
car => car.make === selectedMake
)
};
setCarsList(updatedCarsList);

React array item selection

I am trying to click on one card of a dynamically created list using map(). I want to click on one card from the array and add a class to it, while at the same time deselecting the other card that was previously clicked. How can I accomplish this? This is what I have so far:
const CardList = () => {
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} {...otherData} />
))}
</div>
);
};
export default CardList;
const Card = ({
headline,
time,
views,
thumbImg,
trainerImg,
workouts,
id
}) => {
const [isSelected, setIsSelected] = useState(false);
const [clickId, setClickId] = useState('');
function handleClick(id) {
setIsSelected(!isSelected);
setClickId(id);
}
return (
<div
className={`card ${isSelected && clickId === id ? 'clicked' : ''}`}
onClick={() => handleClick(id)}
>
<div className='thumbnail-div'>
<img className='thumbnail-img' src={thumbImg} alt='video' />
{workouts ? (
<div className='workout-overlay'>
<p>{workouts}</p>
<p className='workouts'>workouts</p>
</div>
) : null}
</div>
<div className='card-info'>
<div className='card-headline'>
<p>{headline}</p>
<img src={trainerImg} alt='trainer' />
</div>
{time && views ? (
<div className='trainer-data'>
<span>
<i className='glyphicon glyphicon-time'></i>
{time}
</span>
<span>
<i className='glyphicon glyphicon-eye-open'></i>
{views}
</span>
</div>
) : null}
</div>
</div>
);
};
export default Card;
The parent component should control what card is clicked. Add className property to card component:
const Card = ({
//...
className,
onClick
}) => {
//...
return (
<div
className={`card ${className}`}
onClick={() => onClick(id)}
>...</div>
)
}
In parent component pass the className 'clicked' and add the onClick callback to set the selected card:
const CardList = () => {
const [isSelected, setIsSelected] = useState(null);
const handleClick = (id) => {
setIsSelected(id);
}
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} className={isSelected===id && 'clicked'} onClick ={handleClick} {...otherData} />
))}
</div>
);
};
You can do something like this.
First you don't have to set state to each card. Instead Lift state Up.
You define which card is selected in parent so you can pass that to children and add classes if current selected is matching that children.
const CardList = () => {
const [isSelected, setIsSelected] = useState();
const handleCardClick = (id) => {
setIsSelected(id);
}
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} {...otherData} handleClick={handleCardClick} isSelected={isSelected}/>
))}
</div>
);
};
export default CardList;
const Card = ({
headline,
time,
views,
thumbImg,
trainerImg,
workouts,
id,
isSelected,
handleClick
}) => {
return (
<div
className={`card ${isSelected === id ? 'clicked' : ''}`}
onClick={() => handleClick(id)}
>
<div className='thumbnail-div'>
<img className='thumbnail-img' src={thumbImg} alt='video' />
{workouts ? (
<div className='workout-overlay'>
<p>{workouts}</p>
<p className='workouts'>workouts</p>
</div>
) : null}
</div>
<div className='card-info'>
<div className='card-headline'>
<p>{headline}</p>
<img src={trainerImg} alt='trainer' />
</div>
{time && views ? (
<div className='trainer-data'>
<span>
<i className='glyphicon glyphicon-time'></i>
{time}
</span>
<span>
<i className='glyphicon glyphicon-eye-open'></i>
{views}
</span>
</div>
) : null}
</div>
</div>
);
};
export default Card;

React <details> - have only one open at a time

I have a component with several elements. I'm trying to figure out how to update the code with hooks so that only one element will be open at a time - when a element is open, the other's should be closed. This is the code:
const HowItWorks = ({ content, libraries }) => {
const Html2React = libraries.html2react.Component;
return (
<HowItWorksContainer>
{content.fields.map((tab, i) => {
const [open, setOpen] = useState(false);
const onToggle = () => {
setOpen(!open);
};
return (
<details
key={i}
onToggle={onToggle}
className={`tab ${open ? "open" : "closed"}`}
>
<summary className="tab__heading">
<div className="wrapper">
<p>{tab.heading}</p>
{open ? (
<i className="icon kap-arrow-minus" />
) : (
<i className="icon kap-arrow-plus" />
)}
</div>
</summary>
<div className="tab__content">
<Html2React html={tab.content} />
</div>
</details>
);
})}
</HowItWorksContainer>
);
};
Instead of having the open state be a boolean, make it be the ID of the element that is open. Then you can have a function that returns if the element is open by comparing the state with the ID.
const HowItWorks = ({ content, libraries }) => {
const [open, setOpen] = useState(0); //Use the element ID to check which one is open
const onToggle = (id) => {
setOpen(id);
};
const isOpen = (id) => {
return id === open ? "open" : "closed";
}
const Html2React = libraries.html2react.Component;
return (
<HowItWorksContainer>
{content.fields.map((tab, i) => {
return (
<details
key={i}
onToggle={onToggle}
className={`tab ${isOpen(i)}`}
>
<summary className="tab__heading">
<div className="wrapper">
<p>{tab.heading}</p>
{!!isOpen(i) ? (
<i className="icon kap-arrow-minus" />
) : (
<i className="icon kap-arrow-plus" />
)}
</div>
</summary>
<div className="tab__content">
<Html2React html={tab.content} />
</div>
</details>
);
})}
</HowItWorksContainer>
);
};

Resources