Passing props in React [duplicate] - reactjs

This question already has an answer here:
componentWillRecieveProps method is not working properly: ReactJS
(1 answer)
Closed 5 years ago.
I'm afraid this is not going to be a well articulated question as I am somewhat new to React.
I'm modifying the methods of a component SeqAlignmentChart, I'm making the following calls from within componentWillReceiveProps
If I do "console.log(this)" I get
This all looks great. However if I do "console.log(this.props)" I get
Note the 'data' field has gone from length 11 in "this" to length 1 in "this.props" while the 'width' property seems to have transferred. Apologies again for the formatting.
It seems very strange to me that when I print "this.props" I get a data object that doesn't contain the values I would be expecting. Can anyone explain why 'console.log(this.props)' doesn't seem to be printing the same props as when I just do 'console.log(this)' ?
Thanks so much for any insight
edit: here is the code for the component, not sure which parts are relevant for this
export default class SeqAlignmentChart extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
console.log("in mount component $$$$$$$$$$$$$$$$$$$$$");
console.log(this);
this.seqAlignmentChart = new SeqAlignmentVis(
ReactDOM.findDOMNode(this),
this.props.data,
{
width: this.props.width ? this.props.width - 15 : 1000 - 15,
margin: { top: 10, right: 10, bottom: 10, left: 10 }
}
);
this.seqAlignmentChart.render();
}
componentWillReceiveProps(nextProps) {
console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!");
console.log(this);
console.log(this.props);
console.log(this.props.data[0]);
console.log(this.props.data.length);
if (this.props.data.length > 1) {
console.log("should rerender here ^^^^^^^^^^^^^^^^^^^^^^^^");
this.componentDidMount();
}
}
render() {}
}

Take a look at the documentation for componentWillReceiveProps. Notice how it passes nextProps as an argument. nextProps refers to the props that the component is about to receive. this.props refers to the old props that the component currently has.
What happens when you console.log(nextProps)? Is this the data that you expected?

Without a concrete example it is hard to debug, but I would guess 'this' is out of scope.
https://github.com/facebook/react-devtools
Could you try's React's Chrome Devtools? Pretty sure it can inspect React's state better than console.logging.

Related

Understanding React Lifecycle Hooks

I'm new at React/React Native and I just began working on a company and, thus, working on existing projects.
The thing is, I can't understand this Creation Lifecycle Hooks on the begining of this code:
export default class ListResult extends React.Component {
constructor(props) {
super(props);
this.state.answers = props.navigation.state.params.answers;
this.state.saving = props.navigation.state.params.saving;
this.state.loading = false;
}
state = {
answers: null,
saving: false,
loading: true,
location: null,
dialogLocation: false,
latitude: '',
longitude: '',
};
location = null;
latitude = '';
longitude = '';
}
Can someone please provide me some explanation why is he using the constructor to initialize the state, and after that, used state to define variables, and then, after that, he also set some values to those variables?
Its a common mistake and even emphasised in related docs:
Avoid copying props into state! (in the constructor) This is a common mistake.
Only use this pattern if you intentionally want to ignore prop updates.
This code roughly should fixed to:
class ListResult extends React.Component {
state = {
answers: null,
...
};
componentDidMount = () => {
const { answers, saving } = props.navigation.state.params;
this.setState({ answers, saving });
};
}
As for explanation, he uses both class instances and a constructor (why?), in "modern" React, with class components you shouldn't use the constructor (instead, use class properties).
When a class is defined, the class instances evaluated first and then the constructor called, thats why he overrides the initial values in their instances.
Without answering the question in details I try to highlight the difference of react lifecycle thinking versus the conventional JS DOM manipulation.
If you work with React you manipulate the so called "virtual dom" with your JS code and not the real DOM.
How virtual dom works in nutshell: if ANY prop or state is changing of any component in the dom tree the whole child dom tree gets re-rendered. Lifecycles means how do you handle these continuous re-renderings within the components.
But no worries. It does not mean the whole DOM gets re-rendered every time. On top of this virtual dom there is a guy called ReactDOM which continuously compares the virtual-dom states and do the necessary changes only in the real DOM.

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.

React Native ScrollView scrollTo function is not working

I'm trying to make scroll to the top, if a certain condition is met, in the component's componentWillReceiveProps event ... but nothing happens:
componentWillReceiveProps(nextProps) {
// some code...
if (newQuery === query && this.scrollViewRef.current) {
console.log('Should scroll to top'); // << logs successfully
this.scrollViewRef.current.scrollTo({
x: 0,
y: 0,
duration: 500,
animated: true,
});
}
}
Code snippet of how I created ref for the scrollView:
class SearchResult extends React.Component {
constructor(props) {
super(props);
this.scrollViewRef = React.createRef();
}
//...
}
render method:
render () {
return (
<ScrollView
ref={this.scrollViewRef}
contentContainerStyle={{ marginVertical: 8 }}
>
...
)
}
I also tried to scroll manually via a button press ... doesn't work as well
Any help ?
I figured this out ...
The scrollView worked perfectly in an isolated env ( a brand new project ) ...
I thought the issue could be in the container of that scrollview ... and I found that the parent component has also a ScrollView ... once i removed it, everything worked perfectly.
For those people who use useRef() method and gets 'xRef.scrollTo' is not a function error, try to use it like xRef.current.scrollTo({[YOUR_PARAMS]}).
I didn't know this current thing and was getting crazy.
React Native docs say:
Note: The weird function signature is due to the fact that, for historical reasons, the function also accepts separate arguments as an alternative to the options object. This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.
Maybe try scrollToOffset method, if you are also using FlatList with ScrollView?

Migrating away from componentWillReceiveProps

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

How do properly use Onsen's Navigator in React?

I'm trying to implement a simple Onsen Navigator in React.
So far I'm receiving an error 'route is not defined' and I was looking through the examples & docs but I only saw the initialRoute prop was provided, how & where does the route prop generated or something? Cause it seems like its not specified.
Here is my the code of my component:
import React, {PropTypes} from 'react';
import ons from 'onsenui';
import * as Ons from 'react-onsenui';
class SignUp extends React.Component {
constructor(props) {
super(props);
this.state = {
index : 0
};
this.renderPage = this.renderPage.bind(this);
this.pushPage = this.pushPage.bind(this);
}
pushPage(navigator) {
navigator.pushPage({
title: `Another page ${this.state.index}`,
hasBackButton: true
});
this.setState({index: this.state.index++});
}
renderPage(route, navigator) {
return (
<Ons.Page key={route.title}>
<section style={{margin: '16px', textAlign: 'center'}}>
<Ons.Button onClick={this.pushPage}>
Push Page
</Ons.Button>
</section>
</Ons.Page>
);
}
render() {
return (
<Ons.Page key={route.title}>
<Ons.Navigator
renderPage={this.renderPage}
initialRoute={{
title: 'First page',
hasBackButton: false
}}
/>
</Ons.Page>
);
}
};
SignUp.propTypes = {
'data-pageName': PropTypes.string.isRequired
};
export default SignUp;
Is this the right syntax in ES6? Have I missed something?
When using Ons.Navigator in react the two required properties are:
initialRoute - it should be an object.
renderPage - method which receives 2 arguments - route and navigator. The route should be an object similar to the initialRoute one. You provide that object when you are calling pushPage and similar methods.
It seems that you already know these 2, but there still 2 other things which you need to be careful about. They are not directly onsen related, but come up a lot when using react in general.
Whenever you have a list of dom elements (for example an array of Ons.Page tags) each of those should have a unique key property.
Whenever you use a method you need to make sure you are binding it if you need some extra arguments.
It seems you also know these two. So the only thing left is to make sure you follow them.
Your syntax is correct - the only thing missing is the route variable in SignUp.render. Maybe you originally copied the renderPage method and that is how you have a leftover Ons.Page.
If you're not putting the SignUp component inside some other navigator, tabbar or splitter then you don't actually need the Ons.Page in its render method. Those are the only cases when they are needed. If you it happens to have one of those components as a parent then you can just specify the key.
PS: I think there should be a React Component Inspector (something like this) which you can install - then I think you may be able to see the place where the error occurs. I think if you knew on which line the problem was you would have been able to solve it. :)
For me, with the object I was passing to initialRoute(), it needed a props property, which itself was an object with a key property. See the before and after below.
Before fixing
render() {
return (
<Navigator
initialRoute={{component: DataEntryPage}}
renderPage={this.renderPage}
/>
);
}
}
This was causing the following console warning:
Warning: Each child in an array or iterator should have a unique "key" prop.
Check the render method of `Navigator`.
After fixing
render() {
return (
<Navigator
initialRoute={{component: DataEntryPage, props: {key: 'DataEntryPage'}}}
renderPage={this.renderPage}
/>
);
}
}
Notice that the difference I needed to make was the addition of , props: {key: 'DataEntryPage'}.
Feel free to check out this medium article for more information.

Resources