Delay in props update in a child component in React - javascript

In my parent component, I have a button that takes the object of the selected item and renders in input fields in a child component.
The parent component renders,
<h4 className="table-title">Status</h4>
<div className="table-box">
{
props.table_data.map((item, idx) => {
if (item.profiledata) {
return (
<div>
<h4 className="profile-title">
<Button className="release-option-button" onClick={() => this.handleOpen('remove-release-profile', item)}>Remove Profile</Button>
</h4>
<Table columnWidths={[140, 220, 260, 140, 310]} numRows={item.profiledata.length}>
...
</Table>
</div>
)
}
})
}
</div>
Whenever I click the button release-option-button, I setState the currRelease to item.
Then I render,
<EditDialog open={isOpen.EDIT_RELEASE} closeDialog={handleClose} selected_item={currRelease} />
and pass currRelease as props.selected_item.
And in my child component,
const init_fields = {
release: props.selected_item.release,
manager: props.selected_item.manager,
start_date: props.selected_item.start,
end_date: props.selected_item.end,
profiles: props.selected_item.profiledata,
}
const EditDialog = (props) => {
// State Hooks
const [fields, setFields] = useState(init_fields)
...
and render these values in input fields
<h5>Version</h5>
<p>
<input
value={fields.release}
className="input-field"
type="text"
/>
</p>
However, whenever I select an item, it renders the fields of the previously selected item, instead of the current one. It seems like there is a delay in updating props.
Is there a way to solve this?

You're generating the local state for EditDialog from passed in props. With useState, the argument you pass is only the initial value for the state. If you want to update state based on props changing, you can use useEffect which can respond to prop changes.
useEffect(() => {
setFields({
release: props.selected_item.release,
manager: props.selected_item.manager,
start_date: props.selected_item.start,
end_date: props.selected_item.end,
profiles: props.selected_item.profiledata,
});
}, [props.selected_item])
However, you might want to reconsider using state to store props. It's a React anti-pattern and can lead to discrepancies like this.

Related

React js doenst re-render after updating state

i created a parent component that gathers a bunch of child components:
Expenses - Parent,
ExpenseItem - child.
i defined the first value of ExpenseItem by hard coding inside of Expenses
i entered the dynamic value to the ExpenseItem component element and then used "props" parameter to get the data from ExpenseItem to Expenses.
function Expenses() {
const Data = [{
title: `מסרק`,
date: Date(),
amount: 2.95,
},
]
return (
<div>
<ExpenseItem
title={Data[0].title}
date={Data[0].date}
amount={Data[0].amount}
/>
</div>
);
}
now, i want to update the values through a button "edit" in ExpenseItem Component.
when i do update values through useState and console log them i see the updated values,
but, the component doesnt re-renders so i see the prev value on the screen. though if i try to hard code the value it does re-renders and changes the value on the screen.
function ExpenseItem(props) {
const [title, setTitle] = useState(props.title);
const [date, setDate] = useState(props.date);
const [amount, setAmount] = useState(props.amount);
const clickHandle = () => {
console.log(title);
setTitle("חתול")
console.log(Date());
setDate(date)
console.log(amount);
setAmount("222")
}
return (
<div className="ExpenseItem">
<div className="ExpenseItem_EditFunctions">
<p className="ExpenseItemDate">{props.date}</p>
<div className="ExpenseItem_EditFunctions_Icons">
<span className="material-icons delete">delete</span>
<span className="material-icons edit" onClick={clickHandle}>
edit
</span>
</div>
<div className="ExpenseItem_MainContent">
<h3 className="ExpenseItemTitle">{props.title}</h3>
<p className="ExpenseItemAmount">₪{props.amount}</p>
</div>
</div>
</div>
);
}
It's because you are using props to display values you need to display state values instead of them.You need to replace all the prop values with corresponding state values to make this code work properly. Just replace {props.date} to {date} {props.title} to {title} and {props.amount} to {amount}

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

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.

Checkbox in react functional component causes rerender

I'm using gatsby and have a functional component that loops through some data to create radio button group with an onchange event and checked item. When i update the state whole page component rerenders. i though adding memo was meant to stop this but it doesn't seem to work.
here is the code
const BikePage = React.memo(({ data }) => {
console.log("page data", data)
const [selectedColor, setColor] = useState(data.bike.color[0])
const onColorChange = e => {
setColor(e.target.value)
}
return (
<div>
{data.treatment.price.map((value, index) => {
return (
<div>
<input
id={`bike-option-${index}`}
name="treatment"
type="radio"
value={value}
checked={selectedColor === value}
onChange={e => onColorChange(e)}
/>
<label
htmlFor={`treatment-option-${index}`}
>
{value}
</label>
</div>
)
})}
<Link
to="/book"
state={{
bike: `${data.bike.title}-${selectedColor}`,
}}
className="c-btn"
>
Book Now
</Link>
</div>
)
});
If you update the state the component will re-render, that's fundamentally how react works. the memoised data prop is coming from outside of the component.
"If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result" react.memo
You're not changing the incoming props though, you're changing the state
Side note: i imagine that on changing this value you probably want to be changing the state of the data on the server through some means also ( REST POST / graphql mutation). Subsequent refetches of this data would re-render this component as well. It depends what you're trying to ultimately achieve.

Using React, JSX: how to retrieve input value, post it as an element, and then clear it to repeat

This is an assignment for school. It involves using React and JSX to create an input field and a submit button. When the button's clicked, the input value should render as an element to the body. I was able to create it for the first click, but don't know how to repeat it.
If you look at the code below, you'll see that when user types, handleChange changes state of input and when the button's clicked, handleClick changes the boolean state of the button (called 'post'). If post is true, the input along with a timestamp is rendered as a heading.
The problem is that after the render, the input isn't cleared. If the user changes input and clicks button again, it updates the heading with a new timestamp and new input instead of adding another heading.
I've tried changing back state for input and post in handleClick, handleChange, componentDidMount, and componentDidUpdate. But that repeatedly calls setState and I get an error message 'maximum update depth exceeded.'
So, what I want it to do is post a new heading of the input value every time the user clicks the post button. I also want it to clear the input/placeholder text.
import React, { Component } from 'react';
import './App.css';
import Firstposts from './firstposts.jsx';
class App extends Component {
constructor(props) {
super(props)
this.state = {
input: "",
post: false
}
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleChange(event) {
this.setState({ input: event.target.value });
}
handleClick() {
this.setState({
post: true
})
}
render() {
let timestamp = new Date();
return (
<div className="container">
<div className="panel">
<img height="100 px" src="https://marketing.twitter.com/content/dam/marketing-twitter/brand/logo.png" alt=""></img>
<h1>Chirper</h1>
</div>
<div className="body">
<input
placeholder="type your message here"
onChange={this.handleChange}
/>
<button
onClick={this.handleClick}
>Post</button>
<h2>Log</h2>
{<Firstposts />}
{this.state.post ?
<div>
<h3>{timestamp.toString()}</h3>
<h4>{this.state.input}</h4>
</div>
:
<div />
}
</div>
</div >
);
}
}
export default App;
Update your handleClick method to set posts to an array of posts, instead of a boolean:
handleClick() {
this.setState({
posts: [
...this.state.posts,
this.state.input
]
})
}
This will add the value of this.state.input to the end of this.state.posts, preserving all previous posts.
You can update this further to clear the value of the input field:
handleClick() {
this.setState({
posts: [
...this.state.posts,
this.state.input
],
input: '' // add this line to clear your input field when a new post is submitted
})
}
Also, make sure to give your <input> element a value of this.state.input:
<input
value={this.state.input}
placeholder="type your message here"
onChange={this.handleChange}
/>
Without this, you will not be able to programmatically update the value of the <input> field by using setState. You can read more on uncontrolled components in React.
Then, update your render method to map over this.state.posts and render each one:
{this.state.posts.map(post => (
<h4>{post}</h4>
))}
You can read more on rendering lists in React.

Categories

Resources