React: state not updating on first click - javascript

I am working on a shopping cart sample.
On each click, I am adding an object of the project to the Cart array.
When I click the add cart button the first time, it doesn't update the Cart, but it updates on the second time.
Although, when I click the viewCart button in the render's return statement, it shows the accurate number of items in the cart.
See my code below:
I need to be able to see the accurate number of items in the cart without clicking the viewCart button. The products come from a local JSON file, so I don't think there is any problem with the loading of the JSON file.
constructor (props) {
super(props);
this.state = {
Cart: []
};
this.addItem = this.addItem.bind(this);
}
addItem(productKey) {
this.setState({ Cart: [...this.state.Cart, ProductsJSON[productKey]]})
console.log(this.state.Cart);
}
viewCart() {
console.log(this.state.Cart);
}
render() {
const AllProducts = ProductsJSON;
var Products = AllProducts.map((item) => {
return (
<div className="col-4" key={item.key} >
<a onClick={() => this.addItem(item.key)} className="btn btn-primary btn-sm">Add to Cart</a>
</div>
)
})
return (
<div className="container">
<div className="row">
{Products}
<a onClick={() => this.viewCart()} className="btn btn-primary btn-sm">viewCart</a>
</div>
</div>
)
}

Just because you don't see it in console.log doesn't mean it will not render correctly, the problem here is that setState is asynchronous, thus you will not see it if you console.log it right away (console.log will execute before state is updated), but if you still want to do log your cart, you can do:
this.setState(
{ Cart: [...this.state.Cart, ProductsJSON[productKey]] },
() => console.log(this.state.Cart)
)
setState accepts a function as a callback once the state has been updated.

Arber's solution makes sense but when I tried it, it produced a console warning saying:
State updates from the useState() and useReducer() Hooks don't support
the second callback argument. To execute a side effect after
rendering, declare it in the component body with useEffect().

Related

UseState not saving user input [duplicate]

This question already has answers here:
Push method in React Hooks (useState)?
(8 answers)
Closed 2 years ago.
Im working on a todo aplication in react using useState, im trying to save user input and then after they click submit push it to the listArray, later to display it...
I think im doing something wrong in the updateArray function, but I can seem to understand what.
import React, { useState } from "react";
function App() {
const listArray = [""];
const [list, updateList] = useState("");
function handleChange(event) {
const { name, value } = event.target;
updateList(value);
//console.log(list);
}
function updateArray() {
console.log(list);
listArray.push(list);
console.log(listArray);
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input name="entry" onChange={handleChange} type="text" />
<button>
<span onSubmit={updateArray}>Add</span>
</button>
</div>
<div>
<ul>
<li>{listArray[0]}</li>
</ul>
</div>
</div>
);
}
export default App;
There are several issues with your current code and I will briefly describe and provide a solution to fix them.
Your functions are working fine and as expected, but in a React application there are few ways to re-render a page or component and changing the local variable is not one of them. So you need to use a local state instead of local listArray variable. Since there is one state already you should either define another state or make your current state an object and put your component related states into it in one place I will go with the second approach, because it's more elegant and cleaner one.
const [state, setState] = useState({ list: [], input: "" });
After you define your state, you need to change them properly without effecting unnecessary ones. You just need to send the previous state, save it in the new one and only change the related state in each function. So with ES6 syntax, updating input state will look like this:
setState((prev) => ({ ...prev, input: value })); // immediate return "({})" of an object with iterating through the previous values "...prev" and updating input "input: value"
NOTE: You can read more about spread operator (...) here.
So your handle and updateArray function will look like this:
function handleChange(event) {
const { value } = event.target;
setState((prev) => ({ ...prev, input: value }));
}
function updateArray() {
setState((prev) => ({ ...prev, list: [...state.list, state.input] }));
}
onSubmit event will only work on forms or submit buttons so you need to change it to onClick. Also, to make the whole button catch the onclick action you need to set it on the button itself, instead of span element.
<button onClick={updateArray}> <!-- It's better to assign onclick action to an element with a function or arrow function to prevent it from running in the first render "(event) => updateArray(event)" -->
<span>Add</span>
</button>
And finally you need to map through the updated array of todo items in your JSX.
<ul>
{state.list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
Working Demo:
Save the current value into the state, and keep the list as well into the state so that it isn't cleared each render cycle.
import React, { useState } from "react";
function App() {
const [list, updateList] = useState([]);
const [currentValue, setCurrentValue] = useState()
function handleChange(event) {
setCurrentValue(event.target.value)
}
function handleClick() {
updateList([...list, currentValue])
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input name="entry" onChange={handleChange} type="text" />
<button type="button" onClick={handleClick}>
<span>Add</span>
</button>
</div>
<div>
<ul>
{list.map((res) => (
<li key={res}>{res}</li>
))}
</ul>
</div>
</div>
);
}
export default App;
Also, moving the onClick to the button makes more sense semantically (and even for the UX) but it's up to you.
listArray is cleared with every rerender.
You should store your data in state. So
const [listArray, setListArray] = useState([])
And updateArray should look like:
function updateArray() {
setListArray([...listArray, list]);
}
I guess, in updateArray function should be some logic to clear list
Notice how listArray will always go back to the default value when your app component re-renders (using useState may re-render the component)
I would instead make the string the user inputs a normal const and use useState for the array so the values in the array will be saved across re-renders

how to update FieldArray elements with redux store variable

I am using redux-form with a FieldArray.By default 1 element will be there in array and it is populated from JSON. I can add upto 3
elements in FieldArray component.
In below code, 'elementList'
property is coming from JSON and also I have store variables named
as'elements' and 'elementList'. I initialize these store variable with elementList
from JSON at first and then keep updating store variables when 'Add
Element' is clicked on. I can see store variables are updating
properly but on screen Field array elements are not updating.It may be because name property 'elementList' in FieldArray may refer to
JSON element.
Is it possible, if I can refer to store variables 'elementList' or 'elements' in name property
of 'FieldArray'. Please advice.
Main page
<div>
<FieldArray name="elementList" component={Elements}
props={this.props}/>
<button type="button" className="btn btn-primary"
onClick={event => this.addElement(elementDTO)}>Add Element
</button>
<br/>
</div>
addElement(elementDTO){
if(this.props.elements && this.props.elements!=undefined && this.props.elements.length >= 3){
return;
}
this.props.addReqElement(this.props.elements);
}
Field Array page
const elements= ({props, meta: {error, submitFailed}}) => {
const {fields} = props;
return (
{fields.map((element, index) => (
<div>
//Field definitions
</div>
))}
Thank you
Update:
Adding method from Redux Action and Reducer
export function addReqElement(childList) {
let state = store.getState()
let newChild=
state.requestReducer.DTOobj.requestDoc; //this is an empty element coming from backend with few properties and adding rest of the //proerties as below to create a new child
newChild.prop1 = null
newChild.prop2 = null
childList.push(newChild)
return (dispatch) => {
dispatch(setDoc(childList));
}
}
export function setDoc(payload) {
return {
type: ADD_DOC,
payload: payload
}
}
Update 2: I tried to remove push and used spread operator , but it did not work. I have inner array also, that is working as I am using different strategy. I take pull parent array ,it's index and update parent array with the new inner array. It works but parent array I am not getting how should I make it work. I tried to set the main array to the form props and render full page by dispatching an action but it did not work. Any suggestions plz?
From the main array page:
render() {
const {fields, parentArrayFromStore} = this.props;
return (
<div className="col-sm-12">
{fields.map((doc, index) => (
<div key={index}>
<div className="col-sm-12">
<FieldArray name={`${doc}.innerArrayList`} component={CustomInnerArrayComponent}/>
</div>
<div className="row">
<div className="col-sm-4">
<button type="button" className="btn btn-primary"
onClick={event => this.addInnerArray(index, parentArrayFromStore ,fields.get(index).innerArrayList)}>Add Printer
</button>
</div>
</div>
</div>
))}
</div>)
}
In Action class
export function addInnerArray(index, parentArrayFromStore, innerArrayList) {
let newInnerItem= {};
newInnerItem.prop1 = null
newInnerItem.prop2 = null
innerArrayList.push(newInnerItem)
parentArrayFromStore[index].innerArrayList = innerArrayList;
return (dispatch) => {
dispatch(setParentArray(parentArrayFromStore));
}
}
export function setParentArray(payload) {
return {
type: ADD_DOC,
payload: payload
}
}
Hi the issue is with the push statement in your function when updating states in the constructor or reducer use concat or spread operator[...]>
I have made a sample over here
please check
onAddItem() {
let list = [...this.state.items, {text:`Check 1`}];
this.setState({items:list});
}
in your case you can do the following
let arr = [...childList, newChild]
then dispatch the arr
dispatch(setDoc(arr));

React onclick function produces increments state instead of changing it

I am trying to implement a function that sets a property of the state, "changedMarkup" on a button click event.
Constructor
constructor() {
super();
this.state = {
value: 0,
changedMarkup: 0
};
}
Render
render() {
const { classes } = this.props;
return (
<Paper className={styles.root}>
<Tabs
value={this.state.value}
onChange={this.handleChange}
variant="fullWidth"
indicatorColor="primary"
textColor="primary"
aria-label="icon label tabs example"
>
<Tab onClick={() => this.changeMarkup(0)} icon={<TrendingUpIcon />} label="TRENDING" />
<Tab onClick={() => this.changeMarkup(1)} icon={<ScheduleIcon />} label="NEW" />
<Tab onClick={() => this.changeMarkup(2)} icon={<WhatshotIcon />} label="HOT" />
</Tabs>
</Paper>
);
}
changeMarkup function
changeMarkup = (markup) => {
this.setState({
changedMarkup: markup
})
console.log("markup", this.state.changedMarkup);
}
Expected behavior
Log statement when the first tab is clicked: markup 0
Log statement when the second tab is clicked: markup 1
Log statement when the third tab is clicked: markup 2
Resulting behaviour
The "changeMarkup" property produces unexpected values. I can't seem to find an exact common pattern but it seems to be increment from 0 to 2 and decrements back to 0 with continuous clicks irrespective of the tab clicked
Any help is appreciated.
See: https://reactjs.org/docs/react-component.html#setstate
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
setState is an async operation, it won't be complete at the time you do your console logging. You can supply a callback after it has been updated:
this.setState({changedMarkup: markup}, () => {
// Do your logging here!
});
Because this.setState({}) is async operation so if you want updated value log than you can do it in two ways.
1. use callback function like this
this.setState({
//set your state
}, () => {
console.log('your updated state value')
})
2. in render function like this
render(){
console.log('your state')
return()
}

hide and show names using react hooks

I am new to react hooks,
in my app initially, all the names of the users should be hidden.
later when I click each user it should show the name.
so I used the show and setshow.
when I tried to print the values in the browser, I don't see it.return(<div>{show}users Data{setShow}
I wrote click for each user but I am not sure how to hide and show.
there will be millions of users in my app, so whats the best way to hide and show the name when I click each
providing my code snippet and sandbox below
https://stackblitz.com/edit/react-t1mdfj?file=index.js
import React, { Component, useState } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import "./style.css";
function DataClick(){
const [show, setShow]= useState(false);
function showItem(e){
console.log("e--->", e.target);
setShow(true);
}
return(<div>{show}users Data{setShow}
<div onClick= {showItem}
//onClick={()=>setShow(true)}
>user1</div>
<div>John</div>
<div onClick= {showItem}
//onClick={()=>setShow(true)}
>user2</div>
<div>Mike</div>
<div onClick= {showItem}
//onClick={()=>setShow(true)}
>user3</div>
<div>Mike3</div><div onClick= {showItem}
//onClick={()=>setShow(true)}
>user4</div>
<div>Mik4e</div><div onClick= {showItem}
//onClick={()=>setShow(true)}
>user5</div>
<div>Mike5</div><div onClick= {showItem}
//onClick={()=>setShow(true)}
>user6</div>
<div>Mike6</div><div onClick= {showItem}
//onClick={()=>setShow(true)}
>user7</div>
<div>Mike7</div><div onClick= {showItem}
//onClick={()=>setShow(true)}
>user8</div>
<div>Mike8</div>
</div>);
}
render(<DataClick />, document.getElementById("root"));
#computer cool, Please see the below implementation to show and hide the usernames when user id is clicked, this is an updated code of #JMadelaine's implementation. Hope this will help. (In #JMadelaine's implementation one drawback I found is, once the username is shown it was not hiding back when clicking on the user id again because he was always setting the state to true onClick), please see the below code.
import React, { Component, useState } from "react";
const UserItem = ({ user }) => {
const [isNameShown, setIsNameShown] = useState(false)
const handleChange = () => {
setIsNameShown(prevState => !prevState)
}
return (
<div onClick={handleChange} >
<div>{user.id}</div>
{isNameShown && <div>{user.name}</div>}
</div>
)
}
function DataClick() {
const users = [
{
id: 'user1',
name: 'John',
},
{
id: 'user2',
name: 'Peter',
},
]
return (
<div>
{users.map(user => <UserItem user={user} />)}
</div>
)
}
export default DataClick;
Here the handleChange function sets the value of the state using the previous value by passing a callback function instead of direct value, so this callback function will get the previous state as argument and returns the inverted value, so if the user is opened, it will close, if the user is closed it will open.
EDIT:
Below is the explanation of the code setIsNameShown(prevState => !prevState)
setIsNameShown or any function that is returned by the useState hook can be written in the below ways.
1st way example:
setIsNameShown(false)
in this, you are directly passing the value to be set irrespective of the previous value.
2nd way example:
setIsNameShown((prevStateVariable) => {
return !prevStateVariable
})
or more concisely this same can be written as
setIsNameShown(prevStateVariable => !prevStateVariable)
in this case, the function setIsNameShown accepts the callback function which receives the previous state (to which the function is related to) as an argument.
So in cases where you need to set values to state which depends on the previous state value, use the callback function instead of directly providing the value.
The useState hook is just like a class component's state and setState. In your case show is the state variable that has a true or false value, and you can change this value using the function setShow.
To conditionally show the users' names, you should use the value of show like so:
return(
<div>
<div onClick={() => setShow(true)}>user1</div>
{show && <div>John</div>}
<div onClick={() => setShow(true)}>user2</div>
{show && <div>Mike</div>}
</div>
)
However, I don't think that is what you want. It sounds like you want to show only the name of the user that was clicked, and in the current state, when you click one user, all usernames will show because they all depend on the same state variable show.
You should create a separate component that contains the logic to show and hide a username, and map each user to that component. That way, each user has their own show state.
EDIT:
I've updated your code with the below:
// each user gets mapped to this component
const UserItem = ({user}) => {
// that means each user has their own 'isNameShown' variable
const [isNameShown, setIsNameShown] = useState(false)
return (
// if you click this div, this user's isNameShown value will be set to true
<div onClick={() => setIsNameShown(true)}>
// user.id is the id of the user from props
<div>{user.id}</div>
// only show this user's name if this user's isNameShown is true
{isNameShown && <div>{user.name}</div>}
</div>
)
}
function DataClick(){
// we just create some example users here for testing
const users = [
{
id: 'user1',
name: 'John',
},
{
id: 'user2',
name: 'Peter',
},
]
return(
<div>
// map each user to a UserItem, passing the user as a prop
{users.map(user => <UserItem user={user} />)}
</div>
)
}
I don't know what your users data structure looks like, so you should change it accordingly.
Something to learn here is that, if you find yourself repeating code over and over or copy pasting things again and again, then there is usually a better way of achieving what you want. Your list of users was basically copied and pasted over and over again, so you should create a single UserItem component instead.
There may be on issue with this code, which is that once a username is visible, you cannot hide it again, but I'll let you figure out how to do that if that is your intention.

ReactJS - array in this.state is updated correctly on delete but result isn't rendered until later state changes?

I've seen variations of this question posted but none match my problem. I am displaying an array of text fields and want to be able to delete one of them. When the delete button next to any text field is clicked, the last item in the array disappears. However, I've checked this.state and the correct item was deleted, just the wrong one was taken from the rendered array. When I click a button that hides the text fields and then un-hide, the array is fixed and shows the correct item deleted. Why is this happening? The state is updated with what I want removed, but it seems like it renders something other than the state. I've tried using this.forceUpload(); in the setState callback but that does not fix it.
I've included some relevant snippets from the code and can include more if needed.
export class Questions extends React.Component {
constructor(props) {
super(props);
this.state = {
...
choices: []
...
};
}
deleteChoice = (index) => {
let tempChoices = Object.assign([], this.state.choices);
tempChoices.splice(index, 1);
this.setState({choices: tempChoices});
};
renderChoices = () => {
return Array.from(this.state.choices).map((item, index) => {
return (
<li key={index}>
<TextField defaultValue={item.text} style={textFieldStyle} hintText="Enter possible response..."
onChange={(event) => { this.handleChoice(event, index); }}/>
<i className="fa fa-times" onClick={() => { this.deleteChoice(index); }}/>
</li>
);
});
};
render() {
let choices = (
<div>
<div className="choices">Choices</div>
<ul>
{this.renderChoices()}
<li>
<RaisedButton label="Add another" style={textFieldStyle} secondary
onClick={() => { this.addChoice(); }}/>
</li>
</ul>
</div>
);
return (
{choices}
);
}
}
Thanks for any help, this is wicked frustrating.
You need to use a key other than the array index in your renderChoices function. You can read more about why this is the case in React's docs:
https://facebook.github.io/react/docs/multiple-components.html#dynamic-children
When React reconciles the keyed children, it will ensure that any
child with key will be reordered (instead of clobbered) or destroyed
(instead of reused).
Consider using item.text as the key or some other identifier specific to that item.

Categories

Resources