How to handle recursive array mapping with React components? - javascript

Here is a sample function called index() that I use to return an index of headers from a Markdown file. As you can see, with only 3 levels of headers, its quickly getting out of hand.
I tried a variety recursion ideas, but ultimately failed at all attempts to compile in create-react-app.
Could anyone offer any guidance as to how to clean this up and maybe even allow for an infinite/greater levels of nesting?
An array is stored in this.state.index which resembles the array below.
[
{
label: 'Header title',
children: [
{
label: 'Sub-header title'
children: [...]
}
]
}
]
Then that data is used in the function below to generate a ul index.
index() {
if ( this.state.index === undefined || !this.state.index.length )
return;
return (
<ul className="docs--index">
{
this.state.index.map((elm,i=0) => {
let key = i++;
let anchor = '#'+elm.label.split(' ').join('_') + '--' + key;
return (
<li key={key}>
<label><AnchorScroll href={anchor}>{elm.label}</AnchorScroll></label>
<ul>
{
elm.children.map((child,k=0) => {
let key = i+'-'+k++;
let anchor = '#'+child.label.split(' ').join('_') + '--' + key;
return (
<li key={key}>
<label><AnchorScroll href={anchor}>{child.label}</AnchorScroll></label>
<ul>
{
child.children.map((grandchild,j=0) => {
let key = i+'-'+k+'-'+j++;
let anchor = '#'+grandchild.label.split(' ').join('_') + '--' + key;
return (
<li key={key}>
<label><AnchorScroll href={anchor}>{grandchild.label}</AnchorScroll></label>
</li>
);
})
}
</ul>
</li>
);
})
}
</ul>
</li>
);
})
}
</ul>
);
}
Like I said, this is a mess! I'm new to React and coding in general so sorry if this is a silly question.

Seems like you want a recursive call of a component.
For example, component Item will render it self based on a condition (existence of the children array).
Running example:
const data = [
{
label: 'Header title',
children: [
{
label: 'Sub-header title',
children: [
{ label: '3rd level #1' },
{
label: '3rd level #2',
children: [
{ label: 'Level 4' }
]
}
]
}
]
}
]
class Item extends React.Component {
render() {
const { label, children } = this.props;
return (
<div>
<div>{label}</div>
<div style={{margin: '5px 25px'}}>
{children && children.map((item, index) => <Item key={index} {...item} />)}
</div>
</div>
)
}
}
const App = () => (
<div>
{data.map((item, index) => <Item key={index} {...item} />)}
</div>
);
ReactDOM.render(<App />, document.getElementById('root'));
<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>
<div id="root"></div>
Edit
Just for completeness, i added another example but this time we handle the isOpen property outside the Item's local component state, in a higher level.
This can be easily moved to a redux reducer or any other state management library, or just let a higher level component like the App in this case manage the changes.
So, to handle changes of a recursive component you would probably write a recursive handler:
const data = [
{
label: 'Header title',
id: 1,
children: [
{
label: 'Sub-header title',
id: 2,
children: [
{ label: '3rd level #1', id: 3 },
{
label: '3rd level #2',
id: 4,
children: [
{ label: 'Level 4', id: 5 }
]
}
]
},
]
},
{
label: 'Header #2',
id: 6,
children: [
{ label: '2nd level #1', id: 7, },
{
label: '2nd level #2',
id: 8,
children: [
{ label: 'Level 3', id: 9 }
]
}
]
}
]
class Item extends React.Component {
toggleOpen = e => {
e.preventDefault();
e.stopPropagation();
const {onClick, id} = this.props;
onClick(id);
};
render() {
const { label, children, isOpen, onClick } = this.props;
return (
<div className="item">
<div
className={`${children && "clickable"}`}
onClick={children && this.toggleOpen}
>
<div
className={`
title-icon
${isOpen && " open"}
${children && "has-children"}
`}
/>
<div className="title">{label}</div>
</div>
<div className="children">
{children &&
isOpen &&
children.map((item, index) => <Item key={index} {...item} onClick={onClick} />)}
</div>
</div>
);
}
}
class App extends React.Component {
state = {
items: data
};
toggleItem = (items, id) => {
const nextState = items.map(item => {
if (item.id !== id) {
if (item.children) {
return {
...item,
children: this.toggleItem(item.children, id)
};
}
return item;
}
return {
...item,
isOpen: !item.isOpen
};
});
return nextState;
};
onItemClick = id => {
this.setState(prev => {
const nextState = this.toggleItem(prev.items, id);
return {
items: nextState
};
});
};
render() {
const { items } = this.state;
return (
<div>
{items.map((item, index) => (
<Item key={index} {...item} onClick={this.onItemClick} />
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.item {
padding: 0 5px;
}
.title-icon {
display: inline-block;
margin: 0 10px;
}
.title-icon::before {
margin: 12px 0;
content: "\2219";
}
.title-icon.has-children::before {
content: "\25B6";
}
.title-icon.open::before {
content: "\25E2";
}
.title-icon:not(.has-children)::before {
content: "\2219";
}
.title {
display: inline-block;
margin: 5px 0;
}
.clickable {
cursor: pointer;
user-select: none;
}
.open {
color: green;
}
.children {
margin: 0 15px;
}
<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>
<div id="root"></div>

I don't have good test data, but try this out:
index() {
if ( this.state.index === undefined || !this.state.index.length ) {
return null;
}
const sections = this.state.index.map(this.handleMap);
return (
<ul>
{sections}
</ul>
)
}
handleMap(elm, i) {
const anchor = '#'+elm.label.split(' ').join('_') + '--' + i;
return (
<li key={i}>
<label><AnchorScroll href={anchor}>{elm.label}</AnchorScroll></label>
<ul>
{
elm.children.map(this.handleMapChildren)
}
</ul>
</li>
);
}
handleMapChildren(child, i) {
let anchor = '#'+child.label.split(' ').join('_') + '--' + i;
return (
<li key={i}>
<label><AnchorScroll href={anchor}>{child.label}</AnchorScroll></label>
<ul>
{
child.children.map(this.handleMapChildren)
}
</ul>
</li>
);
}

Related

onClick() color change for item in mapped list not working

I have a list of items that are displayed on the UI in a React class component. Whatever item the user clicks on should turn green, while the rest are gray. I also want each item to have a hover color of green.
What I currently have is not working. If I click on one list item, ALL the list items turn green. Then, if I click another list item, all revert back to grey.
JS:
class AFile extends Component {
constructor(props) {
super(props);
this.state = {
selected: false
}
}
handleClick= (index) => {
// do stuff with index
this.setState(selected: !this.state.selected);
}
const list = [
{order: 1, name: 'a'},
{order: 2, name: 'b'},
{order: 3, name: 'c'}
]
render() {
return (
list.map( (item, index) => {
return (
<div key={index}>
<Link to='#' onClick={()=>this.handleClick(index)} className={ this.state.selected ? 'list-item-selected' : 'list-item } >
{item.order}. {item.name}
</Link>
</div>
)
}
)
}
}
CSS:
.list-item {
color: gray;
}
.list-item:hover {
color: green;
}
.list-item-selected {
color: green;
}
You should store the selected item in the state. Try this:
class AFile extends Component {
constructor(props) {
super(props);
this.state = {
selectedItem: null,
};
}
handleClick = (selectedItem) => {
this.setState({ selectedItem });
};
render() {
const list = [
{ order: 1, name: 'a' },
{ order: 2, name: 'b' },
{ order: 3, name: 'c' },
];
return list.map((item, index) => {
return (
<div key={index}>
<Link
to="#"
onClick={() => this.handleClick(item)}
className={
this.state.selectedItem?.name === item.name
? 'list-item-selected'
: 'list-item'
}
>
{item.order}. {item.name}
</Link>
</div>
);
});
}
}
Or you can store selected name

Add items to page with localstorage

the array is stored in local storage by clicking on the “More” button 2 news is added to DOM
How should I implement this?
const ARR= [
{
id: 1,
Name: 'Name1',
text:'lorem ipsum'
},
{
id: 2,
Name: 'Name2',
text:'lorem ipsum'
},
{
id: 3,
Name: 'Name3',
text:'lorem ipsum'
},
{
id: 4,
Name: 'Name4',
text:'lorem ipsum'
},
];
10 obj
here we save the array in localStorage
and where to execute its JSON.parse (localStorage.getItem ('news')) and I don’t understand how to implement work with local storage by click
```
localStorage.setItem('news', JSON.stringify(ARR));
class NewsOne extends PureComponent {
constructor() {
super();
this.state = {
listItem: ARR,
formAdd: false,
};
this.createItem = this.createItem.bind(this);
this.updateItem = this.updateItem.bind(this);
this.removeItem = this.removeItem.bind(this);
this.addForm = this.addForm.bind(this);
}
updateItem(item) {
const { listItem } = this.state;
this.setState({
listItem: listItem.map(elem => (
elem.id === item.id ? item : elem
))
});
}
removeItem(itemId) {
const { listItem } = this.state;
this.setState({
listItem: listItem.filter(item => item.id !== itemId)
});
}
createItem(item) {
const { listItem } = this.state;
this.setState({
listItem: [item, ...listItem],
});
}
addForm() {
const { formAdd } = this.state;
this.setState({
formAdd: !formAdd,
})
}
render() {
const { listItem, formAdd } = this.state;
return(
<>
<div className="box">
<Title />
<List
data={listItem}
removeFromProps={this.removeItem}
updateFromProps={this.updateItem}
/>
</div>
<button className ="addnews" onClick = {this.addForm}>
Add
</button>
</>
);
}
}
A class that defaultly displays 2 elements on page.
I tried here to interact with the array from localStorage, but it fails
class List extends PureComponent {
constructor() {
super();
this.state = {
count: 2,
}
this.addObj = this.addObj.bind(this);
}
addObj() {
const { count } = this.state;
this.setState({
count: count + 2,
});
}
render() {
const { count } = this.state;
const { data } = this.props;
return(
<>
<ul>
{
data.slice(0, count).map(item => (
<>
<Item
key={item.id}
item={item}
/>
</>
))
}
</ul>
<button className ="addnews" onClick = {this.addObj}>
More
</button>
</>
);
}
}

Updating nested objects in nested arrays in React

Expected effect:
click button -> call function save -> pass object p to function update
update second object{a: 'purple', desc: 'grt', date: '12 -10-2019 '} in colors array, which is in theproducts array
Before update: {a: 'purple', desc: 'grt', date: '12 -10-2019 '}
After update: {a: 'violet', desc: 'gt', date: '12 -12-1980 '}
Error in console.log:
Uncaught TypeError: this.props.product.colors.map is not a function
App
class App extends Component {
constructor (props) {
super(props);
this.state = {
products: [
{
colors: [{a:'orange', desc: 'grtrt', date: '02-12-2019'}, {a:'purple', desc: 'grt', date: '12-10-2019'}]
desc: 'gfgfg',
},
{
colors: [{a:'black', desc: 'g', date: '12-12-2019'}, {a: 'white', {a:'black', desc: 'grtrt', date: '12-12-2119'}, }, {a:'gray', desc:'', date: '01-01-2000'}],
desc: 'gfgfgfg',
}
],
selectProductIndex: 0 //It is first object in products array
index: 1 //It is second object in colors array
}
}
update = (item) => {
const {selectProductIndex} = this.state;
this.setState(prevState => {
return {
products: [
...prevState.products.slice(0, selectProductIndex),
Object.assign({}, prevState.products[selectProductIndex], {colors: item}),
...prevState.products.slice(selectProductIndex + 1)
]
};
});
}
render () {
return (
<div>
<Items
product={this.state.products[this.state.selectProductIndex]}
update = {this.update}
/>
</div>
)
}
Items
class Items extends Component {
render () {
return (
<ul>
{
this.props.product.colors
.map((item, index) =>
<Item
key= {index}
index = {index}
time = {item}
update = {this.props.update}
/>
)
}
</ul>
</div>
);
}
}
Item
class Item extends Component {
save = () => {
const p = {
a:'violet', desc: 'gt', date: '12-12-1980'
}
this.props.update(p)
}
render() {
return (
<div>
<button onClick={this.save}>Save</button>
</div>
)
}
}
You need to pass the index of the colors item and then update it accordingly
class Item extends Component {
save = () => {
const p = {
a:'violet', desc: 'gt', date: '12-12-1980'
}
this.props.update(p, this.props.index)
}
render() {
return (
<div>
<button onClick={this.save}>Save</button>
</div>
)
}
}
and then in the topmost parent
update = (item, colorIndex) => {
const {selectProductIndex} = this.state;
this.setState(prevState => {
return {
products: [
...prevState.products.slice(0, selectProductIndex),
Object.assign({}, prevState.products[selectProductIndex], {colors: prevState.products[selectProductIndex].colors.map((it,idx) => {
if(idx === colorsIndex) { return item}
return it;
})}),
...prevState.products.slice(selectProductIndex + 1)
]
};
});
}
Working demo
const { Component } = React;
class App extends Component {
constructor (props) {
super(props);
this.state = {
products: [
{
colors: [{a:'orange', desc: 'grtrt', date: '02-12-2019'}, {a:'purple', desc: 'grt', date: '12-10-2019'}],
desc: 'gfgfg',
},
{
colors: [{a:'black', desc: 'g', date: '12-12-2019'}, {a:'black', desc: 'grtrt', date: '12-12-2119'}, {a:'gray', desc:'', date: '01-01-2000'}],
desc: 'gfgfgfg',
}
],
selectProductIndex: 0,
index: 1
}
}
update = (item, colorIndex) => {
const {selectProductIndex} = this.state;
this.setState(prevState => {
return {
products: [
...prevState.products.slice(0, selectProductIndex),
Object.assign({}, prevState.products[selectProductIndex], {colors: prevState.products[selectProductIndex].colors.map((it,idx) => {
if(idx === colorIndex) { return item}
return it;
})}),
...prevState.products.slice(selectProductIndex + 1)
]
};
});
}
render () {
return (
<div>
<Items
product={this.state.products[this.state.selectProductIndex]}
update = {this.update}
/>
</div>
)
}
}
class Items extends Component {
render () {
return (
<ul>
{
this.props.product.colors
.map((item, index) =>
<Item
key= {index}
index = {index}
time = {item}
update = {this.props.update}
/>
)
}
</ul>
);
}
}
class Item extends Component {
save = () => {
const p = {
a:'violet', desc: 'gt', date: '12-12-1980'
}
this.props.update(p, this.props.index)
}
render() {
return (
<div>
<pre>{JSON.stringify(this.props.time)}</pre>
<button onClick={this.save}>Save</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app" />

how to display group messages

Please help me, I'm new in react. I'm rendering values from nested object. Each object has title and message property. Titles can be same. I want display messages under title. If title same as in previos object do not display it , only once. But in my case it displays after each message.
my object:
arrayOfMessages=[
{
title: 'cars',
message: 'toyota'
},
{
title: 'cars',
message: 'ford'
},
{
title: 'cars',
message: 'bmw'
},
{
title: 'bikes',
message: 'suzuki'
},
{
title: 'bikes',
message: 'bmw'
},
]
expected output:
title
message
message
message
title2
message
message
in my case:
title
message
title
message
title
message
title2
message
title2
message
<div>
{arrayOfMessages.map((item, idx) => {
const {
message,
title
} = item
return (
<div key={idx} className="message-content">
<p>{title}</p>
<p>{message}</p>
</div>
)
})}
</div>
One approach would be to group items of arrayOfMessages by the title field, to achieve the required rendering result by using the native .reduce() method:
const arrayOfMessages=[
{
title: 'cars',
message: 'toyota'
},
{
title: 'cars',
message: 'ford'
},
{
title: 'cars',
message: 'bmw'
},
{
title: 'bikes',
message: 'suzuki'
},
{
title: 'bikes',
message: 'bmw'
},
];
/* Use reduce() to group items of arrayOfMessages by title */
const groupedMessages = arrayOfMessages.reduce((groups, item) => {
/* Find group for the title of current item */
const group = groups.find(group => group.title === item.title);
/* If matching group found, add message of item to it's messages array */
if(group) {
group.messages.push(item.message);
}
/* Otherwise, add a new group for this title */
else {
groups.push({ title : item.title, messages : [] });
}
return groups;
}, [])
console.log(groupedMessages);
Using the code above, you could then revise your render() method to render the title once for each item category:
<div>
{ groupedMessages.map((group, idx0) => (<div key={idx0} className="message-content">
<h2>{ group.title }</h2>
{ group.messages.map((message, idx1) => (<p key={idx1}>{message}</p>)) }
</div>))
}
</div>
Hope that helps!
Your object is not well structured, should be like this:
arrayOfMessages=[
{
title:"cars",
description:["toyota","ford","bmw"]
},
{
title:"bike",
description:["suzuki","bmw"]
}
]
then you can implement your code like this :
{
arrayOfMessages.map((message, idx) => {
const { description, title } = message
return (
<div key={idx} className="message-content">
<p>{title}</p>
{description.map((desc, index) => (
<p key={index}>desc</p>
))}
</div>
)
})
}
You should make up your data first.
...
let renderMessages = {}
arrayOfMessages.forEach(message => {
if (!renderMessages[message.title]) {
renderMessages[message.title] = {
title: message.title,
messages: [message.message]
}
} else {
renderMessages[message.title].messages.push(message.message)
}
})
return (
<div>
{Object.keys(renderMessages).map(key => {
let msg = renderMessages[key]
return <div key={key} className="message-content">
<p>{msg.title}</p>
{msg.messages.map(content => <p key={content}>{content}</p>)}
</div>
})}
</div>
)
You could group the messages based on the title using reduce. Then loop through the entries of the merged object to get the desired format
const arrayOfMessages=[{title:'cars',message:'toyota'},{title:'cars',message:'ford'},{title:'cars',message:'bmw'},{title:'bikes',message:'suzuki'},{title:'bikes',message:'bmw'},]
const grouped = arrayOfMessages.reduce((acc, { title, message }) => {
acc[title] = acc[title] || [];
acc[title].push(message);
return acc;
}, {})
console.log(grouped)
Here's a live demo:
class Sample extends React.Component {
render() {
const arrayOfMessages = [{ title: 'cars', message: 'toyota' }, { title: 'cars', message: 'ford' }, { title: 'cars', message: 'bmw' }, { title: 'bikes', message: 'suzuki' }, { title: 'bikes', message: 'bmw' },]
const grouped = arrayOfMessages.reduce((acc, { title, message }) => {
acc[title] = acc[title] || [];
acc[title].push(message);
return acc;
}, {})
return (
<div>
{
Object.entries(grouped).map(([title, messages], i1) => (
<div key={i1}>
<h1>{title}</h1>
{ messages.map((message, i2) => (<p key={i2}>{message}</p>)) }
</div>)
)
}
</div>
);
}
}
// Render it
ReactDOM.render(
<Sample />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
try this
render(){
//title watcher
let watcher = {};
return <div>
{arrayOfMessages.map((item, idx) => {
const {
message,
title
} = item
if(!watcher[title]){
//update watcher with title
//if watcher does not contain your title return title and message
watcher[title]=title;
return (
<div key={idx} className="message-content">
<p>{title}</p>
<p>{message}</p>
</div>
)
} else {
//if watcher found with title return your message
<div key={idx} className="message-content">
<p>{message}</p>
</div>
}
})}
</div>
}

React.js not re-rendering app on state update

I'm working on a React.js based outliner (in similar vein to workflowy). I'm stuck at a point. When I press enter on any one item, I want to insert another item just below it.
So I have used setState function to insert an item just below the current item as shown by the code snippet below:
this.setState({
items: this.state.items.splice(this.state.items.map((item) => item._id).indexOf(itemID) + 1, 0, {_id: (new Date().getTime()), content: 'Some Note'})
})
However, the app is re-rendering as blank without any error showing up.
My full source so far:
import './App.css';
import React, { Component } from 'react';
import ContentEditable from 'react-contenteditable';
const items = [
{
_id: 0,
content: 'Item 1 something',
note: 'Some note for item 1'
},
{
_id: 5,
content: 'Item 1.1 something',
note: 'Some note for item 1.1'
},
{
_id: 1,
content: 'Item 2 something',
note: 'Some note for item 2',
subItems: [
{
_id: 2,
content: 'Sub Item 1 something',
subItems: [{
_id: 3,
content: 'Sub Sub Item 4'
}]
}
]
}
];
class App extends Component {
render() {
return (
<div className="App">
<Page items={items} />
</div>
);
}
}
class Page extends Component {
constructor(props) {
super(props);
this.state = {
items: this.props.items
};
this.insertItem = this.insertItem.bind(this);
}
render() {
return (
<div className="page">
{
this.state.items.map(item =>
<Item _id={item._id} content={item.content} note={item.note} subItems={item.subItems} insertItem={this.insertItem} />
)
}
</div>
);
}
insertItem(itemID) {
console.log(this.state.items);
this.setState({
items: this.state.items.splice(this.state.items.map((item) => item._id).indexOf(itemID) + 1, 0, {_id: (new Date().getTime()), content: 'Some Note'})
})
console.log(this.state.items);
}
}
class Item extends Component {
constructor(props) {
super(props);
this.state = {
content: this.props.content,
note: this.props.note
};
this.saveItem = this.saveItem.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
}
render() {
return (
<div key={this.props._id} className="item">
<ContentEditable html={this.state.content} disabled={false} onChange={this.saveItem} onKeyPress={(event) => this.handleKeyPress(this.props._id, event)} />
<div className="note">{this.state.note}</div>
{
this.props.subItems &&
this.props.subItems.map(item =>
<Item _id={item._id} content={item.content} note={item.note} subItems={item.subItems} insertItem={this.insertItem} />
)
}
</div>
);
}
saveItem(event) {
this.setState({
content: event.target.value
});
}
handleKeyPress(itemID, event) {
if (event.key === 'Enter') {
event.preventDefault();
console.log(itemID);
console.log(event.key);
this.props.insertItem(itemID);
}
}
}
export default App;
The github repo: https://github.com/Hirvesh/mneme
Can anybody help me understand as to why it's not rendering again after I update the items?
Edit:
I have update the code to add keys, as suggested below, still rendering as blank, despite the state updating:
import './App.css';
import React, { Component } from 'react';
import ContentEditable from 'react-contenteditable';
const items = [
{
_id: 0,
content: 'Item 1 something',
note: 'Some note for item 1'
},
{
_id: 5,
content: 'Item 1.1 something',
note: 'Some note for item 1.1'
},
{
_id: 1,
content: 'Item 2 something',
note: 'Some note for item 2',
subItems: [
{
_id: 2,
content: 'Sub Item 1 something',
subItems: [{
_id: 3,
content: 'Sub Sub Item 4'
}]
}
]
}
];
class App extends Component {
render() {
return (
<div className="App">
<Page items={items} />
</div>
);
}
}
class Page extends Component {
constructor(props) {
super(props);
this.state = {
items: this.props.items
};
this.insertItem = this.insertItem.bind(this);
}
render() {
return (
<div className="page">
{
this.state.items.map(item =>
<Item _id={item._id} key={item._id} content={item.content} note={item.note} subItems={item.subItems} insertItem={this.insertItem} />
)
}
</div>
);
}
insertItem(itemID) {
console.log(this.state.items);
this.setState({
items: this.state.items.splice(this.state.items.map((item) => item._id).indexOf(itemID) + 1, 0, {_id: (new Date().getTime()), content: 'Some Note'})
})
console.log(this.state.items);
}
}
class Item extends Component {
constructor(props) {
super(props);
this.state = {
content: this.props.content,
note: this.props.note
};
this.saveItem = this.saveItem.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
}
render() {
return (
<div className="item">
<ContentEditable html={this.state.content} disabled={false} onChange={this.saveItem} onKeyPress={(event) => this.handleKeyPress(this.props._id, event)} />
<div className="note">{this.state.note}</div>
{
this.props.subItems &&
this.props.subItems.map(item =>
<Item _id={item._id} key={item._id} content={item.content} note={item.note} subItems={item.subItems} insertItem={this.insertItem} />
)
}
</div>
);
}
saveItem(event) {
this.setState({
content: event.target.value
});
}
handleKeyPress(itemID, event) {
if (event.key === 'Enter') {
event.preventDefault();
console.log(itemID);
console.log(event.key);
this.props.insertItem(itemID);
}
}
}
export default App;
Edit 2:
As per the suggestions below, added key={i._id}, still not working, pushed latest code to github if anybody wants to take a look.
this.state.items.map((item, i) =>
<Item _id={item._id} key={i._id} content={item.content} note={item.note} subItems={item.subItems} insertItem={this.insertItem} />
)
You miss to put a key on Item, because of that React could not identify if your state change.
<div className="page">
{
this.state.items.map((item, i) =>
<Item _id={item._id}
key={i} // <---- HERE!!
content={item.content}
note={item.note}
subItems={item.subItems}
insertItem={this.insertItem} />
)}
</div>
There are multiple issues with your code:
in you are passing this.insertItem into SubItems (which does not exists)
your this.state.items.splice, changes this.state.items, but returns []. So your new State is []
try this:
this.state.items.splice(/* your splice argument here */)
this.setState({ items: this.state.items }, () => {
// this.setState is async! so this is the proper way to check it.
console.log(this.state.items);
});
To accomplish what you actually want, this will not be enough.. But at least, when you have fixed it, you can see some results.

Categories

Resources