React Native - Update Parent State in Child with Dynamic Key - javascript

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

Related

Props in child doesn't update when parent updates it's state

I've spent a few days on this and it is driving me crazy now.
I have a state in a parent component containing an Array[string] of selected squares which is passed to the child component (a map) along with the set function from the hook. The issue is that when I set the new squares they are changed in the parent, but on selection of another square it is not taking into account the already selected squares.
function Parent(props){
const [selectedSquares, setSquares] = useState([]);
useEffect(() => {
console.log('parent useEffect', selectedSquares);
}, [selectedSquares]);
return (
<Child selectedSquares={selectedSquares}
handleSquaresChange={setSquares}
/>
)
}
function Child(props){
const {selectedSquares, handleSquaresChange} = props;
useEffect(() => {
console.log('child useEffect', selectedSquares)
}, [selectedSquares]);
const handleSelect = evt => {
if(evt.target){
const features = evt.target.getFeatures().getArray();
let selectedFeature = features.length ? features[0] : null;
if (selectedFeature) {
console.log('select (preadd):', selectedSquares);
const newTile = selectedFeature.get('TILE_NAME');
const newSquares = [...selectedSquares];
newSquares.push(newTile);
const newTest = 'newTest';
handleSquaresChange(newSquares);
console.log('select (postadd):', newSquares);
}
}
return(
<Map>
<Select onSelect={handleSelect}/>
</Map>
)
}
On the first interactionSelect component I get this output from the console:
parent useEffect: [],
child useEffect: [],
select (preadd):[],
child useEffect:['NX'],
parent useEffect: ['NX'],
select (postadd): ['NX'].
Making the second selection this is added to the console:
select (preadd):[],
select (postadd): ['SZ'],
child useEffect:['SZ'],
parent useEffect: ['SZ'].
Turns out there is an addEventListener in the library I am using that is going wrong. Thanks to everyone who responded but turns out the issue was not with React or the state stuff.
Consider something like the code below. Your parent has an array with all your options. For each option, you render a child component. The child component handles the activity of its own state.
function Parent(props){
// array of options (currently an array of strings, but this can be your squares)
const allOptions = ['opt 1', 'opt 2', 'opt 3', 'etc'];
return (
<>
// map over the options and pass option to child component
{allOptions.map((option) => <Child option={option}/>)}
</>
)
}
function Child({ option }){
const [selected, setSelected] = useState(false); // default state is false
return (
<>
// render option value
<p>{option}</p>
// shows the state as selected or not selected
<p>Option is: {selected ? "selected" : "not selected"}</p>
// this button toggles the active state
<button onClick={() => setSelected(!selected)}>Toggle</button>
</>
)
}

Get Elements, (children) of React.element

So I'm having an issue I would like to resolve, Maybe someone has an answer for it.
My problem is that I have a Component that has its own Views and Components, at the same time I have a Parent Component thats using the this specific Component.
I want to check if the child of the Child Component has some props.
Child Component
const Child = () => {
return (
<View wantedArgument={true}>
<View anotherWantedArgument={false}>
</View>
</View>
)
}
Parent Component
const Parent = () => {
return (
<Child>
</Child>
)
}
So I want to get the props values of the child views.
I can use useRef for those Views, but it's not that generic and dynamic.
My question is, is there a way I can get those elements of the child?
Thanks ahead
Eden.
You can check props of Parent's children using React.Children API.
In this example, we checking the props of every Parent's child and logging them.
If you want to go to a deeper level (Child of Child of Child etc.), do it with recursion with inductive step child.props.children (if available).
const Child = ({ prop }) => {
return <>{prop}</>;
};
const Parent = ({ children }) => {
useEffect(() => {
React.Children.forEach(children, child => {
console.log(child.props);
});
}, [children]);
return <div>{children}</div>;
};
const App = () => {
return (
<Parent>
<Child prop={1} />
</Parent>
);
};

Child component not updating with updates state via Parent props

I have a parent component from where I am passing a callback function handleProjectStagesSave and some data as props to child component. In my child component, I am accessing the props and storing the props object as a whole to the state using useState.
Parent component:
function ProjectStages(props) {
const [projectStages, setProjectStages] = React.useState(null);
useEffect(() => {
ProjectAPI.getProjectStages().then((response) => {
setProjectStages(response);
});
}, []);
//this function is passed as prop to child component. child component send data through parameters back to parent
function handleProjectStagesSave(val){
projectStages.someProperty = val; //modifying state property here and updating the state below
setProjectStages(projectStages);
}
if(projectStages === null){
return (<Loader/>);
}
return (
<div>
{(() => {
//loop through state data and pass props to child component
return projectStages.phases.map((stage) => {
return (
<ProjectStageTile criterias={stage.criterias} stagesSave={handleProjectStagesSave}/>
);
});
})()}
</div>
);
}
Now, the child component passes the callback function to it's own child component CriteriaScores which basically passes some data and sends it back all the way up to the parent.
Child component:
function ProjectStageTile(props){
const [projectStageTileState, setProjectStageTileState] = React.useState(props);
return projectStageTileState.criterias.map((criteria) => {
return(
<div>
<div>
{criteria.selectedScore}
</div>
<CriteriaScores stagesSave={projectStageTileState.stagesSave} />
</div>
);
});
}
It works fine and I get the data and store update the state setProjectStages(projectStages); in parent component. Now, I expect the child component to have the updated state data immediately. However, what I saw in React dev tools is that, after updating the state, I update the props in the child component manually, then the state changes are reflected in the Child component. Any idea what is wrong? Please let me know if I can clarify more on this.
Update-1:
Instead of mutating the state directly I tried something like below but got same result.
projectStages.phases.filter((phase) => (phase.gdfatStage === "bid"))[0].criterias
.filter((criteria) => (criteria.criteriaId === val.split(' ')[1]))[0].selectedScore = val.split(' ')[2];
let newProjectStages = projectStages;
setProjectStages(newProjectStages);
Update-2:
Also tried below approach but it did not work.
let newProjectStages = projectStages;
newProjectStages.phases.filter((phase) => (phase.gdfatStage === "bid"))[0].criterias
.filter((criteria) => (criteria.criteriaId === val.split(' ')[1]))[0].selectedScore = val.split(' ')[2];
setProjectStages(newProjectStages);
projectStages is a state variable, and you are mutating it directly.
projectStages.someProperty = val;
The recommended way of doing this is as follows:
setProjectStages(...projectStages, someProperty : val)
When you use setProjectStages react will re-render, and the most recent state variable will be passed to the child component.
This worked:
const newProjectStages = Object.assign({}, projectStages); //this is important
newProjectStages.phases.filter((phase) => (phase.gdfatStage === "bid"))[0].criterias
.filter((criteria) => (criteria.criteriaId === val.split(' ')[1]))[0].selectedScore = val.split(' ')[2];
setProjectStages(newProjectStages);
In addition to the answer provided by #user9408899, I would add that the child component does not need to store its props in state. I would change it to:
function ProjectStageTile(props){
const {criteria, stagesSave} = props;
return criterias.map((criteria) => {
return(
<div>
<div>
{criteria.selectedScore}
</div>
<CriteriaScores stagesSave={stagesSave} />
</div>
);
});
}

state not updating in child component - react js

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>
);

React native: invoke method dynamically

How to invoke dynamically named method in Javascript.
I'm using React Native and when assign ref in TextInput I need that set dynamically method.
renderInput(id) {
<TextInput
ref={(input) => this.myInput = input}
/>
}
I need dynamic this.myInput.
I've tested it and got it wrong: this.myInput[id] and this[id].myInput
You should be using current:
this.myInput.current[id]
Here's how:
// First, create a reference (in the constructor)
this.myInput = React.createRef()
// Then, assign the ref (in the element)
<TextInput ref={this.myInput} />
// Now, to access the reference
// this.myInput.current.<<property you wanted to access>>
this.myInput.current[id] // id is defined as a variable
// this will get the dom id
// this.myInput.current.id
But if you insist using callback ref like you're currently having, then you can pass the id prop: (Also, I think the answer you're looking for is)
renderInput(id) {
<TextInput
ref={(input) => this.myInput = input}
id={id} {/* you were missing this in fact */}
/>
}
Now, to get that id:
this.myInput.id // will work fine, coz we have id prop now
// gets DOM id that you assigned in your element
constructor(props) {
super(props);
this.inputs = {};
}
// params Object should be in this format {id:324, referenceKey: 'one'};
renderTextInput(params) {
return <TextInput ref={input => { this.inputs[params.referenceKey] = input }}/>
}
// Using the refernce
componentDidMount(){
this.inputs['one'].focus()
}

Categories