state not updating in child component - react js - javascript

I am passing a state from one component to another component. However, when a state updates in the parent component, the child component doesn't update. Is there something i am doing wrong in my code below .
As shown below, in the Patient.JS i pass the state to the AddPatient.JS. But when i the state of age is updated in the Patient.JS, it doesn't update in the AddPatient.JS.
How can i handle this ?
Patient.JS
state = {
info = {
age = '',
name = ''
}
}
handle_age(event)
{
this.setState({ age:event.target.value}, () => {
console.log('age', this.state.age)
<Modal>
<ModalHeader>
</ModalHeader>
<ModalBody>
<AddPatient
addNewPatient = {this.state.info}
/>
</ModalBody>
<ModalFooter>
<Button variant="contained" className="text-white" onClick={() => this.addPatient()}>Save</Button>
</ModalFooter>
</Modal>
AddPatient
const AddPatient = ({ addNewPatient}) => (
<FormGroup>
<Label for="className">Age</Label>
<Input
type="number"
name="sub_total"
id="sub_total"
placeholder=""
value = {addNewPatient.age}
/>
</FormGroup>
);

Replace your handle_age function with this:
handle_age(event)
{
let info = {...this.state.info}
info.age = event.target.value
this.setState({
info: {...info}
})
}
Explanation: When updating components, React JS does a shallow check on the state keys and values. What it means, is that it will detect a change when a key/value pair changes or updates, but won't detect a change in deeply nested objects within the state. Example:
this.state = {
visible: true,
users: {
{
id: 1,
name: 'john'
},
{
id: 2,
name: 'doe'
}
}
In the above code, React will detect a change in the visible key but not in the users object because it is a deeply nested object.
Now since you are using only the age of the patient, just pass the age as patientAge={this.state.info.age} and use it directly.

In your handler you are assigning the input value to state.age and not inside state.info.age
handle_age(event) {
const age = event.target.value;
this.setState(prevState => ({
info: { ...prevState.info, age }
}), console.log('age', this.state.info.age);
}

you are passing the state as a props to the child component so you are now entering the concept of when does change props will trigger a rerender.
if the prop is a primitive value (eg string or integer etc..) it will cause a rerender on every value change.
if the props are an array or like in your example an object, it will cause a rerender only when there's a new reference for that object or array
sense your only changing a property of an object and react dose shallow comparison of state and props, changing "age" will not trigger rerender in child.
you can do:
setState to entire info every time which will change his reference and trigger rerender at child
or
pass age and name separately as props to your child component
from what I see you are only using age in your child component so I would recommend option 2
like this:
<AddPatient
addNewPatient={this.state.info.age}
/>
const AddProduct = ({ addNewPatient}) => (
<FormGroup>
<Label for="className">Age</Label>
<Input
type="number"
name="sub_total"
id="sub_total"
placeholder=""
value = {addNewPatient}
/>
</FormGroup>
);

Related

Why isn't child component re-rendered even though parent state is updated using hooks

I am a newbie in React and have got a problem using hook.
I am going to build a basic form as a child component, but I am wondering why input element on the child form is not rendered when changing its value.
Here is my code.
'use strict';
function SearchForm({form, handleSearchFormChange}) {
return (
<div className="card border-0 bg-transparent">
<div className="card-body p-0 pt-1">
<div className="form-row">
<div className="col-auto">
<label className="mr-1" htmlFor="number">Number:</label>
<input type="text" className="form-control form-control-sm w-auto d-inline-block"
name="number" id="number" value={form.number} onChange={handleSearchFormChange}/>
</div>
</div>
</div>
</div>
);
}
function App() {
const formDefault = {
number: 'Initial Value'
};
const [form, setForm] = React.useState(formDefault);
const handleSearchFormChange = (e) => {
setForm(Object.assign(form, {[e.target.name]: e.target.value}));
console.log('Handle Search Form Change', e.target.name, "=", e.target.value);
};
return (
<React.Fragment>
<div>Number : {form.number}</div>
<SearchForm form={form} handleSearchFormChange={handleSearchFormChange} />
</React.Fragment>
);
}
const domContainer = document.querySelector('#root');
ReactDOM.render((
<React.Fragment>
<App/>
</React.Fragment>
), domContainer);
I defined an onChange event handler for 'number' element in parent component and tried to transfer the value to child component using props.
The problem is that when I am going to change 'number', 'number' input element is not changed at all. (Not rendered at all). So that input element has always 'Initial Value'.
Could you advise on this?
And I'd like to know if this approach is reasonable or not.
Thanks in advance.
One of the philosophies of react is that state is immutable. Ie, you don't change properties of the existing state object, but instead you create a new state object. This allows react to tell that the state changed by a simple === check from before and after.
This line of code mutates the existing form object, then passes that into setForm:
setForm(Object.assign(form, {[e.target.name]: e.target.value}));
So react compares the object before setForm and after, and sees that they're the same object. Therefore it concludes that nothing changed, and so the component does not rerender.
Instead, you need to make a copy of the object and make your changes on the copy. If you're used to Object.assign, that can be accomplished by putting an empty object as the first argument to Object.assign:
setForm(Object.assign({}, form, {[e.target.name]: e.target.value}));
Alternatively, the spread syntax is a convenient way to make a shallow copy of an object:
setForm({
...form,
[e.target.name]: e.target.value
})
Try replacing setForm(Object.assign(form, {[e.target.name]: e.target.value})); with
setForm(prevState => ({
...prevstate,
[e.target.name]: e.target.value
}));

React Hooks change single value of object with multiple keys without repetition

I'm trying to find a neater way to handle this pattern I keep coming across with react when handling changes for form fields.
For each element of my form object that I handle a change in value for I find myself replicating this pattern quite a bit with the setter function of useState(). I've tried a couple of things like creating shallow copies of the formState and mutating that but the only way I can really get things to work is with the bellow pattern which feels a little repetitive.
const handleTitle = evt => {
props.setFormState({
title: evt.target.value,
bio: props.formState.bio,
formExpertise: props.formState.formExpertise,
formExpertiseYears: props.formState.formExpertiseYears
});
};
If you want to include this.props.formState you can spread the object into the new state. Further, you can use the input’s name as the state key so you don’t have to rewrite this for every input:
props.setFormState({
...this.props.formState, // copy props.formState in
[evt.target.name]: evt.target.value // use input name as state key
});
Suggestion:
You might consider moving the state merging up into the parent component:
// parent component
const [formState, setFormState] = React.useState({});
const onFieldChange = (field, value) => {
setFormState({
...formState,
[field]: value
});
}
return (
<MyFormComponent
formState={formState}
onFieldChange={onFieldChange}
/>
);
Each input can then invoke onFieldChange with the field name and value without concerning itself with the rest of the state:
function MyFormComponent ({onFieldChange}) {
const handler = ({target: {name, value}}) => onFieldChange(name, value);
return (
<div>
<input name="title" value={formState.title} onChange={handler} />
<input name="bio" value={formState.bio} onChange={handler} />
<input name="expertise" value={formState.expertise} onChange={handler} />
</div>
);
}

React - Dynamically set state without hardcoding key field

In ES6, ComputedPropertyName allows us to do things like use a variable as a key, which in turn means we can set state dynamically. However, if you look around at examples of setting state dynamically, they tend to all have one thing in common -- the state key's name is hardcoded. As an example:
class Input extends React.Component {
state = { state1: "" };
handleChange = event => {
const {
target: { name, value }
} = event;
this.setState({
[name]: value
});
};
render() {
return (
<div>
<label>
<input
type="text"
name="state1"
value="new value"
onChange={this.handleChange}
/>
</label>
</div>
);
}
}
This works because we have a state key called "state1", as seen in the line state = { state1: "" };, and we are hardcoding name in the input field to be that state key, as seen in the line name="state1".
I do not like this solution, because it means I now have to keep track of state.state1" in more than one location. If I were to refactorstate.state1to instead bestate.state2, I would have to go findname="state1"1 and update that to read name="state2". Instead of worry about that, I am wondering if there is a way to set state dynamically without hardcoding this state key. That is, I'm looking to change
<input
type="text"
name="state1"
value="new value"
onChange={this.handleChange}
/>
Into something like:
<input
type="text"
name={this.state.state1.keyname}
value="new value"
onChange={this.handleChange}
/>
Obviously the above doesn't work because keyname is undefined, but the intention here is that name can take on the value of "state1" without me having to hardcode it. How can this be achieved?
You can have an array with objects with keys type and name which you can use to set the initial state and render the inputs dynamically. This way you'll only have to change the value once in the array. You can do something like this.
Here is a codesandbox
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
constructor() {
super();
this.arr = [
{ type: "text", name: "state1" },
{ type: "password", name: "state2" }
];
// set the state keys dynamically from this.arr
this.state = this.arr.reduce((agg, item) => {
agg[item.name] = "";
return agg;
}, {});
}
handleChange = event => {
const {
target: { name, value }
} = event;
this.setState(
{
[name]: value
}
);
};
renderInputs = () => {
return this.arr.map((item, i) => (
<div key={i}>
<label>
<input
type={item.type}
name={item.name}
value={this.state[item.name]}
onChange={this.handleChange}
/>
</label>
</div>
));
};
render() {
const inputs = this.renderInputs();
return <div>{inputs}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Hope this helps !
There is the new useReducer() that comes with hooks and context. Check this out i think that is the best pattern to solve your issue. https://reactjs.org/docs/hooks-reference.html.

Get value from an input in an array on button click ReactJs

I am relatively new to ReactJs.I am learning react while I am trying to create a real world app. Here is something I cannot solve.
I have a repeated component that has one input and one button.
everytime the button is clicked, the value of the input will be used in one function.
In Angular I do not have to worry about how to passing those value since in ngFor we can directly assign the value from the ngModel. But there is no such concept in React.
betOnTeam = (_id, coins) => {
return;
};
{this.teamList.map(team => (
<div key={team._id}>
<input type="number" min="100" max="5000" />
<button type="button"
onClick={() => this.betOnTeam(team._id,//value from the
input above)}>
</div>
))}
So basically I Have a function ready to receive an Id and how many coins the user bet.
And As we can see from the picture, I have many inputs which should contain the value of how much coins the user put for a specific team.
each button will trigger this betOnTeam function and will pass the unique Id of the team, and the number coins the user bet.
How can I set states for all thoese teams since they are all dynamic, it could be 5 teams or 100 teams. Is it any way to do it dynamically?
e.g. user input 5000, when he click the button, the id and the value will be passed into the function betOnTeam.
I hope this clarified my question.
==================================
Thanks for all the input from you guys.
I have make it working combine with all your suggestions.
So Here is what I do:
betOnTeam = (event, id) => {
console.log(event.target[0].value, id);
return;
};
{this.teamList.map(team => (
<form key={team._id} onSubmit={(e) => this.betOnTeam(e,team._id)}>
<input type="number" min="100" max="5000" />
<button type="submit">
</form >
))}
Seems like you're really close. I think this ultimately comes down to how you want to construct your components. There is an easy way to do this (the more React) way, and there is a hard way.
The easy way is to split the mark-up created inside the .map() into its own component. You will have an individual component for each team, thus the state is encapsulated to its own component. By doing this you can effectively keep track of the inputs for each team.
Consider this sandbox for example: https://codesandbox.io/s/dazzling-roentgen-jp8zm
We can create a component for the markup like this:
Team
import React from "react"
class Team extends React.Component {
state = {
betValue: 100
};
handleOnChange = e => {
this.setState({
betValue: e.target.value
});
};
handleOnClick = () => {
this.props.betOnTeam(this.state.betValue, this.props.id);
};
render() {
const { id } = this.props;
const { betValue } = this.state;
return (
<div key={id}>
<input
type="number"
min="100"
max="5000"
value={betValue}
onChange={this.handleOnChange}
/>
<button onClick={this.handleOnClick} type="button">
Bet
</button>
</div>
);
}
}
export default Team;
So from a purely jsx standpoint, the markup is the same, but now it is contained inside a class-component.
Now we can keep track of the inputs in a controlled manner.
When we're ready to place the bet, the value is stored in the
individual component state.
We pass down two properties to each Team component, the team_id and
betOnTeam function. The team_id can be accessed using this.props.id and likewise we will pass it into this.props.betOnTeam() when required.
Main Component
import React from "react"
import Team from "./Team"
class App extends React.Component {
teamList = [
{ team_id: 1, name: "TSM" },
{ team_id: 2, name: "SKT" },
{ team_id: 3, name: "CLG" }
];
betOnTeam = (betValue, teamId) => {
console.log(betValue);
console.log(teamId);
};
render() {
return (
<div>
{this.teamList.map(team => (
<Team id={team.team_id} betOnTeam={this.betOnTeam} />
))}
</div>
);
}
}
So the .map() renders a Team component for each team and passes in their respective ids and the betOnTeam function as props. When the button inside the component is clicked, we can pass back up the values stored in the Team Component to execute betOnTeam.
onClick={this.betOnTeam(form._id,value)}
Don't execute this.betOnTeam right from the start, you're actually setting the click handler to the returned result of this.betOnTeam(form._id,value). In React, it's not quite the same as in Angular. For this, you need to set it equal to a function that does that. Like this:
onClick={() => this.betOnTeam(form._id,value)}
Hope this helps.
1. this.betOnTeam = (_id, value) => { ... }
2. constructor(props) { this.betOnTeam.bind(this) }
3. onClick = { () => this.betOnTeam(form._id, value)}
Well if you use onClick = { this.betOnTeam(form._id, value) }, then the code will be executed first, and in betOnTeam function, you will not use 'this' operator.
But if you use the above methods, then you can use 'this' in the function and get the good results.
And your code has some bugs to fix,
{this.array.map(form => (
<div key={form._id}>
<input name={`editform{form._id}`} type="number" min="100" max="5000" onChange={(e) => this.changeNumber(e, form._id) }/>
<button type="button"
onClick={this.betOnTeam(form._id,value)}>
</div>
))}
And in changeNumber function, you should use setState({}) function to set the value to the state, and in betOnTeam function, you can use the state you have already set.
The code must be like this, or otherwise you can use ref but it is not formally encouraged to use ref.
Totally, you should use ControlledComponent. That's the target.
I hope you to solve the problem.

React Native - Update Parent State in Child with Dynamic Key

I am very new to both Javascript and React Native, and I am trying update a parent's state by using a callback function using a dynamic key to avoid writing multiple functions.
In the parent component, I pass this function to the child to child to update the parent's state based on user text input. This code achieves the desired result.
In Parent:
_setAge = (value) => {
this.setState({age: value})}
<ChildComponent name = 'Update Age' _setAge = { this._setAge.bind(this) } />
In Child:
//Other child code
<TextInput onChangeText = { (input) => {this.props._setAge(input)} }
//Etc.
However, I am looking for a way to pass a desired state key from the parent to the child to update the state dynamically. I have tried the following:
In Parent:
const ageKey = 'age'
_setAge = (value, stateKey) => {
this.setState({ [stateKey]: value })}
<ChildComponent name = 'Update Age' _setAge = { this._setAge.bind(this) } stateKey = ageKey } />
In Child:
//Other child code
<TextInput onChangeText = { (input) => this.props._setAge(input, this.props.stateKey)
//Etc.
However this doesn't work. My current work around is writing 6 functions for my 6 child components, each updating the desire state. However, while this would work for my basic app, I am looking for a way that is more scalable for future projects. Thank you!
In Parent
Instead of passing stateKey in props on child key directly pass the state key in onChageText method in child. the code would look like this->>>>
_setAge = (value, stateKey) => {
this.setState({ [stateKey]: value })}
<ChildComponent name = 'Update Age' _setAge = {this._setAge} /> // providing no statekey here
Inside the child
<TextInput onChangeText = { (input) => this.props._setAge(input, 'age') }
// here i know statekey for each child so i directly pass the key from child so i dont have to pass it as a props and then access it

Categories