While React
itself doesn't enforce any specific project strucutre or setup for that matter, it's always better to have
a boilerplate at disposal to kickstart a React
project.
After studying some of the available boilerplates, we found react-slingshot
from a Pluralsight Author coryhouse quite handy. However it is to be noted that it's just a
recommendation and subject to personal preference and at some times project requirements.
After working on multiple React
projects here at Nimble, we settled down on some generic code
organization and stucture that we want to lay down here.
-
actions/
: All theRedux
actions creators go under theactions
directory. It's highly advised to organize the action creators based on the screens. We will discuss about it in details in next section. -
adapters/
: Organize the code making network and/or async tasks under this directory. -
components/
: This directory hosts all thedumb
React
components. They receiveprops
fromReact
components underscreens
and render themselves and their children accordingly. These components should have no direct association toRedux
. Redux actions must instead be passed down from thescreens
. -
config/
: Holds the app specific configuration variables. As always, do not store anysecret keys
here. -
constants/
: Define all theactions
types under this directory. Ideally, the organization of this directory should match that of theactions
. -
containers/
: Holds the top level React components. TheseReact
components are mounted by thereact-router
and map theRedux
actions and state to props and pass down to thescreens
. Refrain youself from having any logic inside these components. -
lib/
: All the custom libraries that stand on their own and can work under anyJavascript
ecosystem go inside this directory. -
reducers/
: Holds theRedux
reducers. Ideally it should reflect the folder structure ofactions
andconstants
. -
screens/
: Holds the page specific components which then renders all other dumb components to build any specific page. These components map one-to-one to those undercontainers
. -
services/
: Holds the Service Objects used inside the project. By definition, these classes should encapsulate one single process of the app. -
store/
: Redux store objects. -
utils/
: Any utilities used in the project should be placed inside this folder. -
App.jsx
: The parent React component of the app. Bridges thereact-router
and Reduxstore
into the app flow. -
routes.js
: All the app routes are defined in this file.
Here at Nimble, we employed three different strategies to have a organized and maintainable app store
.
- Entity Based Store Strucutre: Initially, we had the instinct that organizing store based on the entity types would be most efficient as store is indeed the database of the frontend app. The store would look like this:
users:
isFetching:
fetchSuccess:
fetchFailure:
users: []
articles:
isFetching:
fetchSuccess:
fetchFailure:
articles: []
comments:
...
The store is certainly most elegant. But we stumbled across few issues: * filtering specific entites from a global store and then passing them down to components per page addded quite a lot of overhead. * although the store was concise, handling of data and refreshing specific entities was quite a nightmare.
We abandoned this approach.
- API Based Store Structure: Our next attempt was to organize the store based on the API response. The concept was quite straightforward -- a page makes a request for data, so lets simply organize the store around the API responses.
The store schema looked like this
articles:
isFetching:
fetchSuccess:
fetchFailure:
articles: []
users: []
comments: []
...
This appraoch had the advantange that now fewer sub-stores needed to be passed down to the subsequent components and the
associated entities we encapsulated together, but we still had the issues of
* filtering the global store.
* now an entity of one type could be under multiple substores, so updating a single entity required dispatching multiple
actions.
* when used with REST API
we eventually ended up with a store schema similar to entity based with added overhead of
handling the relationships.
At last, we had to abandon this approach too.
-
Screen Based Store Strucutre: We eventually decided to employ a screen based store schema where store is designed based on the requirements of a page. So basically if a page named
dummy
displays lists of say entity of typesA
,B
andC
, the corresponding store schema would bedummy: A: ... B: ... C: ...
If any other pages need similar entities, a replica of the store schema would be nested under the page name in the store.
dummy: A: ... B: ... C: ... dummyAgain: A: ... D: ...
We find the
screen
based schema quite handy, as- there is no overhead of filtering entites from a global store and passing down to the components.
- clearing of store and refershing a specific entity is quite simple.
- It allows for caching store on a page level which plays quite well for offline supports.