SetState fails in callback (via ComponentWillMount), on server only - reactjs

I need to render React components on the server for SEO. My component fetches data in ComponentWillMount, based on the query parameters - but on the server (Node 4.0.0), SetState fails in the request's callback. The error can be reproduced with a simpler setTimeout too, as in the code example below.
I have found numerous discussion on the web relating to complications between React and server-side rendering. I'm working on two work-around approaches:
removing all ajax requests from the server, instead rendering the result of the request directly into a global variable embedded in the first-serve HTML
moving the ajax request prior to initialization of the React components, on the server only (the request would still have to live in ComponentWillMount (or ComponentDidMount) for the client version.
Please let me know if there is an alternative or recommended approach instead.
var React = require('react');
// Reproduced in React 0.13.3 and 0.14.0-beta1
var ReactDOMServer = require("react-dom/server");
var A = React.createClass({
componentWillMount: function() {
var _this = this;
// for example an ajax call to fetch data based on request parameters:
setTimeout(function(err, res) {
// state is set based on results
_this.setState({ a: 1 });
}, 100);
},
render: function() {
return React.createElement('div', null);
}
});
ReactDOMServer.renderToString(React.createElement(A, null));
Error:
$ node index.js
/app/node_modules/react/lib/getActiveElement.js:25
return document.body;
^
ReferenceError: document is not defined
at getActiveElement (/app/node_modules/react/lib/getActiveElement.js:25:12)
at ReactReconcileTransaction.ReactInputSelection.getSelectionInformation (/app/node_modules/react/lib/ReactInputSelection.js:38:23)
at ReactReconcileTransaction.Mixin.initializeAll (/app/node_modules/react/lib/Transaction.js:168:75)
at ReactReconcileTransaction.Mixin.perform (/app/node_modules/react/lib/Transaction.js:135:12)
at ReactUpdatesFlushTransaction.Mixin.perform (/app/node_modules/react/lib/Transaction.js:136:20)
at ReactUpdatesFlushTransaction.assign.perform (/app/node_modules/react/lib/ReactUpdates.js:86:38)
at Object.flushBatchedUpdates (/app/node_modules/react/lib/ReactUpdates.js:147:19)
at Object.wrapper [as flushBatchedUpdates] (/app/node_modules/react/lib/ReactPerf.js:66:21)
at ReactDefaultBatchingStrategyTransaction.Mixin.closeAll (/app/node_modules/react/lib/Transaction.js:202:25)
at ReactDefaultBatchingStrategyTransaction.Mixin.perform (/app/node_modules/react/lib/Transaction.js:149:16)
Issue opened at https://github.com/facebook/react/issues/4873

Try moving the setState function in another method:
var React = require('react');
// Reproduced in React 0.13.3 and 0.14.0-beta1
var ReactDOMServer = require("react-dom/server");
var A = React.createClass({
stateChange: function( obj ){
setTimeout( this.setState( obj ), 100 );
},
componentWillMount: function() {
this.stateChange( {a: 1} );
},
render: function() {
console.log( this.state.a )
return React.createElement('div', null);
}
});
ReactDOMServer.renderToString(React.createElement(A, null));

Related

if(this.isMounted()) not being called?

Can you tell me why when I do this:
var SomeComponent = React.createClass({
getData: function(){
if (this.isMounted()){
var queryInfo = {
userId: sessionStorage.getItem("user_id"),
userRole: sessionStorage.getItem('user_role'),
aptId : this.props.params
}
io = io.connect();
io.emit('allTasks', queryInfo);
io.on('allTasksInfo', function(data){
reqwest({
url: '/apartment/tasks/address',
method: 'get',
xhrFields: {withCredentials: true},
crossOrigin: true
}).then(function(data){
this.setState({
dataSet: arr
})
}.bind(this));
}.bind(this));
}
},
componentDidMount: function(){
this.getData();
},
render: function(){...}
});
The code inside the if is executed, but I get the Uncaught Error: Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
But when I do this:
var SomeComponent = React.createClass({
getData: function(){
var queryInfo = {
userId: sessionStorage.getItem("user_id"),
userRole: sessionStorage.getItem('user_role'),
aptId : location.pathname.split("/")[4]
}
reqwest({
url:'/operation/staff',
method: 'get',
xhrFields: {withCredentials: true},
crossOrigin: true
}).then(function(data){
if(this.isMounted()){
this.setState({
operationStaff: data
})
}
}.bind(this));
}
componentDidMount: function(){
this.getData();
},
render: function(){...}
});
Everything is ok. Shouldn't the first just be executed when the component is mounted? What I am missing?
EDIT: I'm using react-router and express server with socket.io with server rendering (just the components, not the data - this I will fetch client side). After the answers, I can say:
The component is not unmounting
I can now tell that at first render, this warning doesn't appear even on second example:
https://drive.google.com/file/d/0B1rbX9C6kejlbWVKeTZ6WVdGN0E/view?usp=sharing
But if I change the url and get back to this path (and here yes, the component unmounts off course), the Ajax reqwest is being called 2 times
https://drive.google.com/file/d/0B1rbX9C6kejlUjFRYTBtejVLZGs/view?usp=sharing
This has something to do with the sockets implementation.
I will close this issue and open another regarding this. Thank you for the help.
Shouldn't the first just be executed when the component is mounted?
Yes, and it is (what makes you think it is not?).
However, the Ajax callback itself is executed some time in the future and at that moment, the component may already be unmounted.
In the first example, the test is useless since the component is always mounted after componentDidMount was called. In the second example, you are testing whether the component is mounted just before you call setState, which makes more sense.
Here is a simplified example:
var Hello = React.createClass({
getInitialState: function() {
return {name: 'foo'};
},
componentDidMount: function() {
console.log('mounted...');
setTimeout(function() {
// this works fine
console.log('updating state once...');
this.setState({
name: 'bar'
});
}.bind(this), 1000);
setTimeout(function() {
// this will throw
console.log('updating state twice...');
this.setState({
name: 'baz'
});
}.bind(this), 3000);
},
componentWillUnmount: function() {
console.log('unmounting...');
},
render: function() {
return <div>Hello {this.state.name}</div>;
}
});
React.render(
<Hello />,
document.getElementById('container')
);
setTimeout(function() {
React.unmountComponentAtNode(
document.getElementById('container')
);
}, 2000);
If you run it you will notice that the second timeout will generate the same error because it is called after the component was unmounted:
Console output:
mounted...
updating state once...
unmounting...
updating state twice...
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
DEMO: https://jsfiddle.net/pkzfbcr5/
I'm using react-router and express server with socket.io with server rendering (just the components, not the data - this I will fetch client side). After the answers, I can say:
The component is not unmounting
I can now tell that at first render, this warning doesn't appear even on second example: https://drive.google.com/file/d/0B1rbX9C6kejlbWVKeTZ6WVdGN0E/view?usp=sharing
But if I change the url and get back to this path (and here yes, the component unmounts off course), the Ajax reqwest is being called 2 times https://drive.google.com/file/d/0B1rbX9C6kejlUjFRYTBtejVLZGs/view?usp=sharing
This has something to do with the sockets implementation.
I will close this issue and open another regarding this. Thank you for the help.

Async Client Side Rendering React components using Dust JS Templates

I'm having a dust helper called {#component id="compId" path="path/to/react/component"}.
About React component:
The react component makes an ajax call in componentWillMount and calls forceUpdate.
psueodcode:
React.createClass(){
getInitialState : function() {
this.setState({response : "nothing"});
}
var response = "nothing";
componenetWillMount : function(){
//1.performs ajax call
//2.set state in ajax callback this.setState({response : "success"});
//3. calls this.forceUpdate()
},
render : function() {
//returns response text
}
}
About Dust Helper
component is added to dust helper and chunk is written with response from react.
dust.helpers.component = function(chunk, context, bodies, param) {
return chunk.map(function(chunk) {
var domElement = document.getElementById(param.id);
if (!domElement) {
console.log("Document does not present for " + param.id);
domElement = document.createElement(param.id);
}
require([param.path], function(widgetModule) {
console.log("Rendering " + param.path);
var widget = React.createFactory(widgetModule);
React.render(widget(null), domElement, function() {
chunk.end(domElement.innerHTML);
});
});
});
};
Response In Browser:
I'm able to see the response as "nothing" instead of "success". The issue is The render callback is called after the template is rendered on the browser hence the ajax response is not updated.
How would you make dust to listen for changes to a div after the page is rendered? Is it possible in dust, similar to how react finds diff between dom and virtual dom and decides to re-render. I'm using react 0.13.3 and dust 2.7.1

can i use action in Flux to control routes, HOW?

I am writing a authentication module in Flux, actions : auth,auth_success,auth_error. I am thinking when action auth_error occur, the router will go to '/login'. When action, action, auth_success occur, the router will go to '/dashboard'.
But it seems to be wrong because action only goes to dispatcher. I don't know how to do route the callbacks. Maybe check the store value?
You have to mixin your React class with Route.Navigation object, for instace
/** #jsx React.DOM */
var React = require('react');
var Router = require('react-router')
, Navigation = Router.Navigation;
var UserStore = require('user-store');
var YourClass = module.exports = React.createClass({
mixins:[Navigation], //This is very important
getInitialState: function() {
return {};
},
componentWillMount: function(){
UserStore.addChangeListener(this._method);
},
componentWillUnmount: function(){
UserStore.removeChangeListener(this._method);
},
render: function() {
return (
<div>
</div>
);
},
_method: function() {
// From here you can call methods of Navigator Object
this.transitionTo('SomeRouteName'); //This method will render the specified <Route>
}
});
For further information you can check
https://github.com/rackt/react-router/blob/master/docs/api/mixins/Navigation.md
In order to change the route and according to flux architecture, you should call transitionTo from a callback of some User Store you should have.
I added an example to the code, you may customise it to your specific case.
Happy coding!

Cannot read property of state object when testing React component that renders state variable in render

I'm trying to test a component that has a value from a state object displayed in it's render().
I've simplified the component in question in to this simple Test component for reproduction. I'm using React 0.12.2.
I am populating my "report" in getIntitialState's call to getStateFromStores(). In testing though this value is empty and is what I think is leading to the error.
Certainly a conditional checking to see if this.state.report is defined would work, but it seems a bit much to have to put conditionals on all variables printed in a render() that are populated via state.
Test Component
var React = require('react');
var AppStore = require('../stores/AppStore');
function getStateFromStores() {
return {
report: AppStore.getCurrentReport(),
};
}
var Test = React.createClass({
getInitialState: function() {
return getStateFromStores();
},
render: function(){
return (
<div>
// This call to the report.id on state seems to be the issue
{this.state.report.id}
</div>
);
}
});
module.exports = Test;
The Test
jest.dontMock('../Test');
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
var Test = require('../Test');
describe("Test", function() {
it("should render Test", function() {
var test = TestUtils.renderIntoDocument(<Test />);
expect(test).toBeDefined();
});
});
Ideally, I'd like to pre-populate the state of the component before renderIntoDocument() is called in the test as that is where it is failing.
I receive this failure:
● Test › it should render Test
- TypeError: Cannot read property 'id' of undefined
at React.createClass.render (/Users/kevinold/_development/app/assets/javascripts/_app/components/Test.jsx:20:26)
at ReactCompositeComponentMixin._renderValidatedComponent (/Users/kevinold/_development/node_modules/react/lib/ReactCompositeComponent.js:1260:34)
at wrapper [as _renderValidatedComponent] (/Users/kevinold/_development/node_modules/react/lib/ReactPerf.js:50:21)
at ReactCompositeComponentMixin.mountComponent (/Users/kevinold/_development/node_modules/react/lib/ReactCompositeComponent.js:802:14)
at wrapper [as mountComponent] (/Users/kevinold/_development/node_modules/react/lib/ReactPerf.js:50:21)
at ReactComponent.Mixin._mountComponentIntoNode (/Users/kevinold/_development/node_modules/react/lib/ReactComponent.js:405:25)
at ReactReconcileTransaction.Mixin.perform (/Users/kevinold/_development/node_modules/react/lib/Transaction.js:134:20)
at ReactComponent.Mixin.mountComponentIntoNode (/Users/kevinold/_development/node_modules/react/lib/ReactComponent.js:381:19)
at Object.ReactMount._renderNewRootComponent (/Users/kevinold/_development/node_modules/react/lib/ReactMount.js:312:25)
at Object.wrapper [as _renderNewRootComponent] (/Users/kevinold/_development/node_modules/react/lib/ReactPerf.js:50:21)
at Object.ReactMount.render (/Users/kevinold/_development/node_modules/react/lib/ReactMount.js:381:32)
at Object.wrapper [as render] (/Users/kevinold/_development/node_modules/react/lib/ReactPerf.js:50:21)
at Object.ReactTestUtils.renderIntoDocument (/Users/kevinold/_development/node_modules/react/lib/ReactTestUtils.js:48:18)
at Spec.<anonymous> (/Users/kevinold/_development/app/assets/javascripts/_app/components/__tests__/Test-test.js:9:26)
at jasmine.Block.execute (/Users/kevinold/_development/node_modules/jest-cli/vendor/jasmine/jasmine-1.3.0.js:1065:17)
at jasmine.Queue.next_ (/Users/kevinold/_development/node_modules/jest-cli/vendor/jasmine/jasmine-1.3.0.js:2098:31)
at null._onTimeout (/Users/kevinold/_development/node_modules/jest-cli/vendor/jasmine/jasmine-1.3.0.js:2088:18)
I am not sure how to preload state for this component, which should solve the issue, prior to the renderIntoDocument() call in my test.
I've also considered trying to mock getIntitialState() for this component, but there has to be a better way.
Any ideas on how to test this?
I ended up solving this by mocking out the AppStore.getCurrentReport() method like so:
jest.dontMock('../Test');
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
var Test = require('../Test');
var AppStore = require('../stores/AppStore');
describe("Test", function() {
it("should render Test", function() {
// Mock the return value from the method in the store that populates the value in getInitialState()
AppStore.getCurrentReport.mockReturnValue({id: 1, title: 'Test Rpt'});
var test = TestUtils.renderIntoDocument(<Test />);
expect(test).toBeDefined();
});
});

How to redirect after success from ajax call using React-router-component?

I am building a application using Facebook flux architecture of React JS. I have build the basic part of app where I have a login form. I am fetching the the result from node server to validate user at the store, I am getting the result from server, Now I got stuck that how can I redirect the user to home page after success.
I have read about the react router component and I am able to redirect at the client side but not able to redirect at the time of fetching result from ajax in Store. Please help me.
You need to use the transitionTo function from the Navigation mixin: http://git.io/NmYH. It would be something like this:
// I don't know implementation details of the store,
// but let's assume it has `login` function that fetches
// user id from backend and then calls a callback with
// this received id
var Store = require('my_store');
var Router = require('react-router');
var MyComponent = React.createClass({
mixins: [Router.Navigation],
onClick: function() {
var self = this;
Store.login(function(userId){
self.transitionTo('dashboard', {id: userId});
});
},
render: function() {
return: <button onClick={this.onClick}>Get user id</button>;
}
});
It worked for me when I added to the react element properties a require for the router and used the router like this:
// this is the redirect
this.context.router.push('/search');
// this should appear outside the element
LoginPage.contextTypes = {
router: React.PropTypes.object.isRequired
};
module.exports = LoginPage;
This should work
var Store = require('Store');
var Navigatable = require('react-router-component').NavigatableMixin
var LoginComponent = React.createClass({
mixins: [ Navigatable ],
onClick: function() {
Store.login(function(userId){
this.navigate('/user/' + userId)
}.bind(this));
},
render: function() {
return <button onClick={this.onClick}>Login</button>;
}
});

Resources