I want to change some inner property of a prop. Props claim to be immutable, but when I change the value, the parent's state value is getting changed.I read that props are immutable. But changing the value is reflecting in parent.
class ParentComp extends React.Component {
constructor(props) {
super(props);
let property = {someProperty:'ABCD'};
this.state={
myState: property
}
}
render() {
return(
<div>
Parent:{JSON.stringify(this.state.myState)}
<ChildComp pState={this.state.myState} />
</div>
);
}
}
class ChildComp extends React.Component {
render() {
this.props.pState.someProperty = '1234';
return(
<div>
Child:{JSON.stringify(this.props.pState)}
</div>
);
}
}
At the end, I see both the values are changed to 1234. In online fiddles, it is working as expected(ie., parent value is not changed.). But in my project, the parent is being changed.
How do I achieve this usecase, wherein I want to change some properties in props, and not reflect in the parent's state?
I read that props are immutable
That's not true - you only should treat them as they were immutable. In other words, they are just regular javascript Objects and you should not mutate them.
If you want to change the value of the props, you should dump the props in the state first then do the mutation on that state.
Mutating props is not best practice. So your ChildComp should be like:
class ChildComp extends React.Component {
constructor(props) {
super(props);
this.state = {
pState: props.pState,
}
}
componentDidMount() {
const { pState } = this.state.
pState.someProperty = '1234';
this.setState({
pState,
});
}
render() {
return(
<div>
Child:{JSON.stringify(this.state.pState)}
</div>
);
}
}
Related
I have a Top component which has a checkbox and also a child MyValidator component. When the checkbox changes, I have a handler in the Top component which sets its State var, this.state.activeItemLocked. The MyValidator child component specified in Top has a Prop with this state var.
However, when the Top Component changes its state, the MyValidator is not refreshed with this new change, even though it should render the current value which is its own state var received from Props:
Top
class Top extends React.Component {
constructor(props) {
super(props);
this.state = {
activeItemLocked: false;
};
}
render() {
return (
<div>
<input type="checkbox" defaultChecked={this.state.activeItemLocked} id="bChkboxLocked" onChange={this.handleCheckboxChange}></input>
<label htmlFor="bChkboxLocked">Locked</label>
<MyValidator value={this.state.activeItemLocked}></MyValidator>
<div>
);
}
// Handler to set State var. in Top upon Checkbox Toggle
handleCheckboxChange = (e) => {
this.setState({
activeItemLocked: e.target.checked ? true : false
});
};
}
MyValidator
class MyValidator extends Component {
constructor(props) {
super(props);
this.state = {
value: this.props.value // Initialize MyValidator's State var. from Props
};
}
render() {
return (
<div style={{"color":"red"}}>Checkbox current state: {this.state.value}</div>
);
}
}
Issue : Upon Top's state change, the Prop-based MyValidator state change does not occur (it does not display the current value).
Keeping a class component, there are many ways you can achieve this. 2 of them are:
Don't copy a props to a state variable. Instead just use the props.
class MyValidator extends Component {
render() {
return (
<div style={{"color":"red"}}>Checkbox current state: {this.props.value}. </div>
);
}
}
Use getDerivedStateFromProps
class MyValidator extends Component {
static getDerivedStateFromProps(props, state) {
if (props.value !== state.value) {
return {value: props.value}
}
return state;
}
render() {
return (
<div style={{"color":"red"}}>Checkbox current state: {this.state.value}</div>
);
}
}
Constructor only runs once. So you need to use getDerivedStateFromProps to update child state before render:
static getDerivedStateFromProps(props){
return {
value: props.value
};
}
also convert the boolean value to a string so that it gets printed out:
{this.state.lol.toString()}
Sandbox: https://codesandbox.io/s/late-https-3eh52?fontsize=14&hidenavigation=1&theme=dark
My React structure is
- App
|--SelectStudy
|--ParticipantsTable
In SelectStudy there is a button whose click triggers a message to its sibling, ParticipantsTable, via the App parent. The first Child->Parent transfer works. But how do I implement the second Parent->Child transfer? See questions in comments.
App
class App extends Component {
myCallback(dataFromChild) {
// This callback receives changes from SelectStudy Child Component's button click
// THIS WORKS
alert('SelectStudy Component sent value to Parent (App): ' + dataFromChild.label + " -> " + dataFromChild.value);
// QUESTION: How to Update State of ParticipantsTable (SelectStudy's Sibling) next?
// ........................................................
}
render() {
return (
<div className="App">
<SelectStudy callbackFromParent={this.myCallback}></SelectStudy>
<ParticipantsTable></ParticipantsTable>
</div>
);
}
SelectStudy
class SelectStudy extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [],
selectedStudy: null,
isButtonLoading: false
};
this.handleButtonClick = this.handleButtonClick.bind(this);
}
render() {
const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.state;
return <Button onClick={this.handleButtonClick}>Search</Button>;
}
handleButtonClick = () => {
this.props.callbackFromParent(this.state.selectedStudy);
}
}
ParticipantsTable - this needs to receive a certain variable, e.g. study in its State
class ParticipantsTable extends React.Component {
constructor(props) {
//alert('Constructor');
super(props);
// Initial Definition of this component's state
this.state = {
study: null,
items: [],
error: null
};
}
// THIS METHOD IS AVAILABLE, BUT HOW TO CALL IT FROM App's myCallback(dataFromChild)?
setStudy = (selectedStudy) => {
this.setState({study: selectedStudy});
}
render() {
return ( <div>{this.state.study}</div> );
}
}
The state should live definitively at the App level, not in the child. State needs to live one level above the lowest common denominator that needs access to it. So if both SelectStudy and ParticipantsTable need access to the same bit of state data, then it must live in their closest common ancestor (or above).
This is a core concept of React, known as "lifting state up", so much so that it has its own page in the official React documentation.
In your case, it would look something like this. Notice how state lives in only one place, at the <App /> level, and is passed to children via props.
import React from 'react';
class App extends React.Component {
// State lives here at the closest common ancestor of children that need it
state = {
error: null,
isLoaded: false,
items: [],
selectedStudy: null,
isButtonLoading: false
};
myCallback = (dataFromChild) => {
this.setState(dataFromChild);
};
render() {
return (
<div className="App">
{/* State is passed into child components here, as props */}
<SelectStudy data={this.state} callbackFromParent={this.myCallback}></SelectStudy>
<ParticipantsTable study={this.state.selectedStudy} />
</div>
);
}
}
class SelectStudy extends React.Component {
handleButtonClick = () => {
// Here we execute a callback, provided by <App />, to update state one level up
this.props.callbackFromParent({ ...this.props.selectedStudy, isButtonLoading: true });
};
render() {
const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.props.data;
return <Button onClick={this.handleButtonClick}>Search</Button>;
}
}
// This component doesn't need to track any internal state - it only renders what is given via props
class ParticipantsTable extends React.Component {
render() {
return <div>{this.props.study}</div>;
}
}
I think what you need to understand is the difference between state and props.
state is internal to a component while props are passed down from parents to children
Here is a in-depth answer
So you want to set a state in the parent that you can pass as props to children
1 set state in the parent
this.state = {
value: null
}
myCallback(dataFromChild) {
this.setState({value: dataFromChild.value})
}
2 pass it as a prop to the children
class ParticipantsTable extends React.Component {
constructor(props) {
super(props);
this.state = {
study: props.study,
items: [],
error: null
};
}
Also, although not related to your question, if you learning React I suggest moving away from class-based components in favour of hooks and functional components as they have become more widely used and popular recently.
I have two components Parent and Children. I want to see on my screen actual value of localStorage.getItem("myEl"). Parent state is storage:localStorage.getItem("myEl"). I change the "myEl" in localeStorage in Children component. Unfotunately Parent component not re-renders after "myEl" is changed but it works after I perform some action, such as changing the state again. I know that the problem is that setState is asinc but i don't know how to fix the problem.
For example,
Parent:
class Parent extends React.Component {
constructor() {
super();
this.state = {storage:localStorage.getItem("myEl")};
}
render(){
return <div>
<Child/>
<p>{this.state.storage}</p>
</div>
}
}
Child:
let i=0;
class Child extends React.Component {
render() {
return (
<button onClick={() => {
localStorage.setItem("myEl",i);
i++;
}}>click me</button>
);
}
}
react is not listening to changes in localStorage that is why parent component don't know when child component changes the value in localStorage.
To fix this you have to path your child component onClick function from parent this way:
class Parent extends React.Component {
constructor() {
super();
this.state = {storage:localStorage.getItem("myEl")};
}
handleChildClick = (count) => {
localStorage.setItem("myEl", count);
this.setState({ storage:localStorage.getItem("myEl") });
}
render(){
return <div>
<Child onClick={this.handleClick} />
<p>{this.state.storage}</p>
</div>
}
}
let i=0;
class Child extends React.Component {
render() {
return (
<button onClick={() => {
this.props.onClick(i);
i++;
}}>click me</button>
);
}
}
in case you need this value in other components consider using redux with react-redux containers to have a global storage available to you in any place of the react app.
Component should receive an state or prop in order to rerender itself, in your case it receive none of them. You should not update the localStorage and expect that your component is going to be reRendered with a new value from local storage, you could write a handler for your button in order to save the incremented value into your localstorage. Like below:
class App extends React.Component {
constructor() {
super()
this.state = { _val: 0 }
}
componentDidMount = () => {
const valFromLocalStorage = localStorage.getItem("myEl") || this.state._val
this.setState({ _val: valFromLocalStorage })
}
handleINC = e => {
const _valFromState = this.state._val
const _val = _valFromState++
localStorage.setItem("myEl", _val)
}
render() {
return(
<div>
<button onClick={this.handleINC}>increment value!</button>
</div>
)
}
}
By the way, in componentDidMount you get the value from localStorage or if it was falsy you get the default value from your state. Then in button handler function you get the value from state and increment it and set it in your localStorage in case of component use cases in future, when user closes the tab and opens our website after a while the localstorage data is not been cleared, then this component will get the value from there.
if I have a class
class Role extends Component {
constructor(props) {
super(props);
this.state = {
role: 'something',
value: 1
}
}
render() {
let roleStatus = [];
for (let key in this.state) {
roleStatus.push(<p key={key}>{key}: {this.state[key]}</p>)
}
return (
<div>
<div>
{roleStatus}
</div>
</div>
);
}
and then another class that uses composition (asrecommended by React doc)
class specialRole extends Component {
constructor(props) {
super(props);
// I want to add some other attributes to this.state
}
render() {
return <role />
}
}
}
I want to be able to add another attribute, let's say name, to this.state, but when I use setState to add it, the render doesn't reflect to it. I'm wondering if it's because setState is not synchronized function. If that's the case, how should I achieve what I want to do?
What you'll want to do is think of it like a parent/child composition.
The parent has the logic and passes it to the child, which then displays it.
In your case, the parent should be Role, and the child component be something that renders Role's states, for example: RoleStatus. In addition, you can have another component called SpecialRoleStatus. Note the capitalized component names, component names should be capitalized.
The composition would look something like this:
class Role extends React.Component {
constructor(props) {
super(props)
//lots of state, including special ones
}
render() {
return(
<div>
<RoleStatus normalState={this.state.normalState} />
<SpecialRoleStatus specialState={this.state.specialState} />
</div>
)
}
}
Also, setState() won't behave like you want it to because it does not set the state of any other component other than its own component.
So I have a component called "itemSelection" which contains a state with a property called "allItems" of type array
constructor(props){
super(props);
this.state = {
allItems: []
}
}
Then I have a component called "methods" which contains a function that returns a value
selectMethod = (e) => {
const x = e.target.getAttribute("data");
this.setState({method: x}, () => console.log(this.state.method));
}
So, What I want to do is to bring the value of propery "method" and push it to the "allItems" array aside with its current state.
The solution is to lift state up: the shared state (here the items) should be kept by the closest ancestor of both components. This ancestor then passes the state to the children via props, along with a callback function that children can use to mutate the state.
For instance in pseudo-code:
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
allItems: []
}
this.onSelect = this.onSelect.bind(this)
}
onSelect(item) {
this.setState({allItems: this.state.allItems.push(item)})
}
render() {
return (
<Child1 items={this.state.allItems}/>
<Child2 onSelect={this.onSelect}/>
)
}
}
class Child1 extends React.Component {
render() {
return (
{this.props.items.map(i => i.name)}
)
}
}
class Child2 extends React.Component {
render() {
return (
<button onClick={this.props.onSelect(...)}>button</button>
)
}
}
In the itemSelection component, create a function that will update allItems. Then, pass that function in a property to the method component and call it from there.