setState() doesn't update value - javascript

I'm trying to update state of my component and it's not working. I have Main page container component => nested Navbar container component => nested NavItem UI component. Also on Main page I have AddNavItem container component which should add item to Navbar. It looks like this:
Main
|
|--NavBar
| |
| |--NavItem
|
|--AddNavItem
This is my Main page code:
import React, { Component } from 'react';
import Navbar from '../nav/Navbar'
import AddNavItem from '../fields/AddNavItem'
class Main extends Component {
state = {
items: [
{id: 1, name: 'Услуги', link: 'services'},
{id: 2, name: 'Цены', link: 'prices'},
{id: 3, name: 'Как это работает?', link: 'works'},
]
}
addNavItem = (item) => {
this.setState((state) => {
let newItems = [...state.items];
newItems.unshift(item)
console.log(newItems);
return {
items: newItems
}
});
}
render() {
return (
<div>
<Navbar items={ this.state.items }/>
<AddNavItem addNavItem={ this.addNavItem }/>
</div>
);
}
}
export default Main;
The problem is that I always get old array with 3 initial obj even after addNavItem is firing. <Navbar /> still gets array with 3 element. I have read about async setState and makes as describe in doc https://reactjs.org/docs/faq-state.html What have I doing wrong?
UPD: I changed code (unshift) to return array, not a length of the array. Console log shows me that I get new array with 4 obj
UPD: My complete code for all components:
Navbar
import React, { Component } from 'react';
import NavItem from './NavItem';
class Navbar extends Component {
state = {
items: this.props.items,
}
render() {
return (
<div className="centered navbar grey">
<h2>MyReact</h2>
<ul>
<NavItem items={ this.state.items }/>
</ul>
</div>
);
};
}
export default Navbar;
NavItem:
import React from 'react';
const NavItem = ({ items }) => {
const menuItemList = items.map((item) => {
return (
<li key={item.id}>
{ item.name }
</li>
);
})
return (
<div>
{ menuItemList }
</div>
)
}
export default NavItem;
AddNavItem:
import React, { Component } from 'react';
class AddNavItem extends Component {
state = {
id: Math.random(),
name: null,
link: null
}
handleInput = (e) => {
this.setState({
[e.target.id]: e.target.value,
});
}
handleSubmit = (e) => {
e.preventDefault();
this.props.addNavItem(this.state);
}
render() {
return (
<div className="centered flex-column-centered">
<form onSubmit={ this.handleSubmit }>
<h4 className="labelField">Название раздела:</h4>
<input
className="inputField"
type="text"
id="name"
placeholder="укажите название"
onChange={ this.handleInput } />
<h4 className="labelField">URL:</h4>
<input
className="inputField"
type="text"
id="link"
placeholder="укажите ссылку"
onChange={ this.handleInput } />
<button className="submitBtn">Добавить</button>
</form>
</div>
);
}
}
export default AddNavItem;

According to Mozilla docs
The unshift() method adds one or more elements to the beginning of an array and returns the new length of the array.
So when you try to change your state using this, it's returning the length of the array instead of the new array you're trying to update the state to. On top of that, this method is mutable as you are modifying the original array instead of returning a new copy of it. I would suggest changing your code to use the array.prototype.concat() method instead as it keeps your state immutable as it returns a new array instead of modifying the original array.
Try changing your method to this instead.
addNavItem = (item) => {
this.setState({ items: this.state.items.concat(item) });
}
Edit:
If you are using es6, you can use the spread syntax which also returns a new copy of the array.
addNavItem = (item) => {
this.setState({ items: [...this.state.items, item] });
}
Edit 2:
In your Navbar component, you're setting this.props.items to state when it initializes and then you don't update your state when this.props.items change.
You don't need state for this component, I would change your NavBar component to look like this:
import React from 'react';
import NavItem from './NavItem';
const Navbar = (props) => {
return (
<div className="centered navbar grey">
<h2>MyReact</h2>
<ul>
<NavItem items={ props.items }/>
</ul>
</div>
);
}
export default Navbar;
Edit 3:
If you want to keep your items inside state in your NavBar component, then you need to watch for updates being made to the items props on that component.
import React, { Component } from 'react';
import NavItem from './NavItem';
class Navbar extends Component {
state = {
items: this.props.items,
}
componentDidUpdate(prevProps) {
if (this.props.items!== prevProps.items) {
this.setState({ items: this.props.items });
}
}
render() {
return (
<div className="centered navbar grey">
<h2>MyReact</h2>
<ul>
<NavItem items={ this.state.items }/>
</ul>
</div>
);
};
}
export default Navbar;

let arr = [3,4,5]
console.log(arr.unshift(3,5))
addNavItem = (item) => {
let newItems = [...this.state.items];
newItems.unshift(item)
this.setState({
items: newItems
}
});
}
Now you are updating the array and adding the elements to the begining of it, which i assume you wanted and is why you picked upshift. you are then setting the sate equal to the updated array you have.

addNavItem = (item) => {
this.setState((prevState) => {items: [...prevState.items, item]})
}
This should work.

Related

Attempting to place data from an API onto a modal in React

I'm attempting to put data that I'm getting from an API onto a modal that will appear whenever a button is clicked.
How is this done? I'm able to use the data from the API without the modal, so I know it's not an issue with the syntax of my componentDidMount(). Not sure what the issue is and how it can be resolved.
import React from 'react';
import './App.css';
import Nav from './Nav';
import Meal from './Meal';
import meals from './Meals';
import Modal1 from './Modal'
function App() {
const mealArr = meals.map(item => <Meal food={item.food} picture={item.picture} type={item.id} />)
return (
<div className="content">
<Nav />
{mealArr}
<Modal1 isOpen={false}/>
</div>
);
}
export default App;
import React from 'react';
import Modal from 'react-modal';
class Modal1 extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
isLoaded: false
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json
})
})
}
render() {
const allItems = this.state.items;
let itemArr = allItems.map(item =>
<div>
<ul>
<li key={item.id}>{item.name}</li>
</ul>
</div>)
return (
<div>
<Modal>
{itemArr}
</Modal>
</div>
)
}
}
export default Modal1;
import React, {Component} from 'react';
import Modal1 from 'react-modal';
class Meal extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
}
this.handleClick = this.handleClick.bind(this);
this.turnOff = this.turnOff.bind(this);
}
handleClick() {
this.setState({isOpen: true})
}
turnOff() {
this.setState({isOpen: false})
}
render() {
return (
<div className="meal-container">
<h2>{this.props.type}</h2>
<h1>{this.props.food}</h1>
<img alt="" src={this.props.picture} />
<p className="steps-button" onClick={this.handleClick}>Steps</p>
<Modal1 className="modal-1" isOpen={this.state.isOpen}/>
</div>
)
}
}
export default Meal;
take a look at allItems, it's an empty array before you get the data from the API.
So, for the first render (before component did mount):
const allItems = this.state.items // ----> const allItems = []
mapping through an empty array will not produce any error and return another empty array, but when you map through an empty array, don't expect to have any item or item.name. so the itemArr is not as your expectation and cause the issue with rendering it.
to avoid from this issue, check your allItems to ensure that the data has arrived.
const allItems = this.state.items;
let itemArr = []
if (allItems.length > 0) {
itemArr = allItems.map(item => (
<div>
<ul>
<li key={item.id}>{item.name}</li>
</ul>
</div>
)
}
return (
<div>
<Modal>
{itemArr}
</Modal>
</div>
)

react does not update DOM

import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./index.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
text: "",
listItem: []
}
this.onChangeInput = this.onChangeInput.bind(this);
this.addToList = this.addToList.bind(this);
this.keyPress = this.keyPress.bind(this);
}
onChangeInput(event) {
this.setState({
text: event.target.value
});
}
addToList () {
let list = this.state.listItem;
list.push(this.state.text);
this.setState({
text: ""
});
this.setState({
listItem: list
});
}
deleteItem(event) {
console.log(event.target.remove());
}
keyPress (e) {
if (e.key === "Enter") {
this.addToList()
}
}
render() {
const listItem = this.state.listItem;
const list = listItem.map((val, i) =>
<li key={i.toString()} onClick={this.deleteItem}>
{val}
</li>
);
console.log(list);
return (
<div className="container">
<Input onChange={this.onChangeInput} value={this.state.text}
keyPress={this.keyPress}
/>
<Button addToList={this.addToList}/>
<ul>
{list}
</ul>
</div>
);
}
}
class Input extends Component {
render() {
return <input type="text" className="input" onChange={this.props.onChange}
onKeyPress={this.props.keyPress}
value={this.props.value}/>;
}
}
class Button extends Component {
render() {
return (
<button className="button" onClick={this.props.addToList}>
Add To List
</button>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
I'm very confused and couldn't find solution any where.
I'm new to react.
when I delete the list items, I delete them from DOM but is in state and I didn't delete it from state.
I put console.log(list) in render method and on every key press logs list in console
my question is why DOM does not re-render lists and output those where deleted from DOM and not from state?
and why it works for new list items and ignore those that deleted from DOM ?
react dosent pickup the update in the way you are doing it
deleteItem(event) {
console.log(event.target.remove());
}
although the item will be removed , but react dosent have any clue that happend, to notify react that the items has changed and it need to re-render, you need to call setState , then react calls the render method again,
deleteItem(e) {
const list= this.state.listItem;
list.pop() // remove the last element
this.setState({
list: list
});
}

Conditionally render elements in React JS?

I have a child component which i am looping over array to print out title and values. I have an event listener which renders a new row of title and values. I have a button in child component which i want do not want to be displayed by default but rendered only when i add new rows. So every 2 rows, there will be one button, for every 3, there will be 2 and so on.
This is my app.js file
import React, {Component} from 'react';
import './App.css';
import Child from './Child.js'
class App extends Component {
state = {
myArray: [
{ title: 'Hello', value: 'hello' }
]
}
addNewField = () => {
const myArray = [...this.state.myArray, { title: 'Hello', value: 'hello' }]
this.setState ({
myArray
})
}
render() {
return (
<div className = "App">
{
this.state.myArray.map ( (val,idx) => {
return (
<Child
key = {idx}
title = {val.title}
animal = { val.value }
/>
)
})
}
<button onClick = {this.addNewField}>Add Me</button>
</div>
)
}
}
export default App;
This is the setup for my Child Component:-
import React from 'react'
const Child = (props) => {
return (
<div>
<h3>{props.title}</h3>
<h4>{props.value}</h4>
<button>New Block!!</button>
</div>
)
}
export default Child
So basically the button in the Child component named new block should not be displayed by default but only after every click there after. Thank you.
Add a prop to the parent with the index of the map loop. Then add a flag so only children rendered after the first get the "New Block!!" button:
render() {
return (
<div className = "App">
{
this.state.myArray.map ( (val,idx) => {
return (
<Child
key = {idx}
title = {val.title}
animal = { val.value }
renderIndex = {idx}
/>
)
})
}
<button onClick = {this.addNewField}>Add Me</button>
</div>
)
}
}
import React from 'react'
const Child = (props) => {
return (
<div>
<h3>{props.title}</h3>
<h4>{props.value}</h4>
{props.renderIndex > 0 && <button>New Block!!</button>}
</div>
)
}
export default Child

"Cannot read property 'map' of undefined" within React, what's wrong here?

Trying to get my head around props so forgive me if its a silly mistake. I am trying to pass all of my data into one variable and pass that out into props (using {item.text} and {item.key}), however, my ".map" isn't picking up anything and there's a bunch of errors, what's wrong with my code?
The problem lays specifically here in this block of code
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
Here is the code in full
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoListt extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList(input) {
let listArray = this.state.list;
listArray.push(input);
var newItem = {
text: listArray,
key: Date.now()
};
this.setState(prevState => {
return {
list: prevState.list.concat(newItem)
};
});
this.setState({
list: listArray
})
}
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList(this.state.userInput)}>Press me</button>
<ul>
{this.testingSetup()}
</ul>
</div>
);
}
}
export default TodoListt;
You can use the spread operator to add to an existing array. Simply add a new object to the array in the state, and then clear the user input, ready for another item. Based on your code, here's a simple example of adding to a state list (haven't run myself, so just check for syntax errors and such):
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoList extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList() {
const { list, userInput } = this.state;
// Add item to state list using spread operator and clear input
this.setState({
list: [...list, {text:userInput, key: Date.now()}],
userInput: ""
});
}
render() {
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList()}>Press me</button>
<hr/>
{/* For each item in the list, render the contents */}
{this.state.list.map(item => (
<div key={item.key}>
<h3>{item.text}</h3>
<p>Time: {item.key}</p>
</div>
))}
</div>
);
}
}
export default TodoList;

React JS - How to access props in a child component which is passed from a parent component

In my react application, I am passing my data from parent to child as props. In my child component, I am able to see the data in props however when I try to access the data, I am getting an error saying "cannot read property of undefined".
I have written my child component like below-
Child Component-
import React from 'react';
import ReactDOM from 'react-dom';
import { setData } from '../actions/action'
import { connect } from 'react-redux'
import {
Accordion,
AccordionItem,
AccordionItemTitle,
AccordionItemBody,
} from 'react-accessible-accordion';
import 'react-accessible-accordion/dist/fancy-example.css';
import 'react-accessible-accordion/dist/minimal-example.css';
const ChildAccordion = (props) => {
console.log(props);
return (
<Accordion>
<AccordionItem>
<AccordionItemTitle>
<h3> Details:
{ props?
props.map(d =>{
return <span>{d.key}</span>
})
:
""
}
</h3>
<div>With a bit of description</div>
</AccordionItemTitle>
<AccordionItemBody>
<p>Body content</p>
</AccordionItemBody>
</AccordionItem>
</Accordion>
)
};
export default ChildAccordion
Parent Component-
import React from 'react';
import ReactDOM from 'react-dom';
import ChildAccordion from './ChildAccordion'
import { setData } from '../actions/action'
import { connect } from 'react-redux'
import {
Accordion,
AccordionItem,
AccordionItemTitle,
AccordionItemBody,
} from 'react-accessible-accordion';
import 'react-accessible-accordion/dist/fancy-example.css';
import 'react-accessible-accordion/dist/minimal-example.css';
class ParentAccordion extends React.Component {
componentWillMount() {
//call to action
this.props.setData();
}
getMappedData = (dataProp) =>{
if (dataProp) {
let Data = this.props.dataProp.map(d =>{
console.log(d);
})
}
}
render(){
const { dataProp } = this.props;
return (
// RENDER THE COMPONENT
<Accordion>
<AccordionItem>
<AccordionItemTitle>
<h3>Policy Owner Details:
{ dataProp?
dataProp.map(d =>{
return <span>{d.key1}</span>
})
:
""
}
</h3>
</AccordionItemTitle>
<AccordionItemBody>
<ChildAccordion {...dataProp} />
</AccordionItemBody>
</AccordionItem>
</Accordion>
);
}
}
const mapStateToProps = state => {
return {
dataProp: state.dataProp
}
};
const mapDispatchToProps = dispatch => ({
setData(data) {
dispatch(setData(data));
}
})
export default connect (mapStateToProps,mapDispatchToProps) (ParentAccordion)
I am using map function inside as my api response can be array of multiple objects.
Once you know what the prop that you're passing in is called, you can access it like so from within your child component: {props.data.map(item => <span>{item.something}</span>}
const Parent = () => {
return (
<Child data={[{ id: 1, name: 'Jim' }, { id: 2, name: 'Jane ' }]} />
);
}
const Child = (props) => {
return (
<ul>
{props.data.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
You are passing dataProp down to ChilAccordian as a prop. So in Child component you should access it using props.dataProp and do map on props.dataProp but not on props directly
ChildAccordian:
<h3> Details:
{ Array.isArray(props.dataProp) && props.dataProp.length > 0 ?
props.dataProp.map(d =>{
return <span key={d.id}>{d.key}</span>
})
:
""
}
</h3>
Also keep in mind that you have to add unique key to parent Jsx element when you generate them in loop like for loop, .map, .forEach, Object.keys, OBject.entries, Object.values etc like I did in the above example. If you don’t get unique id from the data then consider adding index as unique like
<h3> Details:
{ Array.isArray(props.dataProp) && props.dataProp.length > 0 ?
props.dataProp.map((d, index) =>{
return <span key={"Key-"+index}>{d.key}</span>
})
:
""
}
</h3>
Edit: If it is an object then do something like below and regarding using a method to generate jsx elements
getMappedData = dataProp =>{
if(props.dataProp){
Object.keys(props.dataProp).map(key =>{
return <span key={"Key-"+key}>{props.dataProp[key]}</span>
});
}else{
return "";
}
}
<h3> Details:
{this.getMappedData(props.dataProp)}
</h3>

Categories

Resources