Migrating away from componentWillReceiveProps - reactjs

The componentWillReceiveProps is becoming deprecated, however, I am unclear as to how to migrate away from it. For example, a simplified version of my current looks something like this:
import Reorder, {reorder, reorderImmutale, reorderFromTo, reorderFromToImmutable} from 'react-reorder'
class ObjectsArea extends React.Component {
constructor(props) {
super(props);
this.state = {
items: this.props.objects ? this.props.objects.items : []
};
}
componentWillReceiveProps(nextProps){
//May have to do a deep compare between nextProps.items and current items?
if (nextProps.objects){
this.setState({items: this.nextProps.objects.items})
}
}
onReorder (event, previousIndex, nextIndex, fromId, toId) {
let new_items = reorder(this.state.items, previousIndex, nextIndex)
this.setState({
items: new_items
});
//call to parent function
}
render(){
orderable_items = <Reorder reorderId="objects" onReorder={this.onReorder.bind(this)}>
{
this.state.items.map(item => (
<div key={item.id}>
{item.text}
</div>
))
}
</Reorder>
return (
<div>{orderable_items}</div>
)
}
My requirements:
Sometimes there will be no objects property (there isn't one on initial load)
When there is an objects property a sortable/draggable list is created using the react-reorder component
When items in the list are dragged to be rearranged the onReorder function is called.
The onReorder function should do two things: update the list on the screen, call a parent function passed in from props.
Currently all of this will work with componentWillReceiveProps, however, what is the proper way to migrate away from componentWillReceiveProps based on the above requirements?

While Tolsee's answer is perfectly correct it is also worth mentioning that the react docs suggest removing derived state (state that is calculated based on props) altogether. There is a great article here that is a great read in my opinion.
Your example fits the Anti-pattern: Unconditionally copying props to state example perfectly.
Without knowing your environment I cannot recommend a solution certainly, but to me it looks like you will be able to use the Fully controlled component example.
In that case, you'd need to lift your state up, simply use objects.items to render your Reorder child, and during the onReorder event simply call a function that you received as a prop.

In your problem you can do.
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.objects){){
return {items: this.nextProps.objects.items};
}
else return null;
}
Please follow this post for better understanding

Related

ReactJS: How to render a collection of objects

So I'm quite new on web development last couple of days. I come from c++ background and I can't wrap my head through all the principles of reactjs. I have 2 classes. The child class called JobAd should render some information that it got from props.
export default class JobAd extends Component {
constructor(props) {
super(props);
this.state ={
index: props.index,
id: props.jobId,
name: props.name,
description: props.description,
location: props.location,
adress: props.adress,
alreadyApplied: props.alreadyApplied,
open: false,
// toggleJob: props.toggleJob,
};
this.toggleJob = props.toggleJob;
}
render() {
return (
<div className={`${styles.jobAd} d-flex` + "job " + (this.state.open ? 'open': '')} key={this.state.index} onClick={() => this.toggleJob(this.state.index)}>
<div className={`${styles.jobTitle}`}>
{this.state.location} - {this.state.name}
</div>
<div className={`${styles.jobDetails}`}>
<div className={`${styles.jobDescription}`}> {this.state.description}</div>
<div className={`${styles.jobAdress}`}>{this.state.adress}</div>
<ApplyButton jobId= {this.props.id} alreadyApplied = {this.props.alreadyApplied}/>
</div>
</div>
)
}
}
The second class, queries a mongoDB db and creates jobAd objects populating them from the info gotten from db.
class JobExplorer extends React.Component
{
...
result.data.jobs.forEach(job => {
var find = job.employees.find(obj => obj === userId);
if (!(find === undefined)) {
alreadyApplied = true;
}
var toPush = new JobAd ({
index: i,
id:job._id,
description:job.description,
name:job.name,
location:job.locationName,
adress:job.locationAdress,
alreadyApplied:alreadyApplied,
open:false,
toggleJob: this.toggleJob.bind(this)
});
jobList2.push(toPush);
console.log("look");
console.log(jobList2)
});
this.setState({
jobList: jobList2
})
this.setState({
error: null,
jobs: result.data.jobs
});
...
render()
{
console.log("look2");
console.log(this.state.jobList);
return (
<div><Navigation />
{this.state.jobList}
</div>
);
}
But I am faced with the following error which I cannot find a fix for.
Error: Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state, toggleJob}). If you meant to render a collection of children, use an array instead.
How should I instantiate those objects so I could render them using the "architecture" I wrote. Is there a fundamental flaw that I have in my classes?
The below snippet doesn't work because new will return an object (this) not the react component.
So, instead of
var toPush = new JobAd({
index: i,
id: job._id,
...
});
jobList2.push(toPush);
you can do this
var toPush = <JobAd
index={i}
id={job._id}
...
/>;
The above snippet works because <JobAd ... /> is converted to React.createElement(JobAd, ... ). However, you still shouldn't do it like this. since there are a lot of better ways to do this. one of them is:
save just the data in joblist and then render the data list on JobAd component
like below:-
render(){
return this.state.joblist.map((job, i) => (
<JobAd
key={job._id}
index={i}
...
/>
));
}
The key is a really important thing. Read about it: https://reactjs.org/docs/lists-and-keys.html
Things that could be improved:-
Don't copy props in the state as you are doing in JobAd class instead directly render the props.
Don't call setState twice as in JobExplorer. you could set all the keys in
setState at the same time. since that would render the component twice.
Suggestions:-
You should avoid using var as that might cause some issues here.
since, you are just a starter, try using functional component first. they are
quite easier to grasp
You seem to have a misconception about state/props in React and web development. It's very normal; I learned python and Java first and many tutorials seem to assume that people just know this already.
"State" in generally refers to variables containing/referring to values that can change without a page refresh in your application. If you know a value is not going to change, it does not need to be held in state. Storing it in a normal variable is exactly what you should do.
"Props" is just another word for arguments that are passed to React components. There's more to it in reality, but as a beginner, that's all you need to really know for now.
So in your job add, things like name, address, jobs, description shouldn't go in state because they aren't going to change as a result of user interaction or for any other reason, unless the underlying data they are loaded from changes, but then that wouldn't be handled by React but instead by the API that your app gets data from. They should just be rendered, so refer to them like this.props.address in your render method. The value for open, however, need to be in state, because that definitely can change.
As for the error, it looks like you are not calling JobAd correctly. You need to use the syntax <Job Ad/> rather than new JobAd...that won't work in React.
I would recommend doing a tutorial to get the basics down.

ReactJS variables problems. It's updating variables that are not being touched

I'm working on a ReactJS app and i'm a new comer.
I have a Component like this
class Type extends React.Component {
constructor(props) {
super(props);
this.state = {
name : props.type.name,
description : props.type.description,
price : props.type.price,
imageList : props.type.images,
mode : 'view',
// i'm cloning the whole object
clone : props.type
};
}
handleDeleteImage(event) {
const imageId = event.target.getAttribute('data-imageId');
// get the current imageList of this Component
var imageList = this.state.imageList;
// checking the length of 2 image list before removing the
// targeted image
console.log(imageList.length) // displays 3
console.log(this.state.clone.images.length) // displays 3
// remove the targeted imageId
imageList.splice(imageId, 1);
// checking the length of 2 image list after removing the
// targeted image
console.log(imageList.length) // displays 2
console.log(this.state.clone.images.length) // displays 2
}
}
So what i'm doing here is i want to clone the object so when the user changes there mind and doesn't want to make changes anymore, they can hit the cancel button and everything is back to the state they were before (i have a function to handle this as well. I set the fields -name, description, price- to the values of the clone)
But as you can see, i didn't touched the image list in the clone at all still it got changed anyway.
Am i doing anything wrong here?
Thank you for any help.
Hey guys! So I realized that the concept I used in this service is not so efficient.
Like #Michael McQuade said, I should control the data in one flow only which is changing the data in the parent Component, not the child ones. I also reviewed the ReactJS Documentation and I can see why.
But with that being said. Let's say I'm working on a Component which has lots of Child-Component, does that mean I have to callback all the way up to the Parent Component to make changes in the Child one? And does that mean i must have multiple handlers in the Parent one that will be passed down to the Child that needs them?
I hope my question doesn't border you guys. Thanks!
You're using state and props together in a way I wouldn't recommend.
Instead of trying to make a copy of the props and storing it as state, make a stateless function and pass down a function which handles the deletion.
Here is an example:
class Child extends React.PureComponent {
render () {
return (<button onClick={this.props.handleBye}>{this.props.text}</button>)
}
}
class Parent extends React.PureComponent {
state = {
text: "Hello"
}
handler = () => {
this.setState({text: "bye"})
}
render() {
return (<Child text={this.state.text} handleBye={this.handler} />)
}ˆ
}
ReactDOM.render(<Parent />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This line will not clone an object, rather it will create a reference
clone : props.type
To clone you can use various techniques (depending on your need) one simple one would be
clone: Object.assign({}, props.type)
beware that this will only create a shallow copy of the object.
To create a deep copy you can use
clone: JSON.parse(JSON.stringify(props.type))
this is an easy technique but it is slow and will not copy dates correctly.
If you need fast and reliable deep clone you better search for something else that suits your needs (maybe a library like lodash).
this.state.clone is just a reference to the props.type object. So when you use splice() you change the contents of the array and therefore "mutate" props.type.
If you really want to clone the object do it like that:
this.state = {
clone: {...props.type} // create a new object and spread the props.type object properties
}
You can read more about the spread operator here

What is the best and most efficient way to bind callbacks in ReactJS? In the constructor or in the render method or as a property initializer?

Say I have a function in the Parent component that I want to use as a callback for the Child component. Which way is better?
render() {
return (
<Child handleClick={this.handleClick.bind(this)} />
)
}
or
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
According to this eslint, it is much better to do so in the constructor because the render() method may be called multiple times. This makes perfect sense to me.
However, this just means that the bound function ends up being a property on each and every instance. Thus defeating the entire purpose of the prototype.
I do know about the property initializers:
handleClick = () => {
// do stuff
}
But it looks this syntax is no where close to being agreed upon for ES2017 (or whatever the next one might be). For this reason, I'm afraid to use this syntax as it might never make it into the language.
So, having said all that, what is the best way to go about this?
Although all options you and other people in comments have said do definitely work, I would go for Airbnb's React styleguide one:
✓ Binding on the constructor
constructor(props) {
super(props);
this.foo = this.foo.bind(this);
}
✓ ES7 binding on constructor is also a good option (though keep in mind that it is still a proposal)
constructor(props) {
super(props);
this.foo = ::this.foo;
}
What about the other options?
✖ Binding on render
render() {
return <button onClick={this.handleClick.bind(this}>...</button>
}
Rebinded on every re-render, not to say that if you have two elements that use the same function, you will have to choose whether to leave one without binding or binding both (which would be ugly or more inefficient).
✖ Arrow function (not binding)
render() {
return <button onClick={(e) => this.handleClick(e)}>...</button>
}
You are going through one more function when there is no need to.

React JS Component "wait for props"

This is not a question as much "how to make this work" as much as it is a "was this the best way." Here's my code:
/**
* React Static Boilerplate
* https://github.com/koistya/react-static-boilerplate
* Copyright (c) Konstantin Tarkus (#koistya) | MIT license
*/
import React, { Component } from 'react';
// import './InputWidgetText.scss';
import ContentBlock from '../ContentBlock';
var i = 0;
var contentBlocks = [];
var ContentContainer = React.createClass({
addNewBlock: function(){
i++;
contentBlocks.push(<ContentBlock key={i} index={i}/>)
this.forceUpdate();
},
render: function(){
if (this.props.inputs) {
contentBlocks = this.props.inputs.map(function(item, index){
i++;
return(<ContentBlock key={index} index={index} content={item} />)
});
}
return (
<div>
{contentBlocks}
<button onClick={this.addNewBlock}>+</button>
</div>
)
}
});
export {ContentContainer as default};
The problem is that every so often on a refresh the props.inputs are not getting passed down to this component and throwing an error when I try to map undefined. So the simple solution is to put the map process in an if check for whether or not the props are there yet - is that actually the right way to handle this? My data is passed in via a reflux mixin on the parent. I just feel like there might be a more proper way to handle this. Thanks for the feedback!
May I strongly suggest you refactor your code to do away with the file variables i and contentBlocks.
The contentBlocks variable seems completely unnecessary, whilst your i variable should be part of the state. Whilst you're at it, give i a more meaningful name, e.g. blockCount.
getInitialState: function () {
return {
blockCount: 0
};
},
Then define your click event handler to modify the state:
addNewBlock: function () {
this.setState({
blockCount: this.state.blockCount + 1
});
},
Every time you call setState(), React will trigger a re-render. You should never need to call forceUpdate().
Finally, your render() function should return its content based SOLELY on this.props and this.state. That is, for any given props and state, the output will be predictable. Think of this.props and this.state as input parameters to the render() function. That is all render() can, or needs to, know about.
I won't try to write the render() function as I'm not sure exactly what you're trying to achieve with this component. But for a given this.props.input and this.state.blockCount (or whatever you choose to use as props and state) you should know exactly what you're outputting.
I know I haven't directly answered the question you put, but I hope this clarifies some React concepts.

Change state when properties change and first mount on React - Missing function?

I have come across a problem about states based on properties.
The scenario
I have a Component parent which creates passes a property to a child component.
The Child component reacts according to the property received.
In React the "only" proper way to change the state of a component is using the functions componentWillMount or componentDidMount and componentWillReceiveProps as far as I've seen (among others, but let's focus on these ones, because getInitialState is just executed once).
My problem/Question
If I receive a new property from the parent and I want to change the state, only the function componentWillReceiveProps will be executed and will allowed me to execute setState. Render does not allow to setStatus.
What if I want to set the state on the beginning and the time it receives a new property?
So I have to set it on getInitialState or componentWillMount/componentDidMount. Then you have to change the state depending on the properties using componentWillReceiveProps.
This is a problem when your state highly depends from your properties, which is almost always. Which can become silly because you have to repeat the states you want to update according to the new property.
My solution
I have created a new method that it's called on componentWillMount and on componentWillReceiveProps. I have not found any method been called after a property has been updated before render and also the first time the Component is mounted. Then there would not be a need to do this silly workaround.
Anyway, here the question: is not there any better option to update the state when a new property is received or changed?
/*...*/
/**
* To be called before mounted and before updating props
* #param props
*/
prepareComponentState: function (props) {
var usedProps = props || this.props;
//set data on state/template
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === usedProps.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},
componentWillMount: function () {
this.prepareComponentState();
},
componentWillReceiveProps: function (nextProps) {
this.prepareComponentState(nextProps);
},
/*...*/
I feel a bit stupid, I guess I'm loosing something...
I guess there is another solution to solve this.
And yeah, I already know about this:
https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html
I've found that this pattern is usually not very necessary. In the general case (not always), I've found that setting state based on changed properties is a bit of an anti-pattern; instead, simply derive the necessary local state at render time.
render: function() {
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === this.props.currentQuestion.id;
});
return ...; // use currentResponses instead of this.state.currentResponses
}
However, in some cases, it can make sense to cache this data (e.g. maybe calculating it is prohibitively expensive), or you just need to know when the props are set/changed for some other reason. In that case, I would use basically the pattern you've written in your question.
If you really don't like typing it out, you could formalize this new method as a mixin. For example:
var PropsSetOrChangeMixin = {
componentWillMount: function() {
this.onPropsSetOrChange(this.props);
},
componentWillReceiveProps: function(nextProps) {
this.onPropsSetOrChange(nextProps);
}
};
React.createClass({
mixins: [PropsSetOrChangeMixin],
onPropsSetOrChange: function(props) {
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === props.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},
// ...
});
Of course, if you're using class-based React components, you'd need to find some alternative solution (e.g. inheritance, or custom JS mixins) since they don't get React-style mixins right now.
(For what it's worth, I think the code is much clearer using the explicit methods; I'd probably write it like this:)
componentWillMount: function () {
this.prepareComponentState(this.props);
},
componentWillReceiveProps: function (nextProps) {
this.prepareComponentState(nextProps);
},
prepareComponentState: function (props) {
//set data on state/template
var currentResponses = this.state.candidatesResponses.filter(function (elem) {
return elem.questionId === props.currentQuestion.id;
});
this.setState({
currentResponses: currentResponses,
activeAnswer: null
});
},

Categories

Resources