Universal React App Starter featuring Redux Saga. Heavily modified version of React Redux Universal Hot Example combined with Redux Saga real-world example.
$ git clone https://github.com/xkawi/react-universal-saga
$ cd react-universal-saga && npm install
$ npm run dev
(run in development mode)
Live Demo: react-universal-saga.herokuapp.com
UPDATE: Checkout react-universal-saga-modular for a different structure of react-universal-saga that's more modular, scalable, and maintainable. 😄
- Universal rendering, with Client and Server Side Data Fetching
- React - latest version
^15.4.2
- Redux's futuristic Flux implementation
- Redux Saga to handle all of your Side Effects logic in a central place
- React Router
- Express
- Babel for ES6 and ES7 magic
- Webpack for bundling
- Webpack Dev Middleware
- Webpack Hot Middleware
- Redux Dev Tools for next generation DX (developer experience). Watch Dan Abramov's talk.
- ESLint to maintain a consistent code style
- redux-form to manage form state in Redux
- PostCSS, style-loader, sass-loader and less-loader to allow import of stylesheets in plain css, sass and less,
- bootstrap-loader and font-awesome-webpack to customize Bootstrap and FontAwesome
- react-helmet to manage title and meta tag information on both server and client
- webpack-isomorphic-tools to allow require() work for statics both on client and server
- Jest to allow writing unit tests for the project.
- Refer to
package.json
for more details
Core Concepts to learn if you are new to ReactJS and this repo:
- Dumb Components (Component)
- Smart Component (Container)
- Actions - define available action types
- Reducers - given previous state and an action, create a new state
- Sagas - manage or orchestrate event/action dispatching flow (called Side Effects)
-
[Hands-on] Get started with ReactJS
-
[Reading] Thinking in React way
-
[Reading] Flux Pattern (video)
-
[Hand-on] Flux
-
[Reading] Why someone created a tool called Redux
-
[Reading] More about Redux
-
[Hands-on] Getting started with Redux
-
[Reading] Redux best practices
-
[Reading + Hands-on] Redux Saga
-
Everything - Awesome Redux
Here is a suggested development workflow that works for me:
-
Define the routes, which helps to reason about the needed containers and components.
-
Implement dump components + props (state and actions).
- Each component should define
.propTypes
for prop's validation, which will throw a warning if the container that uses it do not provides all the props that the component need.
- Each component should define
-
Often, a container = a route page; a container is also where you import necessary components, actions and states (pass down to component) to form a single page.
- if a container wants to "pre-populate" data during server rendering process, assign
Container.preload
with sagas that fetches necessary data from the API server. (refer tocontainers/App.js
for an example)
- if a container wants to "pre-populate" data during server rendering process, assign
-
Implement necessary Actions as you define needed containers and components (PS: this implementation is independent from previous steps). It often helps maintainability and readability if action methods are categorized into 2:
- Actions that manipulate store directly through reducers (e.g.
actions/index.js > createRequestTypes
method), often to update the state and trigger re-render the components. - "LOAD" or "TRIGGER" actions for starting a saga routine/daemon, often to make network call.
- Actions that manipulate store directly through reducers (e.g.
-
Implement Reducers. As you implemented the actions, you will have better idea on how to modify the states for different actions.
Redux Devtools are enabled by default in development.
- CTRL+H Toggle DevTools Dock
- CTRL+Q Move DevTools Dock Position
- see redux-devtools-dock-monitor for more detailed information.
If you have the Redux DevTools chrome extension installed it will automatically be used on the client-side instead.
If you want to disable the dev tools during development, set __DEVTOOLS__
to false
in /webpack/dev.config.js
.
DevTools are not enabled during production.
$ npm run build
$ npm run start
What initially gets run is bin/server.js
, which does little more than enable ES6 and ES7 awesomeness in the
server-side node code. It then initiates server.js
. In server.js
we perform data fetching using Redux Saga (details). Aside from serving the favicon and static content from /static
, the only thing server.js
does is initiate delegate rendering to react-router
. At the bottom of server.js
, we listen to port 3000
and initiate the API server.
The primary section of server.js
generates an HTML page with the contents returned by react-router
. Then we perform server-side data fetching, wait for the data to be loaded, and render the page with the now-fully-loaded redux
state.
The last interesting bit of the main routing section of server.js
is that we swap in the hashed script and css from the webpack-assets.json
that the Webpack Dev Server – or the Webpack build process on production – has spit out on its last run. You won't have to deal with webpack-assets.json
manually because webpack-isomorphic-tools take care of that.
We also spit out the redux
state into a global window.__data
variable in the webpage to be loaded by the client-side redux
code.
The redux-saga provides a mechanism for server-side data fetching from the actual backend API servers/services, when it reaches client-side (React) there is no need to do additional network call. You have to define the Sagas that a container need (refers to containers/UserPage.js > UserPage.preload
for example) for server-side to fetch. PS: You have the flexibility to implement additional logic (e.g. handle authentication) when fetching data at server-side rendering stage, as it differs from client-side.
The client side entry point is reasonably named client.js
. All it does is load the routes, initiate react-router
, rehydrate the redux state from the window.__data
passed in from the server, and render the page over top of the server-rendered DOM. This makes React enable all its event listeners without having to re-render the DOM.
Currently, we only use Saga Middleware and Logger Middleware (for development). If you need to use or add custom middlewares, you can do so by modifying store/configureStore.dev.js
(for dev env) or store/configureStore.prod.js
(for prod env).
If you want to implement authentication/authorization feature, follow this issue posted on redux-saga repo - Question: Authentication flow - it is my main source of reference.
The project uses Jest to run your unit tests and the Test Utilities from Facebook api.
An example is available at components > User
.
To run the tests in the project, just simply run npm test
.
This project uses local styles using css-loader. The way it works is that you import your stylesheet at the top of the render()
function in your React Component, and then you use the classnames returned from that import. Like so:
render() { const styles = require('./App.scss'); }
Then you set the className
of your element to match one of the CSS classes in your SCSS file, and you're good to go!
<div className={styles.mySection}> ... </div>
react-universal-saga
support global style variables by defining the variable in theme/style.scss
. Once defined, you can use in any scss file so long it is imported (refer to RepoPage.scss
for example).
Any contribution is welcome.
Cheers,
Kawi