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} />
)