I keep getting this warning "each child in a list should have unique 'key' prop" even though I have unique items with different keys.
Whenever I create a new 'plant' object I give it a new uuid
setPlants(prevItems => {
return [
{name: newPlantName, key: uuid.v4(), timeInterval: null},
...prevItems,
];
And my listItem component is set up with a key
<ListItem
key={plant.key}
Whenever I print my list all the 'keys' have a different uuid. The warning occurs every time I refresh the app so it might be somehow because i'm using a database to access the data? I'm not really sure but I am using mmkv to store the data from my state and then I show that data when the app first opens.
This is the full mapping:
{plants &&
plants.map(plant =>
plant ? (
<PlantItem
plant={plant}
deletion={openDeleteOrCancel}
setPlants={setPlants}
/>
) : null,
)}
PlantItem component:
return (
<>
<ActionSheet
visible={actionSheetVisible}
closeOverlay={() => {
setActionSheetVisible(false);
}}
actions={actions}
/>
<ListItem
key={plant.key}
onPress={() => {
setActionSheetVisible(true);
}}
bottomDivider>
<ListItem.Content key={plant.key} style={styles.listItemContainer}>
<ListItem.Title>{plant.name}</ListItem.Title>
{/* <Icon name="check" size={20} /> */}
</ListItem.Content>
</ListItem>
{showAddTimeInterval && (
<AddTimeInterval
createTimeInterval={createTimeInterval}
closeModal={toggleShowAddTimeInterval}
plantName={plant.name}
/>
)}
</>
);
This is how my states are initiated
const [plantsStorage, setPlantsStorage] = useStorage('plantss');
const [plants, setPlants] = useState(plantsStorage ? plantsStorage : []);
useEffect(() => {
setPlantsStorage(plants);
});
The warning is just really annoying, if there is no way to change my code to fix it is there a way to mute it somehow? just for this specific warning not all warnings.
The React key should be used on the outermost mapped element.
React Lists and Keys
{plants.map(plant =>
plant ? (
<PlantItem
key={plant.key} // <-- use key here!
plant={plant}
deletion={openDeleteOrCancel}
setPlants={setPlants}
/>
) : null,
)}
Suggestion, filter the plants array to remove the null "holes".
{plants
.filter(Boolean) // include only truthy plant objects
.map(plant => (
<PlantItem
key={plant.key} // <-- use key here!
plant={plant}
deletion={openDeleteOrCancel}
setPlants={setPlants}
/>)
)}
Related
I am getting the following error:
react Each child in a list should have a unique "key" prop.
Parent component:
{data.products
.slice(4, 9)
.map(
({
onSale,
slug,
imageMain,
productHeadline,
outOfStock,
onSalePrice,
}) => (
<>
{onSale === true ? (
<HomeFeatured
slug={slug}
imageMain={imageMain}
productHeadline={productHeadline}
onSalePrice={onSalePrice}
outOfStock={outOfStock}
/>
) : (
<></>
)}
</>
)
)}
Child component:
function HomeFeatured({
slug,
imageMain,
productHeadline,
onSalePrice,
outOfStock,
}) {
return (
<div key={slug} className="xl:w-72 lg:w-48 xs:w-60 transition ease-in-out delay-110 hover:-translate-y-1 hover:scale-105 duration-200 ">
<div className="rounded overflow-hidden shadow-lg">
</div>
</div>
);
}
I have added a key to the parent div but I am still getting the error in the console. I even added a key to the parent component and still would not work. I have done very similar array mapping in other parts of my application but this part has been componentised. Is this the reason why it may not be rendering possibly??
I feel like I have added the key and React is just being picky but I still would like to solve this issue.
In react you can simplify the one-line ternary statement likewise:
{onSale && (
<HomeFeatured
slug={slug}
key={slug}
imageMain={imageMain}
productHeadline={productHeadline}
onSalePrice={onSalePrice}
outOfStock={outOfStock}
/>
)}
This eliminates the empty <></> problem.
The map function gives unique id along with the element every time it iterates through an Array, you can use it as a key.
.map((data, idx) => (
<HomeFeatured key={idx}/>
))
You Should add the key in the parent component. React needs the key on the element you returning in the map function. It can be a component or JSX.
{data.products
.slice(4, 9)
.map(
({
onSale,
slug,
imageMain,
productHeadline,
outOfStock,
onSalePrice,
}) => (
<>
{onSale === true ? (
<HomeFeatured
slug={slug}
key={slug}
imageMain={imageMain}
productHeadline={productHeadline}
onSalePrice={onSalePrice}
outOfStock={outOfStock}
/>
) : (
<></>
)}
</>
)
)}
You have to assign key value not inside the function. But where it is referenced. Like :
{data.products
.slice(4, 9)
.map(
({
onSale,
slug,
imageMain,
productHeadline,
outOfStock,
onSalePrice,
}) => (
<>
{onSale === true ? (
<HomeFeatured
key={slug}
slug={slug}
imageMain={imageMain}
productHeadline={productHeadline}
onSalePrice={onSalePrice}
outOfStock={outOfStock}
/>
) : (
<div key={slug}></div>
)}
</>
)
)}
The key property should be put on the outer most element you are mapping, it doesnt matter that in this case it's a component, it should get a key prop.
I'm currently rendering two different components based on the value of shouldRenderPlanA - however, despite different components being rendered (depending on the value) - I pass both the same props. How can I condense this to reduce repeated code?
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
shouldRenderPlanA ? (
<StyledLabelOptionOne
variousProps={variousProps}
variousProps={variousProps}
variousProps={variousProps}
/>
) : (
<StyledLabelOptionTwo
variousProps={variousProps}
variousProps={variousProps}
variousProps={variousProps}
/>
)
))}
</StyledRow>
</>
);
To pass same Props to multiple React component or to pass multiple Props to a React component, you can use Object unpacking/destruction within components.
function Component() {
const propPack = {
variousProps1: variousProps1,
variousProps2: variousProps2,
variousProps3: variousProps3,
};
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
shouldRenderPlanA
? <StyledLabelOptionOne {...propPack} />
: <StyledLabelOptionTwo {...propPack} />
))}
</StyledRow>
</>
);
}
This is commonly used to pass all the parent props down to children
function Component(props) {
return (
condition
? <StyledLabelOptionOne {...props} />
: <StyledLabelOptionTwo {...props} />
)
}
You can also conditionally pick the component for rendering (but this IMHO is less readable)
function Component() {
const PickedComponent = shouldRenderPlanA ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
<PickedComponent
variousProps1={variousProps1}
variousProps2={variousProps2}
variousProps3={variousProps3}
/>
))}
</StyledRow>
</>
);
}
For conditions/props derived from within .map() simply move the code within the map callback
function Component() {
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => {
const propPack = {
variousProps1: variousProps1,
variousProps2: opt.value,
};
const PickedComponent = opt.condition ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
shouldRenderPlanA
? <StyledLabelOptionOne {...propPack} />
: <StyledLabelOptionTwo {...propPack} />
)
})}
</StyledRow>
</>
);
}
Note how arrow function within map has becomed an arrow function with a complete block. From (opt) => (first_instruction) to (opt) => { first_instruction; return (second_instruction); }. This allows us to add code before rendering at each map() cycle.
You could assign both options to a variable which contains a union of both component types.
Combining and then spreading the props from an object may also be beneficial, depending on where those props come from. If they are taken from opt inside the map then this second step is probably not required:
const LabelComponent = shouldRenderPlanA ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
<LabelComponent
prop1={opt.prop1}
prop2={opt.prop2}
/>
))}
</StyledRow>
</>
);
You could use the React Context API. This would enable you to share the props across multiple children without passing it to each one of them explicitly.
I am new to react native and have a situation where I am trying to set a state to a filtered version of another state(array). With the current code, each item in the unfiltered array is being changed to the filtering condition. How can I setFilteredJobs to only contain 'jobs', where status equals the status that the user has chosen in the AppPicker?
Here is my code:
const [jobs, setJobs] = useState()
const [filteredJobs, setFilteredJobs] = useState()
const [status, setStatus] = useState()
const handleStatusChange = (item) => {
setFilteredJobs(
jobs.filter( job => job.status = item.label )
)
setStatus(item)
}
return (
<View style={defaultStyles.screenNoPadding}>
<AppTextInput placeholder='Search for a Job' icon='magnify' />
<View style={styles.filterContainer}>
<AppPicker
color='white'
selectedItem={category}
onSelectItem={item => handleCategoryChange(item)}
items={categories}
placeholder='Filter'
icon='apps' />
<AppPicker
color='white'
selectedItem={status}
onSelectItem={item => handleStatusChange(item)}
items={statuses}
placeholder='Status'
icon='apps' />
</View>
<FlatList
style={styles.list}
data={filteredJobs ? filteredJobs : jobs}
keyExtractor={job => job.id.toString()}
renderItem={({ item }) => (
<ListItem
company={item.company}
position={item.position}
status={item.status}
onPress={() => navigation.navigate('Details', { item })}
/>
)}
ItemSeparatorComponent={ListItemSeparator}
/>
</View>
);
Thanks in advance! Keep in mind jobs is fetched in a useEffect on loading the component.
It is because you should use a comparison, and not an attribuition.
const handleStatusChange = (item) => {
setFilteredJobs(
// FIX HERE, USE == INSTEAD OF =
jobs.filter( job => job.status == item.label )
)
setStatus(item)
}
#guilherme is right, it's a simple mistake, you assigned instead of comparing. Get in the habit of using === to compare strings.
Also the way to tackle these problems in the future: console.log before and after the thing you are doing that isn't working. It would have jumped out at you pretty quick if you had. Use JSON.stringify for logging of objects if you are getting [Object object] in the logging output.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
although I defined a key for SearchDropDownItem it shows a warning
component DropDown
filteredItems.length > 0 ? (
filteredItems.map(item => {
return (
<SearchDropDownItem
item={item}
buttonTitle={{ buttonJoin: content.buttonJoin }}
onItemSelect={onItemSelect}
/>
);
})
) : (
<SearchDropDownItem emptyList={content.noCommunityFound} />
)
searchDropDownItem component :
const SearchDropDownItem = ({
item = { },
onItemSelect,
buttonTitle = "",
emptyList
}) => {
return (
<DropdownItem key={item.id || 1}>
{!emptyList ? (
<Box>
<Span>{item.name} </Span>
<JoinButton
item={item}
index={item.id}
onSuccess={onItemSelect}
content={buttonTitle}
/>
</Box>
) : (
<Box>
<Span>{item.emptyList}</Span>
</Box>
)}
</DropdownItem>
);
};
Warning: Each child in a list should have a unique "key" prop. Check the render method of SearchBox.
in SearchDropDownItem (at SearchBox/index.jsx:52)
You should place the key where you use the SearchDropdownItem, so in the loop.
filteredItems.length > 0 ? (
filteredItems.map(item => {
return (
<SearchDropDownItem
key={item.id} // <-- This is where it has to be
item={item}
buttonTitle={{ buttonJoin: content.buttonJoin }}
onItemSelect={onItemSelect}
/>
);
})
) : (
<SearchDropDownItem emptyList={content.noCommunityFound} />
)
Docs on keys in React: https://reactjs.org/docs/lists-and-keys.html#keys
I got the same warning message:
Warning: Each child in a list should have a unique "key" prop.
However, my problem and solution were a bit different than the accepted answer. I thought adding my solution to this question might help someone.
I am using a 3rd party component, which has a unique key. However, when I used a loop to dynamically generate several instances of the component, I got the warning message above.
The warning disappeared after I added a key prop to the component. This key is NOT part of the props for the component.
let filterJSX = [];
let i = 0;
for (let [key1, value1] of Object.entries(state.filterListNew)) {
i++;
filterJSX.push(
<MultiSelectModalField
key={i.toString()} // I added this one
items={optionList}
uniqueKey="value"
displayKey="name"
// more properties here...
/>
);
}
setRadio= (id) => {
const {formRating} = this.state;
fetch(`http://localhost:3030/getLessonCondsDB?formId=${id}`)
.then(response => response.json())
.then(response=>{
this.setState({formRating:response.data})
console.log(response.data);})
.catch(err=>console.error(err))
}
The above method assigns the JSON object which is displayed in console as [RowDataPacket {condId: 'C2.1(a)', rate: 3, condition: 'Random text here' }, RowDataPacket {condId: 'C2.2(b)',rate: 3,condition: 'more random text' }]to the state object formRating which is displayed in dev tools as below
formRating: Array
> 0: Object
condId: 'C2.1(a)'
rate: '3',
condition: 'Random text here'
> 1: Object
condId: 'C2.2(b)'
rate: '3',
condition: 'more random text'
Any attempt to console.log(formRating) just prints and empty line on the console.
Instead of fetching from the server I had previously hardcoded this data into an array as below
const formValues= [{condId :'C2.1(a)',rate:'3', condition:'Random text here'},{condId :'C2.2(b)',rate:'3', condition:'more random text'}]
and had a method in another component to create radioGroups mapping each set of conditions allowing users to change the rate value as discussed in How to set defaultValue of a radioGroup from a nested Array object in React state? which works with the hardcoded array but not the JSON array which produces a "TypeError: values.formRating.map is not a function" with the below function in the component where radioGroups are displayed allowing the user to customise the "rate" value.
createRadioGroups = ()=>{
const {values} = this.props;
console.log(values.formRating);
return(values.formRating.map(
item =>
<Grid container>
<Grid item xs={2} style={{marginTop:20, marginRight:0}}>{item.condId} </Grid>
<Grid item xs={6} style={{marginTop:20}}>{item.condition} </Grid>
<Grid item xs={4} style={{marginTop:10}}>
<RadioGroup defaultValue={item.rate} name={item.condId} onChange={this.changeButton(item.condId)} style={{display: 'flex', flexDirection: 'row'}}>
<FormControlLabel value="3" control={<Radio color="primary" />} label=' ' labelPlacement="top"/>
<FormControlLabel value="2" control={<Radio color="primary" />}label=' ' labelPlacement="top"/>
<FormControlLabel value="1" control={<Radio color="primary" />}label=' ' labelPlacement="top"/>
<FormControlLabel value="N/A" control={<Radio color="primary" />}label=' ' labelPlacement="top"/>
</RadioGroup>
</Grid>
</Grid>
))
};
Any help is appreciated.
That is because the fetch operation within setRadio() is asynchronous, thus any operations that are dependent on the state, or the values from setRadio() will fail. This is why calling createRadioGroups() before setRadio() is returned and completed will result in an undefined value.
I am not sure how exactly is your component structured, but you should handle any subsequent operations within the .then() block,
setRadio= (id) => {
const {formRating} = this.state;
fetch(`http://localhost:3030/getLessonCondsDB?formId=${id}`)
.then(response => response.json())
.then(response=>{
this.setState({formRating:response.data})
console.log(response.data);
// do the rest here
})
.catch(err=>console.error(err))
}
Or if the rendering is handled on the template, you should conditionally call the method only after formRating is populated.
render() {
const { formRating } = this.state;
return <>
{ formRating && formRating.length && this.createRadioGroups() }
</>
}
Or, if createRadioGroups() is on another child component,
render() {
const { values } = this.props;
return <>
{ values && values.formRating && values.formRating.length && this.createRadioGroups() }
</>
}
How are you passing the 'values' prop to the createRadioGroup? Seems like you need to pass it in (see snippet below) then try console logging the entire props object to make sure you are actually receiving it.
createRadioGroups = (props)=>{
const {values} = this.props;
After you check that, then consider when you are calling setRadio? Are you sure the state has already been updated so that it is available when you call createRadioGroup? If it possibly hasn't been updated then you can try initializing your state with the 'shape' of your expected data so that it will render with no data, then rerender once the state is updated. Would look something like this:
this.state = {
formValues=
[
{
condId :'',
rate:'',
condition:''
}
];
Try this
return(
<>
this.props.values&&this.props.values.formRating.map()
</>
)