I trying to display the list using map function javascript but I am getting error saying "TypeError: Cannot read property 'map' of undefined".
import React, { Component } from 'react'
import constants from './Constants'
import axios from 'axios'
class Home extends React.Component {
constructor(props) {
super(props)
this.state = {
value: 0,
results: {},
}
this.handleClick = this.handleClick.bind(this)
this.input = React.createRef()
}
handleClick = event => {
this.setState({ value: this.input.current.value })
event.preventDefault()
}
componentDidMount() {
console.log('componnet did mount')
const that = this
axios.get('https://reqres.in/api/users').then(function(response) {
that.setResults(response.data)
})
}
setResults(data) {
this.setState({ results: data })
}
render() {
let newvalue = this.state.value
let obj = this.state.results
console.log(obj)
let latestvalue =
constants.MONTHS[newvalue] == null
? 'invalid month'
: constants.MONTHS[newvalue]
return (
<div className="home">
<h1>Welcome to my portfolio website</h1>
{obj.data.map(emp => (
<tr>
<td> </td>
</tr>
))}
Enter Month number <input type="text" ref={this.input} />
<button type="button" onClick={this.handleClick}>
{' '}
submit{' '}
</button>
<p> Feel free to browse around and learn more about me.</p>
Month is {latestvalue}
</div>
)
}
}
export default Home
Need to display all the first names on DOM.
I just need to display the first names in that array of object also recommend me which Javascript function best to use display data.
Try to update following block of lines may help you:
{obj && obj.data && obj.data.map(emp =>
<tr>
<td> {emp.first_name}</td>
</tr>
)}
When initializing the state, you need to describe the full shape of the object for TypeScript to understand it.
results : {
obj: {
data: null
}
}
Related
I've come to a halt making this covid19 app where I can see a list of countries on the left side of the screen with the option of adding any number of countries to the right side of the screen, which displays more covid data of the added country. I'm also kinda new to React.
Problem is, when I click the add button the added state is updated, and it displays that added country on the right side of the screen. But, when I try adding another country I get an error. I believe the error is somewhere around when I try to setState({ state }) in the addCountry method from within App.js.
In other words, the 'added' state is only letting itself hold no more than one array element. Help much much much appreciated. I posted all the code.
index.js
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
App.js
import CountryList from "./components/CountryList.js";
import Find from "./components/Find.js";
import Added from "./components/Added.js";
class App extends Component {
constructor() {
super();
this.state = {
countries: [],
inputbox: [],
added: [],
};
}
// Arrow functions capture "this" when they are defined, while standard functions do when they are executed.
// Thus, no need for the bind method. Awesome.
handleChange = (e) =>
this.setState({
inputbox: e.target.value,
});
getCountryData = async (slug) => {
const resp = await fetch(`https://api.covid19api.com/live/country/${slug}`);
var addedData = await resp.json();
// Api returns most days of covid, per country, that it tracks
// Thus, we want the last tracked day of a country
addedData = addedData[addedData.length - 1];
return addedData;
};
// Add a country to the added state
// Call when user clicks button associated with their desired country
addCountry = async (btnId) => {
const { countries, added } = this.state;
var addedData = await this.getCountryData(btnId);
countries.map((country) => {
// If the button ID is equal to the current country in the loops' Slug
if (btnId == country.Slug) {
try {
added.push([
{
addedCountry: addedData.Country,
confirmedTotal: addedData.Confirmed,
deathsTotal: addedData.Deaths,
recoveredTotal: addedData.Recovered,
activeTotal: addedData.Active,
},
]);
// (bug) IT IS PUSHING, BUT ITS NOT SETTING THE STATE!
// ITS ONLY LETTING ME KEEP ONE ITEM IN THE STATE
this.setState({ added });
console.log(added);
} catch (error) {
alert(`Sorry, country data not available for ${country.Country}`);
return;
}
}
});
};
removeCountry = (btnId) => {
const { added } = this.state;
added.map((added, index) => {
//console.log(added[index].addedCountry);
if (btnId == added[index].addedCountry) {
added.splice(index, 1);
this.setState({ added: added });
} else {
console.log("not removed");
return;
}
});
};
// Mount-on lifecycle method
async componentDidMount() {
const resp = await fetch("https://api.covid19api.com/countries");
const countries = await resp.json(); // parsed response
this.setState({ countries }); // set state to parsed response
}
render() {
// Filter out countries depending on what state the inputbox is in
const { countries, inputbox } = this.state;
const filtered = countries.filter((country) =>
country.Country.includes(inputbox)
);
return (
<div className="App Container">
<Find
placeholder="Type to find a country of interest..."
handleChange={this.handleChange}
/>
<div className="row">
<CountryList countries={filtered} addCountry={this.addCountry} />
<Added added={this.state.added} removeCountry={this.removeCountry} />
</div>
</div>
);
}
}
export default App;
Added.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
import AddedCountry from "./AddedCountry.js";
class Added extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="col-md-6">
<Table>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Country</th>
<th scope="col">Active</th>
<th scope="col">Confirmed Total</th>
<th scope="col">Recovered</th>
<th scope="col">Deaths</th>
<th scope="col">Action</th>
</tr>
</thead>
{this.props.added.map((added, index) => (
<AddedCountry
added={added[index]}
removeCountry={this.props.removeCountry}
/>
))}
</Table>
</div>
);
}
}
export default Added;
AddedCountry.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
class AddedCountry extends Component {
constructor(props) {
super(props);
}
render() {
return (
<tbody>
<tr>
<td></td>
<td>{this.props.added.addedCountry}</td>
<td>{this.props.added.activeTotal}</td>
<td>{this.props.added.confirmedTotal}</td>
<td>{this.props.added.recoveredTotal}</td>
<td>{this.props.added.deathsTotal}</td>
<td>
{
<Button
onClick={() =>
this.props.removeCountry(
document.getElementById(this.props.added.addedCountry).id
)
}
id={this.props.added.addedCountry}
type="submit"
color="danger"
size="sm"
>
Remove
</Button>
}
</td>
</tr>
</tbody>
);
}
}
export default AddedCountry;
CountryList.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
import Country from "./Country.js";
class CountryList extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="col-md-6">
<Table>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Country</th>
<th scope="col">Actions</th>
</tr>
</thead>
{
// Each country is a component
// Function will display all countries as the Map function loops through them
this.props.countries.map((country) => (
<Country countries={country} addCountry={this.props.addCountry} />
))
}
</Table>
</div>
);
}
}
export default CountryList;
Country.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
class Country extends Component {
constructor(props) {
super(props);
}
render() {
return (
<tbody>
<tr>
<td></td>
<td>{this.props.countries.Country}</td>
<td>
{
<Button
onClick={() =>
this.props.addCountry(
document.getElementById(this.props.countries.Slug).id
)
}
id={this.props.countries.Slug}
type="submit"
color="success"
size="sm"
>
Add
</Button>
}
</td>
</tr>
</tbody>
);
}
}
export default Country;
Find.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
class Find extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="Find container">
<br />
<Form>
<div className="form-row">
<div className="form-group col-md-6">
<h3>Find a Country</h3>
<Input
type="text"
className="form-control"
id="country"
placeholder={this.props.placeholder}
onChange={this.props.handleChange}
></Input>
</div>
</div>
</Form>
</div>
);
}
}
export default Find;
I haven't pored over all that code, but focusing right where you think the issue is it is obvious you are mutating your state object by pushing directly into the added array.
Solution
Don't mutate state!
Since it seems you only want to add a single new "add" and only when the button's btnId matches a country's slug, and the btnId can only ever be a valid value from the mapped countries array, I think this can be greatly simplified.
addCountry = async (btnId) => {
const addedData = await this.getCountryData(btnId);
if (addedData) {
this.setState(prevState => ({
added: prevState.added.concat({ // <-- concat creates a new array reference
addedCountry: addedData.Country,
confirmedTotal: addedData.Confirmed,
deathsTotal: addedData.Deaths,
recoveredTotal: addedData.Recovered,
activeTotal: addedData.Active,
}),
}));
} else {
alert(`Sorry, country data not available for ${country.Country}`);
}
};
Similarly the removeCountry handler is mis-using the array mapping function and mutating the added state. Array.prototype.filter is the idiomatic way to remove an element from an array and return the new array reference.
removeCountry = (btnId) => {
this.setState(prevState => ({
added: prevState.added.filter(el => el.addedCountry !== btnId),
}));
};
Additional Issues & Suggestions
Added.js
If you maintain the added array as a flat array (not an array of arrays) then it's trivial to map the values.
{this.props.added.map((added) => (
<AddedCountry
key={added}
added={added}
removeCountry={this.props.removeCountry}
/>
))}
Country.js & AddedCountry.js
I don't see any reason to query the DOM for the button id when you are literally right there and can enclose the country slug in the onClick callback.
<Button
onClick={() => this.props.addCountry(this.props.countries.Slug)}
id={this.props.countries.Slug}
type="submit"
color="success"
size="sm"
>
Add
</Button>
<Button
onClick={() => this.props.removeCountry(this.props.added.addedCountry)}
id={this.props.added.addedCountry}
type="submit"
color="danger"
size="sm"
>
Remove
</Button>
App.js
This may or may not matter, but it is often the case to do case-insensitive search/filtering of data. This is to ensure something like "France" still matching a user's search input of "france".
const filtered = countries.filter((country) =>
country.Country.toLowerCase().includes(inputbox.toLowerCase())
);
I'm learning ReactJS and using this library https://github.com/salesforce/design-system-react.
I'm attempting to use a component I created SelectCell. It's being used two times. I'd like to pass it a prop selectedOption and in the first instance pass it a property originating from my state, a property selectedSectionId and the second time the component is used set selectedOption to be selectedQuestionId.
The issue is the library obfuscates some of the logic away and I'm not well versed enough in react to understand what to do. I set items on the DataTable component and I know the children components have access to item in props. I'm getting the error TypeError: Cannot read property 'selectedSectionId' of undefined My component is below:
import React from 'react';
import {Button,DataTable,DataTableColumn,DataTableCell,Dropdown,DataTableRowActions} from '#salesforce/design-system-react';
const ParameterDataTableCell = ({ children, ...props }) => (
<DataTableCell title={children} {...props}>
<input type='text' className='slds-input' value={props.item.parameterName} />
</DataTableCell>
);
ParameterDataTableCell.displayName = DataTableCell.displayName;
const SelectCell = ({ children,...props }) => (
<DataTableCell {...props}>
<div>
<Dropdown
align='left'
checkmark={false}
iconCategory='utility'
iconName='down'
iconPosition='right'
label={setPicklistLabel(props.allOptions,props.type,props.item,props.selectedOptionId)}
options={props.allOptions}
value={props.item.sectionName}>
</Dropdown>
</div>
</DataTableCell>
);
const setPicklistLabel = (allOptions,picklistType,item,selectedOptionId) => {
const foundOption = allOptions.find((thisOption) => selectedOptionId===thisOption.id);
return foundOption ? foundOption.label : 'Select an Option';
}
SelectCell.displayName = DataTableCell.displayName;
class ParameterTable extends React.Component {
static displayName = 'ParameterTable';
state = {
paramRows: [
{
parameterName: 'param1',
selectedSectionId: '001441094',
selectedQuestionId: '00ri23or231441094'
},
{
parameterName: 'param2',
selectedSectionId: '001441094',
selectedQuestionId: '00ri23or231441094'
}
],
};
addRow = () => {
const newRow = {'parameterName':'','selectedSectionId':'','selectedQuestionId':''};
const rows = this.state.paramRows;
rows.push(newRow);
this.setState({items:rows});
};
render() {
return (
<div>
<DataTable
items={this.state.paramRows}
className='slds-m-top_large'
>
<DataTableColumn
label='Parameter Name'
primaryColumn
property='parameterName'
>
<ParameterDataTableCell />
</DataTableColumn>
<DataTableColumn
label='Section Name'
property='sectionName'
>
<SelectCell
allOptions={this.props.serverData.allSections}
selectedOptionId={this.props.item.selectedSectionId}/>
</DataTableColumn>
<DataTableColumn
label='Question Name'
property='questionName'
>
<SelectCell
allOptions={this.props.serverData.allQuestions}
selectedOptionId={this.props.item.selectedQuestionId}/>
</DataTableColumn>
</DataTable>
<Button
iconCategory='utility'
iconName='add'
iconPosition='right'
label='Add Parameter'
onClick={this.addRow}
/>
</div>
);
}
}
export default ParameterTable;
Well, from first glance, it seems like you missed out using the constructor(props) and super(props) lines.
class ParameterTable extends React.Component {
static displayName = 'ParameterTable';
constructor(props) {
super(props);
this.state = {
paramRows: [
{
parameterName: 'param1',
selectedSectionId: '001441094',
selectedQuestionId: '00ri23or231441094'
},
{
parameterName: 'param2',
selectedSectionId: '001441094',
selectedQuestionId: '00ri23or231441094'
}
],
};
}
This is my first question here after years, so pardon me if I break any forum/platform rule.
I am trying to build a CGPA CALCULATOR so I am having an issue updating a variable on user input change.
I am a beginner so my code and description may be watery. The problem is with my handleChange method I guess, because every time I make an input (I am testing with the courseInput for now), the app crashes with the error:
TypeError: Cannot read property 'setState' of undefined
Someone should please explain to me in details.
I have actually tried a lot Googling but nothing seems wrong with my code.
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
// this.courseInput = React.createRef();
this.state = {
courseInput: [],
courseCode: '',
courseUnit: [0],
courseGrade: [],
totalPoint: 0,
totalUnit: 0,
newCourseInput: <form>
<input onChange={this.handleChange} type="text" placeholder='COURSE CODE' value={this.courseCode} />
{/* <input type="number" placeholder='COURSE UNIT' ref={this.courseUnit} value={this.courseUnit} />
<input type="number" placeholder='COURSE GRADE' ref={this.courseGrade} value={this.courseGrade} /> */}
</form>
};
this.createAnother = this.createAnother.bind(this);
this.handleChange = this.handleChange.bind(this);
}
// THIS createAnother TAKES THE CURRENT STATE OF courseInput AND CONCATENATES IT WITH THE newCourseInput TO MAKE LIST
createAnother() {
var courseInput = this.state.courseInput.concat(this.state.newCourseInput)
this.setState({ courseInput })
}
handleChange(event) {
var updatedCourseCode = event.target.value;
this.setState({ courseInput: updatedCourseCode }, () => console.log(this.state))
}
render() {
// console.log(this);
// var courseInput = this.state.courseInput;
return(
<div>
<header className="App-header">
<p>
THIS IS A CGPA CALCULATOR
</p>
</header>
{/* MAP FUNCTION LOOPS THROUGH THE ARRAY courseInput AND PRINTS OUT THE CODE UNIT AND GRADE IN IN ORDERED LIST */}
<ol>
{this.state.courseInput.map((courseInput, index) =>
<li key={index}>{courseInput}</li>
)}
</ol>
{/* THIS TRIGGERS AN EVENT HANDLER createAnother LOCATED UP THERE */}
<button onClick={this.createAnother} >ADD ANOTHER COURSE</button>
</div>
);
}
}
export default App;
You should not store jsx elements in your state, but only the necessary data to render these elements later when needed. you also have a mistakes(you tried to assign string to an courseInput whice is array).
import React, { Component } from "react";
// import './App.css';
class App extends Component {
constructor(props) {
super(props);
// this.courseInput = React.createRef();
this.state = {
courseInput: [],
courseCode: "",
courseUnit: [0],
courseGrade: [],
totalPoint: 0,
totalUnit: 0,
};
}
// THIS createAnother TAKES THE CURRENT STATE OF courseInput AND CONCATENATES IT WITH THE newCourseInput TO MAKE LIST
createAnother = () => {
var courseInput = this.state.courseInput.concat({
id: this.state.courseInput.length,
value: "",
});
this.setState({ courseInput });
};
handleCourseChange = (value, id) => {
const newCourseInputs = [...this.state.courseInput];
console.log(newCourseInputs);
console.log(value, id);
let courseToChange = newCourseInputs.find((c) => c.id == id);
courseToChange.value = value;
this.setState({ courseInput: newCourseInputs });
};
render() {
// console.log(this);
// var courseInput = this.state.courseInput;
console.log(this.state.courseInput);
return (
<div>
<header className="App-header">
<p>THIS IS A CGPA CALCULATOR</p>
</header>
{/* MAP FUNCTION LOOPS THROUGH THE ARRAY courseInput AND PRINTS OUT THE CODE UNIT AND GRADE IN IN ORDERED LIST */}
<ol>
{this.state.courseInput.map((courseInput, index) => (
<li key={index}>
<input
onChange={(e) =>
this.handleCourseChange(e.target.value, courseInput.id)
}
type="text"
placeholder="COURSE CODE"
value={courseInput.value}
/>
</li>
))}
</ol>
{/* THIS TRIGGERS AN EVENT HANDLER createAnother LOCATED UP THERE */}
<button onClick={this.createAnother}>ADD ANOTHER COURSE</button>
</div>
);
}
}
export default App;
this code will probably work as you intended.
in handleChange use arrow function instead of regular function :
class A {
handleChange(event){
this // the keyword "this" refer to the function handleChange
}
}
class A {
handleChange =(event)=>{
this // the keyword "this" refer to the class A
}
}
The Difference Between Regular Functions and Arrow Functions : read-me
You're not binding this to handleChange correctly in input tag at
<input onChange={this.handleChange} type="text" placeholder='COURSE CODE' value={this.courseCode} />
You should update onChange function to onChange={this.handleChange.bind(this)}
this is actually a binding that is made when a function is invoked, and what it references is determined entirely by the call-site where the function is called, not where it is declared. More at https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/this%20%26%20object%20prototypes/ch2.md
This is example code from a user named FrankerZ:
class ExampleComponent extends React.Component {
onBlur = async () => {
const results = await axios.get('myhttpendpoint');
this.setState({
results
});
}
render() {
return (
<div>
<form>
<span className="name"> Search Term: </span>
<input id="search-term" value={this.state.value} onBlur={this.onBlur} />
</form>
<div id="results">
{this.state.results}
</div>
</div>)
}
}
But essentially, my question is what if my axios.get returned an object with keys like
[{name: test1, data: datadatadata}, {name: test2, data: datatatatatata}]
How would I render each object in it's own span or own div?
I tried using a map such as
this.setState(results.map((item, index) => (<li key = {index}>{item.name}</li>)));
but it doesn't seem to work. I did this because it seems that React can't render object with keys and it told me to use an array instead which is what I tried.
You should do the map in the render method or any other method and call it in render, but not in set state.
Something like this
class ExampleComponent extends React.Component {
onBlur = async () => {
const results = await axios.get('myhttpendpoint');
this.setState({
results
});
}
render() {
return (
<div>
<form>
<span className="name"> Search Term: </span>
<input id="search-term" value={this.state.value} onBlur={this.onBlur} />
</form>
<div id="results">
{this.state.results.map(item => (<li key={item.name}>{item.name}</li>))}
</div>
</div>)
}
}
Markup should not go inside state.
Your render() should be like this.
<div id="results">
<ul>
{
this.state.results.map((item, index) => <li key = {index}>{item.name}</li>)
}
</ul>
</div>
make sure you initialize the state like this in constructor.
this.state = {
results : []
};
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
frank: [] // Basically, you will have a place in the state where you will save your data, which is empty.
}
componentDidMount(){
// This lifecycle method is used to fire requests.
// Fire your request.
// get the response.
// save the data only in the state, don't save ELEMENTS such like li.
const _result = [
{ id: 1, name: 'Frank1' },
{ id: 2, name: 'Frank2' }
];
this.setState({
frank: _result
});
}
render() {
const { frank } = this.state;
return (
<div>
<form>
<span className="name"> Search Term: </span>
<input id="search-term" value={this.state.value} onBlur={this.onBlur} />
</form>
<div id="results">
{/*HERE DO MAPPING*/}
{
frank.map((item, index) => <li key={index}>{item.name}</li>)
}
</div>
</div>)
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Follow the comments up there.
live demo: https://codesandbox.io/s/kxv6myl8wo
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>
);
}
}