problem with getDerivedStateFromProps when child component make setstate in parent - javascript

I have 3 components. Grandparent, Parent and Child.
if a data change in Grandparent I pass it to Parent as a prop so I can trigger change in Parent and set new data to its state using getDerivedStateFromProps.
export class Parent extends Component {
constructor(props) {
super(props);
this.state = {
userId : this.props.userId
}
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.userId !== prevState.userId)
return {
userId : this.props.userId
}
return null
}
getDataFromChild(value){
this.setState({
userId: value
})
}
render(){
return (
<Child onChange={(value) => this.getDataFromChild(value)} />
)
}
}
there is also a prop in Child component called onChange. When a data change in Child Component I use this.props.onChange(data) to passing it to parent.
so I want to store this data in Parent's state.
export class Child extends Component {
constructor(props) {
super(props);
this.state = {
userId: this.props.userId
}
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.userId !== prevState.userId)
return {
userId: this.props.userId
}
return null
}
async passToParent(value) {
await this.setState({
userId: value
})
this.props.onChange(value)
}
render() {
return (
<input type="text" onChange={(e) => this.passToParent(e.target.value)} value={this.state.userId}/>
)
}
}
the problem is when any states or props change in Parent component, getDerivedStateFromProps is triggered and because nextprops.userId is not equal to prevState(new state that has been set by Child), the old userId(comes from Grandparent) set to state.
what should I do.
sorry for my bad english skill.

If you would like the state in Parent to change when a value in Grandparent changes, then the quickest way forward would be to use componentDidUpdate:
export class Parent extends Component {
constructor(props) {
super(props);
this.state = {
userId : this.props.userId
}
}
getDataFromChild(value){
this.setState({
userId: value
})
}
componentDidUpdate(prevProps) {
if(prevProps.userId !== this.props.userId) {
this.setState({
userId: this.props.userId,
});
}
}
render(){
return (
<Child onChange={(value) => this.getDataFromChild(value)} />
)
}
}
If, on the other hand, if you don't want state in Parent to change once the user has set it to something, then you can manually set a state that indicates that Child has set it and use it in componentDidUpdate.
getDataFromChild(value){
this.setState({
userId: value,
setByChild: true,
})
}
componentDidUpdate(prevProps) {
if(!this.state.setByChild && prevProps.userId !== this.props.userId) {
this.setState({
userId: this.props.userId,
});
}
}
Related:
You don't need derived state - React Blog

Related

Re-render child after parent state change with get request

So I'm a beginner with react and I was wondering how to re-render the child after setting the state in the parent (from the child). Here's a code sample. I have a function that calls a GET request using Axios and when I press the button in the child component ideally it will update the state in the parent and also re-render the child but it only does the former.
Parent:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
fetchData = () => {
axios
.get(url)
.then(res => this.setState({data: res.data}))
}
Render() {
return (<Child data={this.state.data} fetchData={this.fecthData}/>)
}
// ...
Child:
class Child extends Component {
// ...
render() {
const { data, fetchData } = this.props
// render data
return <button onClick={fetchData}>Change data then fetch</button>
}
}
Also, are you supposed to make a local state in the Child and set it as a copy of the Parent's state or just passing it down as a prop is okay?
Your parent component holds the data and the child uses it. It seems to me you're doing it the right way. Here is a fully working example:
Codesandbox
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
this.updateData = this.updateData.bind(this);
}
async fetchData() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
return response.json();
}
updateData() {
this.setState({ data: [] }) // Creates a flicker, just so you see it does refresh the child
this.fetchData().then((res) => this.setState({ data: res }));
}
render() {
return <Child data={this.state.data} onAction={this.updateData} />;
}
}
Note I renamed your child prop fetchData into onAction (I don't know what's the name of the action that triggers a refresh, could be onRefresh). It's always best to see components props with separation between data attributes and event attributes.
Even standard components have it this way: <input value={user.firstname} onChange={doSomething} />. So, better to prefix events by on, then the parent decides what to do with it. It's not the child's concern.
class Child extends Component {
render() {
const { data, onAction } = this.props;
return (
<>
<button onClick={onAction}>Change data then fetch</button>
{data.map((item) => (
<div key={item.id}>
{item.id} - {item.title}
</div>
))}
</>
);
}
}

React setState not triggering a re-render with Contexts

I've got a React component that updates its state after making a request.
In the render function of this component, I am creating a context provider and passing in the value of the state.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
text: 'defaultValue',
}
}
componentDidMount() {
makeAxiosRequest()
.then((response) => {
this.setState({
text: 'updatedValue'
})
})
.catch((error) => {
console.log(error)
})
}
render() {
console.log(this.state.text)
return (
<div>
<MyContext.Provider value={{
text: this.state.text
}}>
<SomeComponent />
</MyContext.Provider>
</div>
)
}
}
export default App;
Then, In the child component, I am trying to get that value from the context.
class SomeComponent extends React.Component {
static contextType = MyContext;
constructor(props, context) {
super(props, context);
console.log(this.context.text)
}
}
However, the value in the context in SomeComponent is 'defaultValue', instead of the updated value.
The final console log is:
defaultValue
defaultValue
updatedValue
Shouldn't it instead be
defaultValue
defaultValue
updatedValue
updatedValue
because when the value is updated, then a re-render should be triggered and context should be updated. What am I missing here?
SomeComponent's constructor() only gets called when the component mounts, not when it re-renders. In order to see the new context value you would need to put your console.log(this.context.text) inside its render() or a lifecycle method like componentDidUpdate().

How to take the values from a componentDidUpdate() and save it so that i can use it later for displaying

export default class newroom extends Component {
constructor(props) {
super(props)
this.state = {
currentUser:NaN,
rooms:NaN
}
}
static getDerivedStateFromProps(nextProps, prevState){
// console.log(prevState)
const {currentUser} = nextProps
return {currentUser}
}
componentDidUpdate(){
const {...currentUser} = this.state.currentUser
currentUser.getJoinableRooms()
.then((rooms)=>{
//this rooms has a id and a name which i want to store so that i can display in the h3 tag
})
.catch(err=>console.log(err))
}
render() {
return (
<div className="new-room">
<h3>{}</h3>
</div>
)
}
}
The getDerivedStateFromProps() method returns the currentUser object and sets the state,
and when ever the curentUser updates the componentDidUpdate method fires and gets the rooms object but i cant figure out how i will store the rooms.id & rooms.name so that i can later display in in the h3 tag.If i use the setState in the componentDidUpdate() the state is updated each time and the method is fired again.
Compare current props with previous props before calling getJoinableRooms
componentDidUpdate(prevProps, prevState){
if(prevProps.currentUser.id !== this.props.currentUser.id){
this.props.currentUser.getJoinableRooms()
.then((rooms)=>{
//set state here
})
.catch(err=>console.log(err))
}
}
You should avoid using getDerivedStateFromProps, and do your fetches in your componentDidMount method (When it's first mounted):
export default class newroom extends Component {
constructor(props) {
super(props)
this.state = {
rooms:NaN
}
}
componentDidMount(){
const { currentUser } = this.props
currentUser.getJoinableRooms()
.then((rooms)=>{
this.setState({rooms})
})
.catch(err=>console.log(err))
}
render() {
return (
<div className="new-room">
{rooms.map(room => <h3 key={room.id}>{room.id}</h3>)}
</div>
)
}

React: Using `setState` with a function inside `componentWillReceiveProps`

Considering the following React Component snippet, I need to set a new component state based on the new props and the current state. It's preferred to use an "updater" function when defining new state which is based on the old one, now my confusion is that I have the nextProps parameter given to the componentWillReceiveProps lifecycle method, but the updater function also, gets a props parameter. Which one should I use?
Now I guess this should be specific to React 16.2; I'm guessing the introduction of the new getDerivedStateFromProps(nextProps, prevState) static method in React 16.3 should eliminate this confusion (right?)
class MyComponent extends Component {
constructor(props, ...args) {
super(props, ...args);
this.state = {
value: props.value,
};
}
componentWillReceiveProps(nextProps) {
this.setState((prevState, props) => {
return {
value: (/* `nextProps or props` */).value,
}
});
}
render() {
// ...
}
}
There are exactly the same thing. If your want to access previous props, the this.props remains untouched in componentWillReceiveProps method.
My intuition is that setState doesn't fire immediately in side componentWillReceiveProps.
Let's consider the example below.
After first click, the curProps will get 0, and props, nextProps both return 1.
class Test extends Component {
state = {
value: 0
};
componentWillReceiveProps(nextProps) {
const curProps = this.props;
this.setState((prevState, props) => {
console.log('[curProps]', curProps.num);
console.log('[props]', props.num);
console.log('[nextProps]', nextProps.num);
const value = prevState.value + nextProps.num;
return {
value
};
});
}
render() {
return <h4>{this.state.value}</h4>;
}
}
class App extends Component {
state = {
value: 0
};
render() {
return (
<div>
<Test num={this.state.value} />
<button onClick={this.onClick}>Add</button>
</div>
);
}
onClick = () => {
this.setState(prevState => ({ value: prevState.value + 1 }));
};
}
There is the codesandbox demo https://codesandbox.io/s/zxpovzkywx

re-rendering the Value When I pass to Parent Class to Child Class

I am developing a sample application. Actually when i was Pass the Value Parent Class to Child Class getting in componentWillReceiveProps() method more times re-rendering that Value.
Here this is my Code:
This is my Parent Class:
class ParentClass extends React.Component {
constructor (props) {
this.state = {
BadgeValue: 0,
setTabVal: false
}
}
getBadgeLength (badgeValue) {
this.setState({
badgeLength: badgeValue,
setTabVal: true
})
}
render () {
return (
<ChildClass navigator= {this.props.navigator} getBadgeLength={this.getBadgeLength.bind(this)} />
)
}
}
Now I Assumed Child Class: in this Case i am actually Calling for Update Purpose again same Methods calling in componentWillReceiveProps()
But, My main Issue more times re-rendering in this scenario.
class ChildClass extends React.Component {
componentWillReceiveProps () {
var getBadgeValue = array.length
this.props.getBadgeLength(getBadgeValue)
}
componentWillMount () {
var getBadgeValue = array.length
this.props.getBadgeLength(getBadgeValue)
this.setState({
dataSource: this.state.dataSource.cloneWithRows(array)
}, () => {
this.getshopaddressData()
this.getShopDetails()
}
render(){
return()
}
}
First of all your method name should be setBadgeLength because its updating the value. Secondly you should pass down badge value down to child class as a prop and then compare it in child class :
shouldComponentUpdate(nextProps) {
if (this.props.badgeValue != nextProps.badgeValue) {
return false; // don't update if badgeValue is changing
} else {
return true;
}
}

Categories