I think I'm conceptually missing something with server-side rendering using React.js
Assume I want to create a page to display items from a server-side DB, with an input field to filter them.
I want a page:
/items?name=foobar
Assume I have a public REST API to query the items on the client side.
Conceptually, what I want to do at first request (GET /items?name=foobar
) is :
initialName
)So I tried this:
// A stateful component, maintaining the value of a query field
var ItemForm = React.createClass({
getInitialState : function () {
return {
name : this.props.initialName
};
},
handleInputValueChanged : function() {
var enteredName = this.refs.query.getDOMNode().value.trim();
this.props.onNameChanged(enteredName);
},
render : function () {
return React.DOM.form({
children : [
React.DOM.label({
children : "System name"
}),
React.DOM.input({
ref : "query",
value : this.state.name,
onChange : this.handleInputValueChanged
})
]
});
}
});
As I understand it, I need a simple component to display the list, receiving it as a 'prop':
// A stateless component, displaying a list of item
var ItemList = return React.createClass({
propTypes : {
items : React.PropTypes.array
},
render : function () {
return React.DOM.ul({
children : _.map(this.props.items, function (item) {
return React.DOM.li({
children : [
React.DOM.span({
children : ["Name : ", item.name].join(" ")
})]
});
})
});
}
});
I tried this:
// A stateful react component, maintaining the list of items
var ItemPage = React.createClass({
getInitialState : function () {
// ?????
// This is where I'm sure the problem lies.
// How can this be known both on server and client side ?
return {
items : this.props.initialItems || []
};
},
queryItems : function (enteredName) {
var self = this;
// The field was emptied, we must clear everything
if (!enteredName) {
this.setState({
items : []
});
} else {
// The field was changed, we want to do a query
// then change the state to trigger a UI update.
// The query code is irrelevant, I think.
doQuery(enteredName).then(function (items) {
self.setState({
items : items
});
});
}
},
render : function () {
// I don't want to display the same view
// if there is no results.
// This uses a 'state' property of the page
var results = null;
if (_.isEmpty(this.state.items)) {
results = React.DOM.div({
ref : "results",
children : "No results"
});
} else {
results = ItemListView({
// Here items is a 'prop', the ItemList is technically
// stateless
items : this.state.items
});
}
return React.DOM.div({
children : [
ItemForm({
initialName : this.props.initialName,
onNameChanged : this.queryItems
}),
results
]
});
}
});
That's where I'm stuck. I can render things on the server side using something like:
var name = // The name from the query parameters
var items = doQueryOnServerSide(name);
React.renderComponentAsString(ItemPage({
initialName : name,
initialItems : items
});
But when I try to do write the client side javascript, what should I do ? I know where I want my dom to be rendered, but what initial props should I pass to the react component?
React.renderComponent(ItemPage({
initialName : ??? // Read the name parameter from URL ?
initialItems : ??? // I don't know yet, and I don't want to make an API call until the user entered something in the input box
});
Most attempts I have tried ends up with the DOM being 'erased' on the client-side, with this message:
React attempted to use reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server.
Note that only the markup of my ItemList component is erased, so I suppose it's an implementation problem of my component, but I don't really know where to start.
Am I on the right track? Or do I miss something entirely?
When using server rendering, you should always pass down the same props that you used to render the component on the server. In this case, you need to pass down the same initialItems prop in order for React.renderComponent
to pick up the server-rendered markup (by simply JSONifying the props and putting it in the call to renderComponent
).
Your general structure of reading from initialItems when specified makes sense to me. Doing so allows you to either make a component with preloaded data or one that has none. What you need to set the initial state to depends on what you want to show in cases where you're rendering a brand-new component from scratch. (If you're always picking up server-rendered markup then you can simply always pass the initialName
and initialItems
props.)
Hope that makes sense.