Warning: flattenChildren(...): Encountered two children with the same key in reactjs - javascript

I'm using Tabs from Material UI where I'm displaying a List component filtered by the tab, please see the code of the Tabs in my Container Component:
<Tabs
className="DrawerTabs"
inkBarStyle={{ display: 'none' }}
>
<Tab label="Headline"
data-route="/headline"
onActive={this.handleActive}
className={this.isActive('Headline')}
>
<div>
<ModulesListContainer
filter="Headline"
/>
</div>
</Tab>
<Tab label="Body"
data-route="/body"
onActive={this.handleActive}
className={this.isActive('Body')}
>
<div>
<ModulesListContainer
filter="Body"
/>
</div>
</Tab>
<Tab
label="Other"
data-route="/other"
onActive={this.handleActive}
className={this.isActive('Other')}
>
<div>
<ModulesListContainer
filter="Other"
/>
</div>
</Tab>
</Tabs>
and the code of the ModuleList I placed in each of the tabs which is showing only items based on the filter passed from the Container Component:
const ModulesList = (props) => {
let ListItems = props.modulesProps.map(module => {
if (props.filter === module.category) {
return (
<ListItem
key={module.id}
className="ModulePreview"
>
{module.id} - {module.name} - {module.thumbnail}
<FontAwesome
name="plus-circle"
size="2x"
onClick={props.addModule.bind(this, module)}
className="AddModule"
/>
</ListItem>
)
}
})
return (
<div className="ModulesList">
<List>
{ListItems}
</List>
</div>
)
}
Even though I can see only the filtered items in each of the tabs (thus key is unique as each item is there only once) I'm still getting this warning:
Warning: flattenChildren(...): Encountered two children with the same
key, 1. Child keys must be unique; when two children share a key,
only the first child will be used.
Why is that?
Any help / ideas / tips would be highly appreciated.
Thanks a lot in advance! :)

Meaning of that line is module.id is not unique, there will be 2 objects in array that have the same id=1, to avoid that you can use index of object, it will always be unique.
Use this:
let ListItemsUI = [];
props.modulesProps.forEach((module, i) => {
if (props.filter === module.category) {
ListItemsUI.push (
<ListItem
key={i}
className="ModulePreview"
>
{module.id} - {module.name} - {module.thumbnail}
<FontAwesome
name="plus-circle"
size="2x"
onClick={props.addModule.bind(this, module)}
className="AddModule"
/>
</ListItem>
)
}
})
return (
<div className="ModulesList">
<List>
{ListItemsUI}
</List>
</div>
)
One more thing map is not suitable for these cases where you want to return only few element on the basis of condition, use forEach for that. Reason is if you don't return anything, by default map will return undefined.
Check the output of this example:
let a = [1,2,3,4,5,6,7,8];
let b = a.map(el=>{
if(el % 2 == 0)
return el;
})
console.log(b);

Related

NextJS Dynamic List Not Updating on Delete/Update

Currently, my react/nextjs dynamic list is not updating correctly, I've given an array to map so it'll show a new row on frame update since it's stored on useState, Usually, the issue is with the key of the list that is not unique, but my key is unique
Heres my code
const [allSelectedMaterials, setAllSelectedMaterials] = useState([]) // This variable stores a javascript object into the array
{console.log('-- Updated --')}
{allSelectedMaterials.map((material, i) => {
const key = Object.keys(material).toString()
console.log(`${i} - [${key}] ${material}`)
return (
<div key={key}>
<Row className='align-items-center'>
<Col xs='auto'>
<h6>{material[key].name}</h6>
</Col>
<Col>
<Button variant='danger' className='mb-1' onClick={() => handleDeleteMaterial(key)}>
Remove
</Button>
</Col>
</Row>
<InputGroup>
<InputGroup.Text>
<Image src={material[key].image} className={`${styles.allMaterialsImage}`} />
</InputGroup.Text>
<Form.Control type='number' min={1} ref={(e) => (selectedMaterials.current[i] = e)} required />
</InputGroup>
<div className='mt-1' />
</div>
)
})}
The problem is after I added the first item on the list and adding a new one it won't update the view, here's the console output
And here's me adding a second entry to the list
It clearly says on the log that the array (stored in useState) is updated but it's not changing the view it's still the same as the previous one. But if I reupdate the frame by updating any useState variable it updated the list
Update:
Here's my code for adding new material
(loadedMaterials is just a list of materials that is retrieved from an API)
const handleAddSelectedMaterial = () => {
loadedMaterials.map((material) => {
const key = Object.keys(material)
if (key == currentSelectedMaterial) {
let _material
if (allSelectedMaterials.length > 0) _material = allSelectedMaterials
else _material = []
_material.push({ [material[key].id]: material[key] })
setAllSelectedMaterials(_material)
}
})
}
Try replacing
from
_material.push({ [material[key].id]: material[key] })
setAllSelectedMaterials(_material)
to
_material.push({ [material[key].id]: material[key] })
setAllSelectedMaterials([..._material])
Thank you everyone for the input and responses, but I've managed to solve this issue by redoing how the form & dynamic list work and it's working perfectly now.
If anyone is wondering I just basically follow this person implementation on a dynamic list
https://www.cluemediator.com/add-or-remove-input-fields-dynamically-with-reactjs

in react when I remove a dynamic input, the input does not show the right value on the browser

I'm new in React and i'm learning to use it with hooks.
I tried to put dynamics inputs which works so far but I have a display problem.
If I delete the last input, no problem but if I delete others inputs than the last one then the correct values does not show on the browser.
For example I add 2 inputs.
first one titled "one" and second titled "two".
If I delete "one", the remain input on screen shows "one" or it should show "two".
However, in the array where I collect the inputs infos, there is the correct remaining input.
(see screenshot).
How can I do to show the correct title in the input on the browser ?
const [titleSelected, setTitleSelected] = useState(true);
const [questionType, setQuestionType] = useState([]);
const [questionsTitle, setQuestionsTitle] = useState([]);
{questionType ? (
questionType.map((questions, index) => {
return (
<div key={index} className="questions">
<div className={questions === "texte" ? "orange" : "red"}>
<span>{questions === "texte" ? "1" : "2"}</span>
<img src={Minus} alt="" />
<img
src={questions === "texte" ? FileWhite : StarWhite}
alt=""
/>
</div>
<input
type="text"
placeholder="Ecrivez votre question"
onChange={(event) => {
let tab = [...questionsTitle];
// si index de l'objet existe on update index de l'objet par index sinon on push le nouvel objet
let tabIndex = tab.findIndex(
(element) => element.index === index
);
if (tabIndex !== -1) {
tab[tabIndex].type = questionType[index];
tab[tabIndex].title = event.target.value;
} else {
tab.push({
index: index,
type: questionType[index],
title: event.target.value,
});
}
setQuestionsTitle(tab);
}}
></input>
<div>
<img src={ChevronUp} alt="move up" />
<img src={ChevronDown} alt="move down" />
<img
src={SmallTrash}
alt="delete question"
onClick={() => {
let tab = [...questionType];
tab.splice(index, 1);
setQuestionType(tab);
let tabTitle = [...questionsTitle];
tabTitle.splice(index, 1);
setQuestionsTitle(tabTitle);
}}
/>
</div>
</div>
);
})
) : (
<div></div>
)}
<div className="questionType">
<div
className="addText"
onClick={() => {
let tab = [...questionType];
tab.push("texte");
setQuestionType(tab);
}}
>
<img src={File} alt="" />
<p>Ajouter une question "Texte"</p>
</div>
<div
className="addNote"
onClick={() => {
let tab = [...questionType];
tab.push("note");
setQuestionType(tab);
}}
>
<img src={Star} alt="" />
<p>Ajouter une question "Note"</p>
</div>
</div>
screenshot
Issues
You are mutating the array you are mapping so don't use the array index as the react key. If you remove the ith element all the elements shift up, but the keys remain unchanged and react bails on rerendering.
Lists & Keys
questionType.map((questions, index) => (
<div key={index} className="questions">
...
</div>
)
The array index as a React key doesn't "stick" to the element object it "identifies".
You've also some state object mutation occurring.
tab[tabIndex].type = questionType[index];
tab[tabIndex].title = event.target.value;
This is masked by the shallow copy let tab = [...questionsTitle];.
Solution
Select a react key that is unique among siblings and related to the elements, like an id.
Since you enclose the index when adding new elements to the array I think you can resolve the key issue by simply using the saved index.
questionType.map((questions, index) => (
<div key={questionsTitle[index].index} className="questions">
...
</div>
)
This may be a little confusing so I suggest changing the property to id.
questionType.map((questions, index) => (
<div key={questionsTitle[index].id} className="questions">
...
<input
...
onChange={event => {
...
tab.push({
id: index,
type: questionType[index],
title: event.target.value,
});
...
}}
/>
...
</div>
)
A further suggestion is to avoid using the array index at all. The following code can quickly get out of sync when the array index being mapped doesn't align to the saved index in the element.
let tabIndex = tab.findIndex(element => element.index === index);
Generate id's for your elements and use that to determine if elements should be updated or appended. When updating make sure to shallow copy the array and then also shallow copy the element object being updated.

How to map i and i+1 values of a javascript array?

I would like to use a map to display the ith value and the i+1th value inside a javascript map to display a set of two cards inside a carousel.
The following is the code I am using, which I would like to modify to get the desired result:
{testimonialContent.map((testimonial, i) => {
return (
<Carousel.Item className={classes.carousel_item}>
<div className={classes.root}>
<div className={classes.testimonial_card}>
{testimonial[i]}
</div>
{/* Second card */}
<div className={classes.second_card}>
{testimonial[i+1]}
</div>
</div>
</Carousel.Item>
Any help or suggestion is appreciated. Thanks.
You can use reduce() to combine data.
NOTE: (index % 2 === 0) is where you specify your nth child (eg: 2 in this case)
Later in your render method use destructuring in map().
const sampleData = ['first', 'second', 'third', 'fourth', 'fifth']
class App extends React.Component {
render() {
console.log("rendered");
const edited = [].concat(sampleData).reduce((unique, value, index) => {
if (index % 2 === 0) {
return [...unique, [value]];
}
unique[unique.length - 1].push(value);
return unique;
}, [])
return (
<div>
{edited.map(([item1, item2]) =>
<div className='block'>
{item1 && <div className='odd'>{item1}</div>}
{item2 && <div className='even'>{item2}</div>}
</div>
)}
</div>
);
}
}
// Render it
ReactDOM.render(
<App />,
document.getElementById("react")
);
.block {
border: 1px solid gray;
}
.odd {
color: red;
}
.even {
color: blue;
}
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
use the entire array not the current iteration
Testimonial in each iteration of the map is a single object. Whereas testimonialContent is the entire array.
{testimonialContent.map((testimonial, i) => {
return (
<Carousel.Item className={classes.carousel_item}>
<div className={classes.root}>
<div className={classes.testimonial_card}>
{testimonial[i]}
</div>
{/* Second card */}
<div className={classes.second_card}>
{testimonialContent[i+1]} // this is the line to change
</div>
</div>
</Carousel.Item>
But something feels very off here. I think you might have an x y problem
Why would you want to use the next iteration as the current iteration? The last item will fail because it doesn't exist and the value of the first testimonialContent will never be shown because you're always starting from index 1.
I would rethink what you're doing here and maybe take another approach.
testimonial is not an array, but the current iterated item in that array. For the "next" item you can use the array testimonialContent.
From the comment I learn that you want to actually produce n/2 rows, so that each item only occurs once. In that case using a plain .map() is not good as it will generate as many rows, and you only need half that number of rows. Instead, you could generate an array with half the number of rows (rounded upwards) with Array.from and use the callback argument of that method.
Array.from({length: (testimonialContent.length+1)>>1}, (_, i) => {
return (
<Carousel.Item className={classes.carousel_item}>
<div className={classes.root}>
<div className={classes.testimonial_card}>
{testimonialContent[i*2]}
</div>
{/* Second card */}
<div className={classes.second_card}>
{testimonialContent[i*2+1] || ""}
</div>
</div>
</Carousel.Item>
)
})
Refer to the testimonial instance in your first div and to the element on the i-th +1 palce in your testimonialContent array in your seccond div.
{testimonialContent.map((testimonial, i) => {
return (
<Carousel.Item className={classes.carousel_item}>
<div className={classes.root}>
<div className={classes.testimonial_card}>
{testimonial}
</div>
{/* Second card */}
<div className={classes.second_card}>
{testimonialContent[i+1]}
</div>
</div>
</Carousel.Item>

How to add a <br> tag in reactjs between two strings?

I am using react. I want to add a line break <br> between strings
'No results' and 'Please try another search term.'.
I have tried 'No results.<br>Please try another search term.'
but it does not work, I need to add the <br> in the html.
Any ideas how to solve it?
render() {
let data = this.props.data;
let isLoading = this.props.isLoading;
let isDataEmpty = Object.entries(data).length === 0;
let movieList = isLoading ? <Loader /> : isDataEmpty ? 'No results. Please try another search term.' :
Object.entries(data).map((movie, index) => <MovieTile key={index} {...movie[1]} />);
return (
<div className='movieList'>{movieList}</div>
);
}
You should use JSX instead of string:
<div>No results.<br />Please try another search term.</div>
Because each jsx should have 1 wrapper I added a <div> wrapper for the string.
Here it is in your code:
render() {
let data = this.props.data;
let isLoading = this.props.isLoading;
let isDataEmpty = Object.entries(data).length === 0;
let movieList = isLoading ? <Loader /> : isDataEmpty ? <div>No results.<br />Please try another search term.</div> :
Object.entries(data).map((movie, index) => <MovieTile key={index} {...movie[1]} />);
return (
<div className='movieList'>{movieList}</div>
);
}
You can use CSS white-space to solve the problem.
React Component
render() {
message = `No results. \n Please try another search term.`;
return (
<div className='new-line'>{message}</div>
);
}
CSS
.new-line {
white-space: pre-line;
}
OUTPUT
No results.
Please try another search term.
break text to line:
render() {
...
<div>
{this.props.data.split('\n').map( (it, i) => <div key={'x'+i}>{it}</div> )}
</div>
...
Some HTML elements such as <img> and <input> use only one tag. Such tags that belong to a single-tag element aren't an opening tag nor a closing tag. Those are self-closing tags.
In JSX, one has to include the slash. So, remove <br> and try <br />
Here is how I got around this. Let message be the prop/variable that has the string containing line breaks to be displayed in HTML as follows:
message = 'No results.<br>Please try another search term.';
<div>
{message}
</div>
To make this work, we need to use \n instead of break tag <br> and set the following css on the wrapper element of this message as follows:
message = 'No results.\nPlease try another search term.';
<div className="msg-wrapper">
{message}
</div>
CSS:
.msg-wrapper {
white-space: pre-wrap;
}
OUTPUT:
No results.
Please try another search term.
If you don't want put the string inside a <div> you could use <> to do it.
Like this:
var text = <>This is a text in the first line;<br />this is a text in a second line</>;
Just split text by /n, I do this in this way:
<div>
{text.split('\n').map((item, i) => <p key={i}>{item}</p>)}
</div>
Try with span
return (
<div className='movieList'><span>{movieList}</span></div>
);
If you are like in my situation and you don't want to add css, you can do that :
render () {
...
return (
...
<Typography component="p">
...
{(contact.lastname)?<div>Hello {contact.firstname} {contact.lastname}</div>:''}
...
</Typography>
...
);
}
using ` worked for me however i am not sure if it is the exact solution to the problem :
import React from 'react';
import ReactDOM from 'react-dom';
let element = (
<div>
<h1> Hello world</h1>
This is just a sentence <br></br>
But This line should not be in the same previous line. <br></br>
The above content proves its working. <br></br>
npm v6.14.6 | react : {React.version}
</div>
);
ReactDOM.render(element,document.getElementById("html-element-id"))
You can add a span tag and add block as a class.
Pomodoro Technique Timer <span className="block">with Bla</span>
The simplest thing which I did is by creating a component.
const EmptySpace = ({ spaceCount = 0 }) => {
return (
<>
{Array.from({ length: spaceCount }, (item, index) => {
return <br key={index} />;
})}
</>
);
};
export default EmptySpace;
<EmptySpace spaceCount={1} />
In your case you could do something like this:
const msg = (
<p>
No results <EmptySpace spaceCount={2} />
Please try another search term.
</p>
);

How to get Name on chip or Index of chip?

1) I am using following method to create multiple chips (Material Design). In React
<div style={styles.wrapper} className="container">
{
arrayName.map((row, index)=>(
<Chip
style={styles.chip}
key={index}
onTouchTap={handleTouchTap}
>
{row.name}
</Chip>
))
}
</div>
I am not able to get value from event handler by using "event.target.value"
is there another way to get value on chips?
arrayName contains name of programming languages like Angular, React etc.
2) can change color of chips based on index I set?
You can bind the index or value directly to onTouchTap function, and access the value directly.
Binding Name:
<div style={styles.wrapper} className="container">
{
arrayName.map((row, index)=>(
<Chip
style={styles.chip}
key={index}
onTouchTap={this.handleTouchTap.bind(this, row.name)}
>
{row.name}
</Chip>
))
}
</div>
handleTouchTap(name){
console.log('name:', name);
}
Binding Index:
<div style={styles.wrapper} className="container">
{
arrayName.map((row, index)=>(
<Chip
style={styles.chip}
key={index}
onTouchTap={this.handleTouchTap.bind(this, index)}
>
{row.name}
</Chip>
))
}
</div>
handleTouchTap(index){
console.log('name:', arrayName[index].name);
}
Update:
To assign different colors to chip, First define the color codes in an To array like this:
let colors = ['#color1', '#color2', '#color3', '#color4'....];
Then Use Math.floor(Math.random() * colors.length); to pick a random no in the range of 0 to colors.length, assign that color to chip like this:
style={{backgroundColor: colors[Math.floor(Math.random() * colors.length)]}}
It will work.

Categories

Resources