an evolution of react-universal-saga with a more modular code structure, inspired by react-boilerplate.
$ git clone https://github.com/xkawi/react-universal-saga-modular.git
$ cd react-universal-saga-modular
$ npm install
$ npm run build-dev
$ npm run dev
$ npm run lint
$ npm test
$ npm run build
$ npm start
build-dev
and dev
commands are meant for development purpose (e.g. logging, hot module replacement).
npm run lint
is to perform eslint check across your code base, while npm test
executes test cases with Jest.
When it is ready for deployment, build
the production ready folder and npm start
to run it. You would need something like PM2 to run your code/React project as daemon/service.
NOTE: PORT is not set for better-npm-run start-prod
script, because it is necessary configuration for Heroku deployment. If you need to specify the port, feel free to add it.
The main objective of react-universal-saga-modular is to provide a more modular, scalable, and manageable codebase as your project grows in complexity and size. This repo can serve as an example, or a starting point for your next Universal React project. If you are not familiar with this structure, or prefer something else (e.g. generator-based, less opinionated, etc) feel free to checkout other alternatives, such as:
- Next.js
- react-universal-saga
- react-boilerplate
- react-server
- retax
- react-universally
- create-react-app
-
Modular code structure
-
Universal/Isomorphic - server side rendering
-
React ^15
-
redux & react-redux - state manager
-
redux-saga - side effects manager
-
Async/Await provided by babel-preset-stage-0
-
react-router - route manager
-
redux-persist - persist state to browser local storage
-
react-helmet - Document head manager
-
Jest - Painless JavaScript Testing
-
Webpack - project builder/bundler
-
ESLint - consistent code style
-
Plain CSS, LESS, & SASS - for styling, enabled by style-loader, less-loader and sass-loader respectively
A module consist of a set of actions, reducers, sagas, selectors, or any other functions for specific functionality. An example of github module is provided, where all actions, sagas, reducers, and selectors related with github put into a single module folder called 'github'.
Ideally, a module should be modularized enough that it can be used in a plug-and-play manner. Imagine someone writes and share an "authentication" module. If you want to use that module, just place it inside src/modules
folder (e.g. src/modules/authentication
), and what's left to do is to import relevant reducers and sagas of that module into modules/rootReducer.js
and modules/rootSaga.js
. You will have everything setup for you. Finally, you can just import and use the viewActions in your containers or components. This is the biggest benefit of this code structure.
When adding a module, here are some things to take note:
-
export the necessary reducers functions, actions, or saga watchers of your module
-
import reducers of the new module into
rootReducer.js
to be combined into the global store -
import saga watchers of the new module into
rootSaga.js
and fork it inside the export default function to run it. -
import
viewActions
into the view layer when you need it -
(optional) provides
index.js
into your module to export everything into single object. This helps if you want to import the module as a wholeimport github from 'modules/github'
; -
(optional) if you perform 5.) ensure that there is an
export default
for reducers, actions, sagas, selectors, or other things that you want to export. Otherwise, the import at step 5.) will return undefined, as it does not know the default export of its children (reducers, actions, sagas, selectors, and others).
In writing the actions, react-universal-saga-modular suggest to organize it into 2 types: View Actions & Saga Actions. This is not a strict rule, it is just a better way of organising it in my opinion. You can organize it in whatever ways you want to.
View Actions define and contain all actions that are available for any view to use. In short, it helps to provide a more explicit way of saying: "hey <views/containers/components>, you can only use this set of actions".
Saga Actions define and contain all actions that are available for sagas to use. Sagas are considered as the "business logic" layer where you handle the flow of side effects. This is often used for making network calls, processing the response/error, store the response/error to the state, or manipulate the state, though the possibilities are endless. As an example, github
module has sagaActions
with user
as one of its key. It has 3 actions, request
, success
, and failure
, and they are only used by the sagas to update the store whether it successfully get the response or failed.
Again, this is just something that I come up with to helps organise the code better. Feel free to organise it in whatever ways you are comfortable with.
Redux Persist is used to achieve this. To demonstrate this, follow this steps:
-
Type a github username (e.g. xkawi) and click "Go"
-
scroll all the way down and click "Load More"
-
Visit another username (e.g. someone), and back to the previous username (e.g. xkawi). You will notice at Redux DevTools panel, the @@INIT action contains all previous user entities and repos restored from browser local storage. You can then write your own logic anywhere to use these restored data. The bulk of the logic for this can be found at
src > client.js
.
react-universal-saga-modular comes with Jest to write test cases for your React components. So whatever Jest can do, you can do it here.
It can also be used to write test cases for your sagas. An example is provided for github/sagas
that test one of the saga routine, loadUser, feel free to look at the code. You can refer to redux-saga documentation on how to test your sagas here.
Feel free to contribute any feedback, bug reports, or PRs. They are most welcomed. 😄
would like to buy me coffee? appreciate it. can do it over here and you will have my gratitude and sincere thank you. ❤️
Cheers,
@xkawi