sudo a2enmod rewrite && sudo service apache2 restart
or
sudo a2enmod rewrite && sudo /etc/init.d/apache2 restart
sudo a2enmod rewrite && sudo service apache2 restart
sudo a2enmod rewrite && sudo /etc/init.d/apache2 restart
Question
, and we want to call an API to obtain the current questions
and then render the HTML.questions
reducerloadQuestions
action. This will put the data into the questions
reducer after the question has been successfully fetched from the API.ReactDOM.render((
<Provider store={store}>
<Question />
</Provider>
), document.getElementById('root'));
containers/Question.js
would look like:import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { loadQuestions } from 'actions/questions';
import _ from 'lodash';
class Question extends Component {
componentDidMount() {
this.props.loadQuestions();
}
render() {
return (
<p>
<h2>Question</h2>
{
_.map(this.props.questions, (q)=> {
return (
<p key={q.id}> { q.content }</p>
);
})
}
</p>
);
}
}
function mapStateToProps (state) {
return { questions: state.questions };
}
export { Question };
export default connect(mapStateToProps, { loadQuestions })(Question);
actions/questions.js
would look like:export const LOADED_QUESTIONS = 'LOADED_QUESTIONS';
export function loadQuestions() {
return function(getState, dispatch) {
request.get('http://localhost:3000/questions')
.end(function(err, res) {
if (!err) {
dispatch({ type: LOADED_QUESTIONS, response: res.body });
}
})
}
}
Question
mounts, it will send an API to the server to pull the necessary data and then update the view.Question
should be quite straightforward, but things get a bit more complex when you want to do server-side rendering.Question
component above, the data entry point will be inside componentDidMount()
. In most cases, we need a router to control the relationship between the URL and its respective component. My personal approach is to stick a static method fetchData()
into every routing’s leaf node (if the routing is nested, I’d put it in the innermost one). By doing so, we can use react-router to match the URL to the component we’ll be rendering, and thus calling fetchData()
and obtaining the data entry point.fetchData()
used above to return a promise, and once this promise is resolved, it would mean that the asynchronous API call has completed and that the data is ready.import { RoutingContext, match } from 'react-router'
import createMemoryHistory from 'history/lib/createMemoryHistory';
import Promise from 'bluebird';
import Express from 'express';
let server = new Express();
server.get('*', (req, res)=> {
let history = createMemoryHistory();
let store = configureStore();
let routes = crateRoutes(history);
let location = createLocation(req.url)
match({ routes, location }, (error, redirectLocation, renderProps) => {
if (redirectLocation) {
res.redirect(301, redirectLocation.pathname + redirectLocation.search)
} else if (error) {
res.send(500, error.message)
} else if (renderProps == null) {
res.send(404, 'Not found')
} else {
let [ getCurrentUrl, unsubscribe ] = subscribeUrl();
let reqUrl = location.pathname + location.search;
getReduxPromise().then(()=> {
let reduxState = escape(JSON.stringify(store.getState()));
let html = ReactDOMServer.renderToString(
<Provider store={store}>
{ <RoutingContext {...renderProps}/> }
</Provider>
);
res.render('index', { html, reduxState });
});
function getReduxPromise () {
let { query, params } = renderProps;
let comp = renderProps.components[renderProps.components.length - 1].WrappedComponent;
let promise = comp.fetchData ?
comp.fetchData({ query, params, store, history }) :
Promise.resolve();
return promise;
}
}
});
});
index.ejs
would be:<!DOCTYPE html>
<html>
<head>
<title>Redux real-world example</title>
</head>
<body>
<p id="root"><%- html %></p>
<script type="text/javascript" charset="utf-8">
window.__REDUX_STATE__ = '<%= reduxState %>';
</script>
<script src="http://localhost:3001/static/bundle.js"></script>
</body>
</html>
fetchData()
to containers/Question.js
like this:class Question extends Component {
static fetchData({ store }) {
// return a promise here
}
// ...
}
fetchData()
(loadQuestions()
) and return a promise at the same time?fetchData()
.middleware/api.js
) would look like:import { camelizeKeys } from 'humps';
import superAgent from 'superagent';
import Promise from 'bluebird';
import _ from 'lodash';
export const CALL_API = Symbol('CALL_API');
export default store => next => action => {
if ( ! action[CALL_API] ) {
return next(action);
}
let request = action[CALL_API];
let { getState } = store;
let deferred = Promise.defer();
// handle 401 and auth here
let { method, url, successType } = request;
superAgent[method](url)
.end((err, res)=> {
if ( !err ) {
next({
type: successType,
response: res.body
});
if (_.isFunction(request.afterSuccess)) {
request.afterSuccess({ getState });
}
}
deferred.resolve();
});
return deferred.promise;
};
actions/questions.js
:export const LOADED_QUESTIONS = 'LOADED_QUESTIONS';
export function loadQuestions() {
return {
[CALL_API]: {
method: 'get',
url: 'http://localhost:3000/questions',
successType: LOADED_QUESTIONS
}
};
}
fetchData()
in containers/Question.js
would become like this:import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { loadQuestions } from 'actions/questions';
import _ from 'lodash';
class Question extends Component {
static fetchData({ store }) {
return store.dispatch(loadQuestions());
}
//...
}
http://localhost://3000/q/1/hello
😀 For an example of how we’ve used this code, check out our side project, Refactor.io! It’s a simple tool for developers to share code instantly for refactoring and code review.