Skip to content

Structuring a React/Redux/TypeScript app

I’m relatively new to the stack of React, Redux and TypeScript but I had to do a lot of coding in this area over the past months. To get started I read some books (yea old school – baby), got some nice and helpful intro by my co-workers and read a ton of online articles and tutorials.

But somehow it didn’t feel structured enough for me. So I revisited my code and where I’ve put things multiple times and slightly modified it on each attempt. With the current state I am quite happy bit its far from being perfect. So here is how I structure things as of today. No guarantee that I will be the same structure next week but hey, this is how the weg works 😉

Readings

First of all a few good reading everyone should read first.

A good reading for React/Redux apps is the Redux documentation. Several pages have very good information on how to structure things – even if they are not focused on TypeScript and type-safety. Each time a read – or better: re-read – chapters of this documentation I have a aha moment and think by myself “oh god, I’ll never get this all together in my code (a positive thing for the Redux docs).

Another good reading related to TypeScript is the TypeScript React Starter tutorial.

The result

Let’s start straight with the result. Here is how my files and folder structure currently looks like:

+ src
  + components
    + MyAppInputBox
      - MyAppInputBox.tsx
      - MyAppInputBox.less
    + MyAppPageLayout
      - MyAppPageLayout.tsx
      - MyAppPageLayout.less
    + ...
  + core
    + entities
      - IContract.ts
      - ...
    + api
      - ApiManager.ts
      - MyAppServer.ts
      - Fetcher.ts
      - IResponseObject.ts
      - ...
    + ioc
      - ServiceRegistry.ts
      - ServiceIdentifiers.ts
      - ...
  + redux
    + globals
      + actions
        - factories.ts
        - index.ts
        - types.ts
      + logic
        - serverInfo.ts
        - ...
      + reducers
        - globalsReducer.ts
        - ...
        - index.ts
      index.ts
      state.ts
    + reducers
      - index.ts
      - rootReducer.ts
      - pagesReducer.ts
    index.ts
    state.ts
  + pages
    + StartPage
      + components
      + redux
      + scences
      - StartPage.tsx
      - StartPage.less
    + ...

The index.ts files re-export the relevant members per folder. One can think of them as a kind of “published” visibility or the “public API of that folder”. They also allow for shorter import statements.

Components

There are many ways one can structure its React components and tons tutorials and articles can be found online. I like to divide my components in different categories depending on their role.

  • Simple components that are not connected to the global state and just do some rendering. They are located within pages or other “components* and are stored within a `componentsfolder. Other sources I found online call themelements“ but IDE’s – like WebStorm – have smart defaults for folder called components. That’s why I stick with this name.
  • Components that compose several simple components to a more complex layout and may are connected to the global state. The are pages or located in scenes subfolders. You’ll find other names for these components online like Views (a totally overused word in IT in my point of view), container-components, etc. I’ve started putting them into local scenes folders instead of local components folders.
  • Root level components that act as Pages or sometimes called Scenes. I find it handy to treat the entry point components for each new page (top-level component that renders the entire screen) separately. Located in src/pages or src/scenes.
  • Central components like input controls, logo’s, page-template etc. that are re-used all over in the application. I put these components in src/components.

This whole chapter is not yet in its final stage. Chances are quite good that I will further change the structuring of the React components in the future. But as I wrote – there are so many systems/concepts online for structuring React components.

Redux

Let’s talk about all the Redux related code and where I put those things.

First of all I normally have a src/redux where I put in my global Redux-related code which is not specific to a Page or Component. Things like my root state and reducers as well as setting up the Redux store itself goes in here.

More local sub-state – eg. the one specific to a certain page – goes into their own ./redux folder within the scope they belong to (Page, Component, etc.).

The Redux folders are always structured the same way:

+ actions
(+ logic)
+ reducers
- index.ts
- state.ts

State

The state.ts contains two things:

  • The interfaces describing the states and sub-states.
  • A getXyState(appState: IAppState): IXyState per state interface to get the given sub-state object out of the store. Using these functions the components etc. don’t need to know the structure of my reducers but only that fields are in the given state. So the only places where the cascading of my state-nodes matter are the combineReducers() and these getter functions.

A note on typing the states: I always use interfaces to describe / type the surface of a state. Others may use type aliases instead interfaces. I prefer interfaces.

Actions

Note: Actions are per state – not per reducer. The actions therefore are for all reducers within the given state node.

When using TypeScript the actions get more code then with vanilla Javascript because we like to type them and not using string literals all over. Then we need the factory functions (aka action creators) which produce the action objects. If one puts this all into one file per state this file quickly gets large. That’s why I split this code and introduced the actions folder.

./redux/actions/:

  • factories.ts: Contains all the functions that create the action objects (aka action creators). For example:
    export function fetchContractsError(errorMessage: string): FetchContractsError {
    return {
        type: FETCH_STARTABLE_CONTRACTS_ERROR,
        errorMessage
    };
    }
    
  • types.ts: Contains all the action type definition like shown on the TypeScript quick-starter tutorial and this is the only place where we use the string literals for action names:
    export const FETCH_CONTRACTS_ERROR = 'FETCH_CONTRACTS_ERROR';
    export type FETCH_CONTRACTS_ERROR = typeof FETCH_CONTRACTS_ERROR;
    export type FetchContractsError = {
      type: FETCH_CONTRACTS_ERROR;
      errorMessage: string;
    };
    
  • ìndex.ts: Pulls the above things together by re-exporting them.
    export * from './types';
    export * from './factories';
    

I’ve written Live-Templates for WebStorm for them so most of this code generated automatically. I just have to enter the new action name and fill it with additional fields.

I am thinking to change the content of the actions folder in the future the way that I have one file per action which contains the action-object typings and the factory-function (action creator). This way if I later need to move a action into another state-node I can grab the entire file and let WebStorm fix all the imports. But this would lead in even more files. As I said: thinking about it.

Reducers

The Reducers are stored within the reducers folders and are structured as shown in Redux docs and the TypeScript tutorial. Nothing special here except that we started putting the reducers into classes so we can use constructor-injection from the IoC container (we use Inversify and are happy with it). To get these reducers into Redux we write a wrapper-function per reducer which instantiates these classes using the IoC container (using the service-locator (anti-)pattern at this point) and can be used with the combineReducer() function of Redux.

Why do we need dependency injection (DI) in Reducers? We heavily rely on unit-testing (using Jest and WallabyJs). To do so we split our code using the SOLID principals into small classes that separate concerns. For example we extract domain-/business-logic into their own service-classes which we then register with the IoC container. To get instances of them we rely on constructor-injection and thats why we need classes and instantiate them using the service-locator pattern.

Logic folders

Not sure if this name will stay for long but it mainly contains stuff that don’t belong to the actions and not to reducers. For example async-actions (aka meta-actions) are stored here as they are not plain actions and also contain a decent amount of logic (eg. how to fetch server-data incl. error-handling). Logic utilities or sometimes the domain-/business-services goes in here as well. Its content is not highly standardized yet.

Core

The src/core folder contains client-side code that does not belong to either a specific React component nor a specific Redux state. Stuff like the fetch-client or domain-entities definition (= TypeScript interfaces) go in here. The whole IoC configuration is stored in here too as well as domain services and some other central code.

Summary

With this structure I feel kind of in control. But on the other hand we have a lot of files and folders and I always find things I don’t like. For example see the note on (re-)structuring the actions folder. This is why I declare this structure as work in progress. I wrote it down so you guys can give feedback and I can start over improving it – again 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: