I'm pretty new to React, and I'm trying to practice by building a simple notes app. As far as I can tell it's going great, but! I read that state should not be updated manually, so I'm copying my state array and filtering out a result for a removal operation.
But it fails! Rather, if I console log, it correctly removes the to-be-deleted element from the state array, however, when I call setState() on the copy to update my view, the list is wrong!
For some reason my React list is always removing the last element visually from the page, and appears then out of sync with my state.
The app itself is a Form container of sorts with a nested list and list-item component, which use props from the form class to manage.
What am I doing wrong?
Form Class
class NotesForm extends Component {
constructor(props) {
super(props);
const list = [
{ text: "Build out UI" },
{ text: "Add new note" },
{ text: "delete notes" },
{ text: "edit notes" }
];
this.state = {
'notes': list
};
// this.notes = list;
this.handleSubmit = this.handleSubmit.bind(this);
this.deleteNote = this.deleteNote.bind(this);
}
handleSubmit(e) {
e.preventDefault();
if (this.input.value.length === 0) { return; }
this.state.notes.push({text: this.input.value});
this.setState({ notes: this.state.notes });
this.input.value = "";
}
// BUG - deletes WRONG note!!
deleteNote(note) {
console.log({'DELETE_NOTE': note.text})
// var list = _.clone(this.state.notes);
var list = [...this.state.notes];
var filteredNotes = _.filter(list, function(n) {
return (n.text !== note.text);
})
console.log({
'list': list,
'filteredNotes': filteredNotes
})
this.setState({ notes: filteredNotes });
}
render() {
return (
<div className="row notes-form">
<div className="col-xs-12">
<form onSubmit={this.handleSubmit}>
<input type="text" className="new-note-input" ref={(input) => this.input = input} />
<br />
<button className="add-btn btn btn-info btn-block" type="button" onClick={this.handleSubmit}>Add</button>
<br />
<NotesList notes={this.state.notes} deleteNote={this.deleteNote} />
</form>
</div>
</div>
);
}
}
List Class
class NotesList extends Component {
constructor(props) {
super(props);
}
render() {
return (
<ul className="notes-list">
{this.props.notes.map((n, index) => <NotesListItem key={index} note={n} deleteNote={this.props.deleteNote} />)}
</ul>
);
}
}
List Item Class
class NotesListItem extends Component {
constructor(props) {
super(props);
this.state = {
'text': props.note.text
};
this.delete = this.delete.bind(this);
}
delete() {
this.props.deleteNote(this.props.note);
}
render() {
return (
<li className="notes-list-item">
<span className="item-text">{this.state.text}</span>
<div className="notes-btn-group btn-group" role="group">
<button className="delete-btn btn btn-danger" type="button" onClick={this.delete}>×</button>
</div>
</li>
);
}
}
Try using something like a unique id instead of index as the key for each NotesListItem in NotesList. See this related question (maybe a duplicate actually):
import React, { Component } from 'react';
import NotesListItem from './NotesListItem';
class NotesList extends Component {
constructor(props) {
super(props);
}
render() {
return (
<ul className="notes-list">
{this.props.notes.map((n, index) => <NotesListItem key={n.id} note={n} deleteNote={this.props.deleteNote} />)}
</ul>
);
}
}
export default NotesList;
You can use something like uuid to generate a "unique" id. There are many ways you could generate a unique key, but it depends on your data structure. Also using a unique id and filtering based on the id, can help avoid a situation where two notes in the array have the same text as filtering based on the text value would delete both of them.
import uuidv1 from 'uuid/v1';
// ...
handleSubmit(e) {
e.preventDefault();
if (this.input.value.length === 0) { return; }
this.state.notes.push({id: uuidv1(), text: this.input.value});
this.setState({ notes: this.state.notes });
this.input.value = "";
}
I only suggest to use something like this as it's possible your text could be duplicated. You could probably even get away with using something like:
{this.props.notes.map((n, index) => <NotesListItem key={index + n.text} note={n} deleteNote={this.props.deleteNote} />)}
Also, you shouldn't be directly mutating state like this.state.notes.push({text: this.input.value});. Try something like this instead:
handleSubmit(e) {
e.preventDefault();
if (this.input.value.length === 0) { return; }
const note = { id: uuidv1(), text: this.input.value };
const notes = [...this.state.notes, note];
this.setState({ notes });
this.input.value = "";
}
Also, I'd avoid using ref for handling controlled inputs, especially to set value. Why not create a property on state that handles the value of the input in combination with a simple onChange event handler. This would be in line with the React Forms documentation and the "standard" React way of handling input value updates:
handleChange(e) {
this.setState({ text: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
if (this.state.text.length === 0) { return; }
const note = { id: uuidv1(), text: this.state.text };
const notes = [...this.state.notes, note];
this.setState({ text: '', notes });
}
render() {
// ...
<input type="text" className="new-note-input" value={this.state.text} onChange={this.handleChange} />
// ...
}
Here is an example in action.
The other answer may be enough to resolve your issue. I'd recommend to review the following article mentioned/linked in the React Keys documentation discuss the potential negative impacts of using an index as a key.
Hopefully that helps!
The constructor of a Component only runs once. React will reuse component instances passing them new props. The problem here is that NodeListItem caches the text of the note in its own local state and uses that text in the render method. When its Parent passes a new note to it via the props, it does not use it. It uses the state which is now stale.
Child components should usually use data from props passed in by the Parent.
class NotesListItem extends Component {
constructor(props) {
super(props);
// The problem is this statement here
this.state = {
'text': props.note.text
};
this.delete = this.delete.bind(this);
}
}
Here is a fixed version of the NotesListItem class.
class NotesListItem extends Component {
constructor(props) {
super(props);
this.delete = this.delete.bind(this);
}
delete() {
this.props.deleteNote(this.props.note);
}
render() {
return (
<li className="notes-list-item">
<span className="item-text">{this.props.note.text}</span> {/* <-- using props */}
<div className="notes-btn-group btn-group" role="group">
<button
className="delete-btn btn btn-danger"
type="button"
onClick={this.delete}
>
×
</button>
</div>
</li>
);
}
}
Related
I am trying to build a ToDoList app and I have two components. I have a main component that handles the state and another button component that renders a delete button next to every task that I render. The problem I have is that i cant seem to connect the delete button to the index of the array and delete that specific item in the array by clicking on the button next to it.
I have tried to connect the index by using the map key id to the delete function.
just need help with how my delete function should look like and how its going to get the index of the item that is next to it and delete it.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
userInput: '',
toDoList : []
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.delete = this.delete.bind(this);
}
handleSubmit() {
const itemsArray = this.state.userInput.split(',');
this.setState({
toDoList: this.state.toDoList.concat(itemsArray),
userInput: ''
});
}
handleChange(e) {
this.setState({
userInput: e.target.value
});
}
delete(id) {
this.setState({
toDoList: this.state.toDoList.filter( (item) => id !== item.id )
})
}
render() {
return (
<div>
<textarea
onChange={this.handleChange}
value={this.state.userInput}
placeholder="Separate Items With Commas" /><br />
<button onClick={this.handleSubmit}>Create List</button>
<h1>My Daily To Do List:</h1>
<Button toDoList={this.state.toDoList} handleDelete={this.delete} />
</div>
);
}
};
class Button extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<ul>
{
this.props.toDoList.map( (item) => <li key={item.id}>{item.text} <button onClick={this.props.delete(item.id)}>Done!</button></li> )
}
</ul>
);
}
};
I reviewed your edited code and made a couple of changes.
I don’t get what exactly you want to achieve with you handleSubmit method but items it adds to the list are simple strings and don’t have neither ‘id’ nor ‘text’ properties you’re referring to in other places. Possibly you’re going to change this later but while your to do items are just strings I’ve edited your code so that it work properly under this condition.
Edited delete method now accepts not item.id as a parameter but the whole item object. Yet I'm using functional form of setState as it was correctly suggested by #Hamoghamdi
delete(itemToDelete) {
this.setState(state => ({
toDoList: state.toDoList.filter( (item) => itemToDelete !== item)
}))
}
Edited render method of Button class now displays items as text and properly bind delete handler...
render() {
return (
<ul>
{
this.props.toDoList.map( (item) => <li key={item}>
{item}
<button onClick={() => this.props.handleDelete(item)}>Done!</button>
</li> )
}
</ul>
);
}
BTW Button is a bad naming for the component that isn’t exactly a button. Yet it’s better to implement it as a functional component. Use class components only if the component has its own state.
you should try using an anonymous function with setState() instead of returning an object literal directly, specially when you want to do something affected by the previous or current state
using this.state inside of setState() won't give you any good results.
here, try this:
delete = (id) => {
this.setState((prevState) => {
return { toDoList: prevState.filter( (task) => id !== task.id )}
});
You need to bind the method in constructor for example:
constructor(props) {
//...
this.handleDelete = this.handleDelete.bind(this)
}
also you can find another ways how to bind methods
In terms of handling the deleting the items, you can use
handleDelete(index) {
// Use the splice array function: splice(index, deleteCount)
this.todoList.splice(index, 1);
}
And that is all that easy
I have three components:
PageBuilder - is basically a form where the user adds a page name and selects some items.
PageList - stores all pages the user has created in state and renders that state as a list
PageUpdater - takes the form info from PageBuilder and adds it to PageList
The problem I'm having is that the state of each component is always one step behind. I realise that this is because setState is asynchronous but I'm not sure what's the best way to get around that. I've read a few possible solutions but I'm not sure how best to implement them in my setup. Can anyone advise?
Here is PageBuilder (I've cut it down for clarity):
constructor(props){
super(props);
this.state = {
pageTitle: '', pageDesc:'', items: [], id:''
};
}
updateTitle = (e) => {
this.setState({pageTitle: e.target.value});
}
updateDesc = (e) => {
this.setState({pageDesc: e.target.value});
}
addNewPage = () => {
let info = {...this.state};
this.props.callBack(info);
}
render() {
return (
<input className="pageTitleField" type="text" placeholder="Page Title"
value={this.state.pageTitle} onChange={this.updateTitle}></input>
<textarea className="pageDescField" placeholder="Page description..."
onChange={this.updateDesc}></textarea>
<button onClick={this.addNewPage}>New Page</button>
)
}
PageUpdater:
export class PageUpdater extends React.Component{
constructor(props){
super(props);
this.state={
data: ''
}
}
updatePageList = (pageAdded) =>{
this.setState({data:pageAdded});
console.log(this.state)
}
render(){
return(
<div>
<PageBuilder callBack={this.updatePageList} />
<PageList addToList={this.state.data} />
</div>
)}}
PageList:
export class PageList extends React.Component{
constructor(props){
super(props);
this.state = {pages:''}
}
componentWillReceiveProps(props) {
this.setState({pages: [...this.state.pages, this.props.addToList]})
}
getPages = () => {
var pages = []
for(var key in this.state.pages){
pages.push(this.state.pages[key].pageTitle)}
return pages // Return an array with the names
}
render(){
return(
<div>
{this.getPages().map((page, index) => <li key={index}>{page}
</li>)}
</div>
)}}
Inside of componentWillReceiveProps this.props refers to the previous version of props. But what you need is to use the latest version of props.
Instead of
componentWillReceiveProps(props) {
this.setState({pages: [...this.state.pages, this.props.addToList]})
}
You should write
componentWillReceiveProps(nextProps) {
this.setState({pages: [...this.state.pages, nextProps.addToList]}) // notice the difference this.props vs nextProps
}
(Pardon the verbose question. I'm brand new to React and ES6, and I'm probably overly-convoluting this.)
I am writing an app that contains a button component. This button calls a method onAddChild that creates another component of class ColorModule by adding a value to an array stored in the App's state.
In each newly created ColorModule, I want to include another button that will remove the module. Since this component is created by an array.map method, my thought is that if I can find the index of the array item that corresponds with the component and use that index in array.splice then perhaps that component will be removed (untested theory). That said, I'm not really sure how to find the index where I would use this in my onRemoveModule method.
Two part question: 1) How would I go about finding the index of the array item in my state, and 2) if I'm completely off base or there's a better way to do this altogether, what does that solution look like?
imports...
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(1) });
}
onRemoveModule( e ) {
e.preventDefault();
...¯\_(ツ)_/¯
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} /> /* Add module button lives here */
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule}
key={index}
moduleId={'colorModule' + index}
/>
); /* Remove module button would live in the module itself */
}
)}
</div>
</div>
);
}
}
export default App;
Well this part is pretty easy, all you need to do is pass the index as prop to the ColorModule component and when calling the onRemove method in it you could pass it back to the onRemoveModule. However react optimizes based on keys and its a really good idea to have a unique id given to each module instance.
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(uuid()) }); //uuid must return a unique id everytime to be used as component key
}
onRemoveModule( index ) {
// now with this index you can update the moduleList
}
render() {
const { className } = this;
return (
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
index={index}
onRemove={this.onRemoveModule}
key={delta}
moduleId={'colorModule' + delta}
/>
);
}
)}
</div>
);
}
}
Now in ColorModule component
class ColorModule extends React.Component {
onRemoveClick=() => {
this.props.onRemove(this.props.index);
}
}
Check this answer for more details on how to pass data from Child component to Parent
I ended up solving this problem using some of the guidance here from #ShubhamKhatri (didn't know about unique ID generation!), but I took a slightly different approach and handled the solution using state manipulation in App without needing a new method in my ColorModule component. I also never knew about currying in ES6, so that discovery made passing in the index values needed to manipulate my state array possible
If I'm off-base here or being inefficient, I'm definitely still open to feedback on a better way!
class App extends Component {
constructor(props) {
super(props);
this.state = {
moduleList: [{ id: UniqId(), removeModule: false }],
};
this.onAddChild = this.onAddChild.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({
moduleList: moduleList.concat({
id: UniqId(),
removeModule: false,
}),
});
}
onRemoveModule = ( i, arr ) => (e) => {
const moduleList = this.state.moduleList;
e.preventDefault();
moduleList[i].removeModule = true;
this.setState({ moduleList: moduleList });
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} />
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
if ( !this.state.moduleList[index].removeModule ) {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule( index, this.state.moduleList )}
index={index}
key={delta.id}
moduleId={'colorModule' + delta}
/>
);
}
}
)}
</div>
</div>
);
}
}
I'm teaching myself react with a super simple app that asks the user to type a word presented in the UI. If user enters it correctly, the app shows another word, and so on.
I've got it almost working, except for one thing: after a word is entered correctly, I need to clear the input element. I've seen several answers here about how an input element can clear itself, but I need to clear it from the component that contains it, because that's where the input is checked...
// the app
class AppComponent extends React.Component {
constructor() {
super();
this.state = {
words: ['alpha', 'bravo', 'charlie'],
index: 0
};
}
renderWordsource() {
const word = this.state.words[this.state.index];
return <WordsourceComponent value={ word } />;
}
renderWordinput() {
return <WordinputComponent id={1} onChange={ this.onChange.bind(this) }/>;
}
onChange(id, value) {
const word = this.state.words[this.state.index];
if (word == value) {
alert('yes');
var nextIndex = (this.state.index == this.state.words.count-1)? 0 : this.state.index+1;
this.setState({ words:this.state.words, index:nextIndex });
}
}
render() {
return (
<div className="index">
<div>{this.renderWordsource()}</div>
<div>{this.renderWordinput()}</div>
</div>
);
}
}
// the input component
class WordinputComponent extends React.Component {
constructor(props) {
this.state = { text:''}
}
handleChange(event) {
var text = event.target.value;
this.props.onChange(this.props.id, text);
}
render() {
return (
<div className="wordinput-component">
<input type="text" onChange={this.handleChange.bind(this)} />
</div>
);
}
}
See where it says alert('yes')? That's where I think I should clear the value, but that doesn't make any sense because it's a parameter, not really the state of the component. Should I have the component pass itself to the change function? Maybe then I could alter it's state, but that sounds like a bad idea design-wise.
The 2 common ways of doing this is controlling the value through state in the parent or using a ref to clear the value. Added examples of both
The first one is using a ref and putting a function in the child component to clear
The second one is using state of the parent component and a controlled input field to clear it
class ParentComponent1 extends React.Component {
state = {
input2Value: ''
}
clearInput1() {
this.input1.clear();
}
clearInput2() {
this.setState({
input2Value: ''
});
}
handleInput2Change(evt) {
this.setState({
input2Value: evt.target.value
});
}
render() {
return (
<div>
<ChildComponent1 ref={input1 => this.input1 = input1}/>
<button onClick={this.clearInput1.bind(this)}>Clear</button>
<ChildComponent2 value={this.state.input2Value} onChange={this.handleInput2Change.bind(this)}/>
<button onClick={this.clearInput2.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent1 extends React.Component {
clear() {
this.input.value = '';
}
render() {
return (
<input ref={input => this.input = input} />
);
}
}
class ChildComponent2 extends React.Component {
render() {
return (
<input value={this.props.value} onChange={this.props.onChange} />
);
}
}
ReactDOM.render(<ParentComponent1 />, document.body);
<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>
I had a similar issue: I wanted to clear a form which contained multiple fields.
While the two solutions by #noveyak are working fine, I want to share a different idea, which gives me the ability to partition the responsibility between parent and child: parent knows when to clear the form, and the items know how to react to that, without using refs.
The idea is to use a revision counter which gets incremented each time Clear is pressed and to react to changes of this counter in children.
In the example below there are three quite simple children reacting to the Clear button.
class ParentComponent extends React.Component {
state = {revision: 0}
clearInput = () => {
this.setState((prev) => ({revision: prev.revision+1}))
}
render() {
return (
<div>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<button onClick={this.clearInput.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent extends React.Component {
state = {value: ''}
componentWillReceiveProps(nextProps){
if(this.props.revision != nextProps.revision){
this.setState({value : ''});
}
}
saveValue = (event) => {
this.setState({value: event.target.value})
}
render() {
return (
<input value={this.state.value} onChange={this.saveValue} />
);
}
}
ReactDOM.render(<ParentComponent />, document.body);
<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>
EDIT:
I've just stumbled upon this beautifully simple solution with key which is somewhat similar in spirit (you can pass parents's revision as child's key)
Very very very simple solution to clear form is add unique key in div under which you want to render form from your child component key={new Date().getTime()}:
render(){
return(
<div className="form_first_step fields_black" key={new Date().getTime()}>
<Form
className="first_step">
// form fields coming from child component
<AddressInfo />
</div>
</Form>
</div>
)
}
I'm building a to do list application which is taking in two objects from a store set as default.
Having trouble taking what the user has inputted as their new "to do" item and pushing it to the store / application state.
If I click on the "Create new" button, this successfully creates a new to do item however, no text.
I am getting the value from the input with the handleChange function - I need to send that value as part of the createTodo function to the store.
The createToDo function is what calls the action to add a new "to do" item to the store. I can't figure out how to tie the input value to that function to create the new to do item.
Here is my code so far:
import React from "react";
import Todo from "../components/Todo";
import * as TodoActions from "../actions/TodoActions";
import TodoStore from "../stores/TodoStore";
export default class Featured extends React.Component {
constructor() {
super()
this.getTodos = this.getTodos.bind(this);
this.state = {
todos: TodoStore.getAll()
}
}
componentWillMount() {
TodoStore.on("change", this.getTodos);
console.log("count", TodoStore.listenerCount("change"));
}
componentWillUnmount() {
TodoStore.removeListener("change", this.getTodos);
}
getTodos() {
this.setState({
todos: TodoStore.getAll(),
});
}
createTodo(text) {
TodoActions.createTodo();
}
handleChange(e) {
let text = e.target.value;
console.log(text);
}
reloadTodos() {
TodoActions.reloadTodos();
}
render() {
const { todos } = this.state;
console.log("todos", todos);
const todoitems = todos.map((todoitem) => {
return <Todo key={todoitem.id} {...todoitem} />
});
return (
<div className="todolist-wrapper">
<h1>To do list</h1>
<button onClick={this.reloadTodos.bind(this)}>Reload</button>
<ul>
{todoitems}
</ul>
<input type="text" onChange={this.handleChange.bind(this)} />
<button onClick={this.createTodo.bind(this)}>Create new</button>
</div>
);
}
}
First add the value to your component's state:
constructor() {
...
this.state = {
todos: TodoStore.getAll(),
value: ""
}
}
You can add this as the value attribute of the input element itself (in order to let the initial value within your state control the initially-rendered value of the input element):
<input type="text" value={this.state.value} onChange={this.handleChange.bind(this)} />
Then in your handleChange function, modify the state's value using setState:
handleChange(e) {
let text = e.target.value;
this.setState({
value: text
});
}
Now you can grab the value from your component's state within your createTodo function:
createTodo() {
let text = this.state.value;
TodoActions.createTodo(text);
}