React - this.state is empty in render when getInitialState is properly defined - javascript

I'll try my best to explain the scenario, as making a fiddle of the smallest possible case might be hard. Essentially, I have a SPA using React-Router. I'm currently getting a strange behavior in specifically one version of Firefox (31.4esr).
I have two sidebar icons which trigger a change in routes, navigating to a new page. On occasion when I switch quickly between them, I'm getting an error that this.state.list is undefined(this is a list that I populate a dropdown with).
The issue is, upon debugging, console.log(this.state) is returning an empty object just before the call (that errors) to this.state.list happens in my render method. However, I have list defined in getInitialState (along with a bunch of other state variables) and so this.state definitely shouldn't be empty.
The only thing I could think of that would be causing this is if due to the quick switching there is some confusion with mounting/unmounting of components and my component still thinks it is mounted, so skips the getInitialState and goes ahead and tries to render. Either that or some bug in React-Router.
Any thoughts? Thanks in advance for the help!
Nick
P.S I should reiterate this also only occurs very rarely during quick switching, and normally the componentDidMount -> getInitialState -> render occurs as expected, so it is not simply an error in how my getInitialState is written etc.
Edit: Using React 0.13.3 and React router 0.13.3
Edit 2: Here is the stripped down version of the lifecycle methods, very basic.
getInitialState: function() {
return { list: listStore.getList("myList") || [] }
},
render: function() {
var newList = [];
//this is the line that errors with this.state.list is undefined
this.state.list.forEach(function(listItem) {
...
}
return (
<div>
<OtherComponent newList={newList} />
</div>
)
};
When putting console.log in componentWillMount (just attaches store listeners), getInitialState, and render, I get output like this when the error occurs:
"Setting initial state"
"2,3" //This is this.state.list in componentWillMount
"2,3" //This is this.state.list in initial Render
Object { } //This is this.state the next time it gets called in render :S.

you will have to use mixin like this:
var state = {
getInitialState: function() {
return { list: listStore.getList("myList") || [] }
}
}
var nameRouter = React.createClass({
mixins : [state],
// more content
})
this is because react-router ignore the getInitialState that was definite

Related

Best way to run a function when page refresh

I am trying to call a function when the page is refreshed. I adding a state if the page is rendered with the data I got from my backend end but I get an warning message "Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state." Even though it works fine (except with the warning message), I dont think this is the best, most efficient way to do it?
If this is the best, most efficient way, how do I fix the waring message?
function Demo() {
constructor(){
this.state = {
username: "unknown",
rendered: false,
}
this.renderUserProfile = this.renderUserProfile.bind(this);
}
update(){
//code to retrieve data from backend node.js *
this.setState({ username: data });
this.setState({ rendered: true });
}
render(){
if (!this.state.rendered) {
this.update();
}
return (<p>demo</p>)
}
}
Thank you for your help!
Do never change state inside render, because every state (or prop) change will call render again. That is what the warning is telling you: you risk having infinite loops.
There is no need of a state param for "rendered", because your component will call render anyway as soon as this.setState({username: data}); executes. If you want something to happen then, add it in update just after the setState line.
Now let's imagine that you still really want it. If you don't want your component to render when the rendered state changes, then just don't use the React Component state, but any standard class attribute:
class MyComponent extends React.Component {
rendered = false
...
render() {
this.rendered = true
....
}
}
Just be aware that this looks super wrong (and useless) since it tries to go around what the React framework is good at.
Finally, from this code there is no way to know how you intend you have new data coming in. If it is an Ajax call, then you will call this.update with that data in the callback of your Ajax call - certainly not in render.

NextJS - can't identify where state change is occurring on page component when using shallow Router push

I have an app. that uses NextJS. I have a page that looks like the following:
import React from 'react'
import { parseQuery } from '../lib/searchQuery'
import Search from '../components/search'
class SearchPage extends React.Component {
static getInitialProps ({ query, ...rest }) {
console.log('GET INITIAL PROPS')
const parsedQuery = parseQuery(query)
return { parsedQuery }
}
constructor (props) {
console.log('CONSTRUCTOR OF PAGE CALLED')
super(props)
this.state = props.parsedQuery
}
render () {
return (
<div>
<div>
<h1>Search Results</h1>
</div>
<div>
<h1>DEBUG</h1>
<h2>PROPS</h2>
{JSON.stringify(this.props)}
<h2>STATE</h2>
{JSON.stringify(this.state)}
</div>
<div>
<Search query={this.state} />
</div>
</div>
)
}
}
export default SearchPage
getInitialProps is ran for SSR - it receives the query string as an object (via Express on the back end) runs it through a simple 'cleaner' function - parseQuery - which I made, and injects it into the page via props as props.parsedQuery as you can see above. This all works as expected.
The Search component is a form with numerous fields, most of which are select based with pre-defined fields and a few a number based input fields, for the sake of brevity I've omitted the mark up for the whole component. Search takes the query props and assigns them to its internal state via the constructor function.
On changing both select and input fields on the Search component this code is ran:
this.setState(
{
[label]: labelValue
},
() => {
if (!this.props.homePage) {
const redirectObj = {
pathname: `/search`,
query: queryStringWithoutEmpty({
...this.state,
page: 1
})
}
// Router.push(href, as, { shallow: true }) // from docs.
this.props.router.push(redirectObj, redirectObj, { shallow: true })
}
}
)
The intention here is that CSR takes over - hence the shallow router.push. The page URL changes but getInitialProps shouldn't fire again, and subsequent query changes are handled via componentWillUpdate etc.. I confirmed getInitialProps doesn't fire again by lack of respective console.log firing.
Problem
However, on checking/unchecking the select fields on the Search component I was surprised to find the state of SearchPage was still updating, despite no evidence of this.setState() being called.
constructor isn't being called, nor is getInitialProps, so I'm unaware what is causing state to change.
After initial SSR the debug block looks like this:
// PROPS
{
"parsedQuery": {
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
Then after checking a select field in Search surprisingly it updates to this:
// PROPS
{
"parsedQuery": {
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
I can't find an explanation to this behaviour, nothing is output to the console and I can't find out how to track state changes origins via dev. tools.
Surely the state should only update if I were to do so via componentDidUpdate? And really shouldn't the parsedQuery prop only ever be updated by getInitialProps? As that's what created and injected it?
To add further confusion, if I change a number input field on Search (such as lowPrice), the URL updates as expected, but props nor page state changes in the debug block. Can't understand this inconsistent behaviour.
What's going on here?
EDIT
I've added a repo. which reproduces this problem on as a MWE on GitHub, you can clone it here: problem MWE repo.
Wow, interesting problem. This was a fun little puzzle to tackle.
TL;DR: This was your fault, but how you did it is really subtle. First things first, the problem is on this line:
https://github.com/benlester/next-problem-example/blob/master/frontend/components/search.js#L17
Here in this example, it is this:
this.state = props.parsedQuery
Let's consider what is actually happening there.
In IndexPage.getInitialProps you are doing the following:`
const initialQuery = parseQuery({ ...query })
return { initialQuery }
Through Next's mechanisms, this data passes through App.getInitialProps to be returned as pageProps.initialQuery, which then becomes props.initialQuery in IndexPage, and which is then being passed wholesale through to your Search component - where your Search component then "makes a copy" of the object to avoid mutating it. All good, right?
You missed something.
In lib/searchQuery.js is this line:
searchQuery[field] = []
That same array is being passed down into Search - except you aren't copying it. You are copying props.query - which contains a reference to that array.
Then, in your Search component you do this when you change the checkbox:
const labelValue = this.state[label]
https://github.com/benlester/next-problem-example/blob/master/frontend/components/search.js#L57
You're mutating the array you "copied" in the constructor. You are mutating your state directly! THIS is why initialQuery appears to update on the home page - you mutated the manufacturers array referenced by initialQuery - it was never copied. You have the original reference that was created in getInitialProps!
One thing you should know is that even though getInitialProps is not called on shallow pushes, the App component still re-renders. It must in order to reflect the route change to consuming components. When you are mutating that array in memory, your re-render reflects the change. You are NOT mutating the initialQuery object when you add the price.
The solution to all this is simple. In your Search component constructor, you need a deep copy of the query:
this.state = { ...cloneDeep(props.query) }
Making that change, and the issue disappears and you no longer see initialQuery changing in the printout - as you would expect.
You will ALSO want to change this, which is directly accessing the array in your state:
const labelValue = this.state[label]
to this:
const labelValue = [...this.state[label]]
In order to copy the array before you change it. You obscure that problem by immediately calling setState, but you are in fact mutating your component state directly which will lead to all kinds of weird bugs (like this one).
This one arose because you had a global array being mutated inside your component state, so all those mutations were being reflected in various places.

How to have multiple instances of getInitialState in Reactjs?

This is my code in React. I have multiple instances of getInitialState for different functions. However, I am finding that there may be a problem with getting them all to run this way.
getInitialState: function(){
if (localStorage.getItem('something'))
{return {signedin: true}}
else {return {signedin: false}}
},
getInitialState:function(){
return {error: true}
},
getInitialState:function(){
return {fliphouse: false}
},
To explain, the second instance of getInitialState, as is, does not work. That is, unless, I flip the order of the second and third getInitialState. When I do that, the code runs as it should. But I get the feeling React is trying to tell me something.
Is this normally the way to organize React code within a component?
You can define them all in the same object:
getInitialState() {
return {
signedIn: !!localStorage.getItem('something'),
error: false,
fliphouse: false
};
}
You can change them individually anywhere in the component (except in the render() function), e.g.:
this.setState({ error: true }) // causes a new render
This is not the correct way to put the initial state of the component. Initial state function is called only once when you component is about to mount; over here you set the state of the component(what variables or object properties you need) and then update them accordingly using setState in other methods like componentDidMount etc..
So in your question I would say put all 3 variables signedIn, error and fliphouse in the state object and then update this object. So a single initialState function would do.

Mounting Components in React Native

I am relatively new to JS and RN and I am currently working with an app where I have bumped in to some major issues regarding my handling of Components.
I've tried to run through the following guide: https://facebook.github.io/react/docs/component-specs.html as well as https://facebook.github.io/react/docs/advanced-performance.html but the latter one flies a bit over my head.
However, as I understand: componentWillMount fires whatever piece of code that is within before the render function is executed, and componentWillUnmount erases whatever it sais to forget. Or how can I specify?
My specific problem lies within the fact that I have three functions, one main and within main I have compOne and compTwo, where the two latter are called in the main component when pressing on a certain sub-navigator. This means that I have three instances of getInitialState whereas compOne and compTwo defines basically the same stuff but calls different parts of the server (hence the code is very much the same).
Also this issue resurfaces sometimes when I go between different frames, and return again to my home screen.
In my Home screen I have it like this:
var Home = React.createClass({
getInitialState: function() {
return {
componentSelected: 'One',
userName: "Loading...",
friendFeed: 'Loading...',
loaded: false,
loadedlocal: false,
};
},
componentWillMount: function() {
Method.getFriendFeed(this.props.tokenSupreme)
.then((res) => this.setState({
friendFeed: JSON.parse(res).friendPosts,
loaded: true,
}))
.catch((error) => console.log(error))
.done();
Method.getLocalFeed(this.props.tokenSupreme, )
.then((res) => this.setState({
localFeed: JSON.parse(res).friendPosts,
loadedlocal: true,
}))
.catch((error) => console.log(error))
.done();
},
Where I pass this.state.friedFeed to be a this.props.friendData in one of two components and vice versa for the localFeed.
Picking it up in my CompOne:
var ComponentOne = React.createClass({
getInitialState: function() {
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
return {
dataSource: ds.cloneWithRows(this.props.friendData),
};
},
render: function() {
if (!this.props.loaded) {
return this.renderLoadingView();
} else {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
style={styles.card} />
)
}
},
Followed by the renderRow function etc and the compTwo function is basically identical.
But my question is: How should I go about to unmount the component? If it even is what I want? Another frequently but not consequently occuring issue is the error of null is not an object (evaluating 'prevComponentInstance._currentElement' with reference to the _updateRenderedComponent hence my belief that I should go about some different method in mounting, unmounting and updating my components, or am I wrong?
After some browsing ill add another question to this, which might be the main question... Is it even possible for a RN app to handle mutiple listviews and mulitple fetchers in mutilple scenes?
In most situations, you do not need to be concerned about unmounting the components. When a React component is no longer needed, React in general just forgets about it, including its contents, props, state, etc. componentWillUnmount is typically reserved for things that are in a global state that, once the component is forgotten about would cause problems if they still existed.
The documentation on the page you linked to mentions cleaning up timers as an example. In Javascript, if you set a timer via setTimeout() / setInterval(), those timers exist in the global space. Now imagine you had a component that set a timer to modify some element on screen or potentially try to interact with a component, let's say 30 seconds in the future. But then the user navigates away from the screen/component, and because it is no longer on screen React forgets about it. However, that timer is still running, and may cause errors if it fires and it can't interact with that now-trashed component. componentWillUnmount gives you a chance to clear out that timer so weird side effects don't occur when it fires to interact with elements that no longer exist.
In your case you probably don't have anything that needs cleanup, as far as I can tell. You might want to clarify your question because you don't say what the trouble behavior is that you're seeing, but note also that getInitialState is only called the first time a component is created, and won't get called if only props change. So if the friendData is changing but the component stays on the screen, you will need to update your ds via a componentWillReceiveProps.
To your last question, yes it is certainly possible for React to handle multiple ListViews/fetches/etc.

React js - Disable render of a component in a mixin

I'm trying to develop a React mixin to check the user access level before rendering the component.
If the user doesn't have the permission to see the component, I would like to disable the rendering of the component.
I've been looking for something build in react to handle this but found nothing, so I did that:
var AuthentLevelMixin = {
componentWillMount: function() {
if(!Auth.check()) {
// Disable component render method
this.render = function () {
return false;
}
}
}
}
It works as expected but I feel like it's the "dirty way".
So my question is: what is the "React way" for doing the same as this snippet ?
For a mixin this is about the best you can do. It's just a simple early return in render.
var AuthentLevelMixin {
isAuthenticated: function(){
return Auth.check();
}
};
var C = React.createClass({
mixins: [AuthentLevelMixin],
render: function(){
if (!this.isAuthenticated()) return <div />;
return (
<div>...</div>
);
}
});
If you decide to go with your initial strategy (I don't recommend it), it just needs to be modified slightly:
// more explicit names are important for dirty code
var PreventRenderUnlessAuthMixin = {
componentWillMount: function() {
this._originalRender = this.render;
this._setRenderMethod();
},
componentWillUpdate: function(){
this._setRenderMethod();
}.
_emptyRender: function () {
return <span />;
},
_setRenderMethod: function(){
this.render = Auth.check() ? this._originalRender : this._emptyRender;
}
}
If you want to handle the authorization inside your mixin without adding logic to your component you are doing it the right way. BUT: Every component implementing this mixin should then be aware of what happens within this mixin. If the result you expect is, that nothing is rendered, then you are perfectly right with what you are doing. So if your way is resulting in simplicity it is the React-Way. And in my Opinion this is the case.
In the componentWillMount lifecycle event you will capture the moment right before rendering - which is a great time to prevent rendering. So I really dont see anything speaking against your code.
EDIT:
aproach of defining: "react way"
Once you have the same input resulting in the same output every time your code becomes predictable. With your code being predictable you achieve simplicity. These are terms used by Pete Hunt to describe the intentions of React. So therefor if you stay predictable and in result achieving simplicity you are doing it the react way.
In case of the above mixin both these rules apply and is therefor the "react way" in the definition I have provided above.
My advice here would be to not use a mixin. The best way to clean up your component is to remove this logic from the component, and simply not render the component based on the result of checking Auth.
The problem with this is that you have a component that is no longer consistent, because it depends on something other than its props. This doesn't really do much other than push the problem upwards, but it does allow you to have one more pure component.
I can see why the mixin is attractive though, so here's a simpler way of doing what you need that doesn't involve dynamically swapping the render method:
var PreventRenderUnlessAuthMixin = {
componentWillMount: function () {
var oldRender = this.render;
this.render = function () {
return Auth.check() ? this.render() : <div />
}.bind(this);
}
}

Categories

Resources