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 theReduxactions creators go under theactionsdirectory. 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 thedumbReactcomponents. They receivepropsfromReactcomponents underscreensand 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 keyshere. -
constants/: Define all theactionstypes under this directory. Ideally, the organization of this directory should match that of theactions. -
containers/: Holds the top level React components. TheseReactcomponents are mounted by thereact-routerand map theReduxactions 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 anyJavascriptecosystem go inside this directory. -
reducers/: Holds theReduxreducers. Ideally it should reflect the folder structure ofactionsandconstants. -
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-routerand Reduxstoreinto 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
dummydisplays lists of say entity of typesA,BandC, 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
screenbased 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.