I want to build Isomorphic react
+ react-router
application and after a few days googling, now I can achieve isomorphic application that only handles GET request.
Here's what I've done so far:
react-router
to handle all requestreact-router
will call fetchData
functions that resides in each React View that matches the route.string
string
and data fetched before as global variable window.__STATE__
into HTML and deliver the HTML to the clientwindow.__STATE__
as the props of our React App, and React will not re-render because the state is the sameThe problem is it will not work with POST/PUT/DELETE/WHATEVER request. When handling GET request, react-router
have information about params
and query
. For example if we have a route: /user/:uid
and client request this url: /user/1?foo=bar
, then params
would be: {uid: 1}
and query
would be {foo: 'bar'}
react-router
then can pass it down to fetchData
function so it will know to fetch user with uid
of 1 and do whatever with foo
query.
While in POST request, react-router
doesn't know about the POST parameters. On Server, of course we could pass the POST parameters to fetchData
function, but what about the Client? It doesn't know what the POST parameters are.
Is there a way that the server could tell the Client about the POST parameters? Below is an example of my Login View. I want when user submit the form, the server will render error message on error, or redirect it to dashboard on success.
fetchData.js
import whenKeys from 'when/keys';
export default (authToken, routerState) => {
var promises = routerState.routes.filter((match) => {
return match.handler.fetchData;
}).reduce((promises, match) => {
promises[match.name] = match.handler.fetchData(authToken, routerState.params, routerState.query);
return promises;
}, {});
return whenKeys.all(promises);
}
server.js
...
app.use((req, res) => {
const router = Router.create({
routes,
location: req.originalUrl,
onError: next,
onAbort: (abortReason) => {
next(abortReason);
}
});
router.run((Handler, state) => {
fetchData(authToken, state).then((data) => {
// render matched react View and generate the HTML
// ...
})
});
});
...
login.jsx
import React from 'react';
import DocumentTitle from 'react-document-title';
import api from './api';
export default class Login extends React.Component {
constructor(props) {
super(props);
// how to fill this state with POST parameters on error?
// how to redirect on success?
// and remember that this file will be called both from server and client
this.state = {
error: '',
username: '',
password: ''
};
}
// I saw some people use this function, but it'll only work if
// the form's method is GET
static willTransitionTo(transition, params, query) {
// if only we could read POST parameters here
// we could do something like this
transition.wait(
api.post('/doLogin', postParams).then((data) => {
transition.redirect(`/dashboard`);
});
);
}
render() {
return (
<DocumentTitle title="Login">
<div className="alert alert-danger">{this.state.error}</div>
<form method="post">
<input type="text" name="username" value={this.state.username} onChange={this._onFieldChange('username')} placeholder="Username" /><br />
<input type="password" name="password" value={this.state.password} onChange={this._onFieldChange('password')} placeholder="Password" /><br />
<button type="submit">Login</button>
</form>
</DocumentTitle>
);
}
_onFieldChange(name) {
var self = this;
return (e) => {
e.preventDefault();
var nextState = {};
nextState[name] = e.target.value;
self.setState(nextState);
}
}
}
On the client side, you get POST data by extracting values from your form inputs in a way which corresponds to what you would have received on the server had the form been submitted normally.
So now you have your POST data, but you still have the problem that there's no way to feed the POST data into your transition hooks in React Router 0.13.x and earlier. I created a pull request for this feature which has now been closed because it was included as part of the rewrite for the upcoming v1.0 release.
The gist of it is that locations now have a state object for squireling away any extra data you need about the current request/transition (the two are analagous) being handled:
req.body
transitionTo()
.Now your transition hook is capable of receiving the same form data in both environments. If things go well, great! If things don't go well, you need a way to pass errors and re-render the form again. New state object to the rescue again! Use transition.redirect()
and pass both input data and errors and you now have everything you need to render on both sides.
I'm not going into more specific detail right now because v1.0 is still in beta and v0.13.x doesn't have the necessary API to do this anyway, but I have a repository which uses the pull request above to implement this workflow with 0.13.x which you could look at in the meantime:
Here are some rough flow diagrams of the process, too:
I've also created a few reusable modules related to this scenario:
<AutoForm>
, which you can use instead of <form>
to receive all the data from a form's inputs as an argument to its onSubmit
handler<form>
what React Router's <Link>
is to <a>
- it handles triggering a transition to the given action
, passing method
and body
(form data) state - this will be updated for v1.0 soon.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With