Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux: organising containers, components, actions and reducers

The question:

What is the most maintainable and recommended best practice for organising containers, components, actions and reducers in a large React/Redux application?

My opinion:

Current trends seem to organise redux collaterals (actions, reducers, sagas...) around the associated container component. e.g.

/src
    /components
        /...
    /contianers
        /BookList
            actions.js
            constants.js
            reducer.js
            selectors.js
            sagas.js
            index.js
        /BookSingle
            actions.js
            constants.js
            reducer.js
            selectors.js
            sagas.js
            index.js        
    app.js
    routes.js

This works great! Although there seems to be a couple of issues with this design.

The Issues:

When we need to access actions, selectors or sagas from another container it seems an anti-pattern. Let's say we have a global /App container with a reducer/state that stores information we use over the entire app such as categories and enumerables. Following on from the example above, with a state tree:

{
    app: {
        taxonomies: {
            genres: [genre, genre, genre],
            year: [year, year, year],
            subject: [subject,subject,subject],
        }   
    }
    books: {
        entities: {
            books: [book, book, book, book],
            chapters: [chapter, chapter, chapter],
            authors: [author,author,author],
        }
    },
    book: {
        entities: {
            book: book,
            chapters: [chapter, chapter, chapter],
            author: author,
        }
    },
}   

If we want to use a selector from the /App container within our /BookList container we need to either recreate it in /BookList/selectors.js (surely wrong?) OR import it from /App/selectors (will it always be the EXACT same selector..? no.). Both these appraoches seem sub-optimal to me.

The prime example of this use case is Authentication (ah... auth we do love to hate you) as it is a VERY common "side-effect" model. We often need to access /Auth sagas, actions and selectors all over the app. We may have the containers /PasswordRecover, /PasswordReset, /Login, /Signup .... Actually in our app our /Auth contianer has no actual component at all!

/src
    /contianers
        /Auth
            actions.js
            constants.js
            reducer.js
            selectors.js
            sagas.js

Simply containing all the Redux collaterals for the various and often un-related auth containers mentioned above.

like image 764
AndrewMcLagan Avatar asked Jul 14 '16 23:07

AndrewMcLagan


People also ask

How do I organize my Redux reducers?

At its core, Redux is really a fairly simple design pattern: all your "write" logic goes into a single function, and the only way to run that logic is to give Redux a plain object that describes something that has happened.

What is the difference between actions and reducers Redux?

Reducers: As we already know, actions only tell what to do, but they don't tell how to do, so reducers are the pure functions that take the current state and action and return the new state and tell the store how to do.

How actions and reducers are connected in Redux?

A Redux app really only has one reducer function: the "root reducer" function that you will pass to createStore later on. That one root reducer function is responsible for handling all of the actions that are dispatched, and calculating what the entire new state result should be every time.

What are the different components of Redux?

There are three building parts: actions, store, and reducers. Let's briefly discuss what each of them does. This is important because they help you understand the benefits of Redux and how it's to be used.


1 Answers

I personally use the ducks-modular-redux proposal.

It's not the "official" recommended way but it works great for me. Each "duck" contains a actionTypes.js, actionCreators.js, reducers.js, sagas.js and selectors.js files. There is no dependency to other ducks in these files to avoid cyclic dependency or duck circle, each "duck" contains only the logic that it have to managed.

Then, at the root I have a components and a containers folders and some root files :

components/ folder contains all the pure components of my app

containers/ folder contains containers created from pure components above. When a container need a specific selector involving many "ducks", I write it in the same file where I wrote the <Container/> component since it is relative to this specific container. If the selector is shared accros multiple containers, I create it in a separate file (or in a HoC that provides these props).

rootReducers.js : simply exposes the root reducers by combining all reducers

rootSelectors.js exposes the root selector for each slice of state, for example in your case you could have something like :

/* let's consider this state shape

state = {
    books: {
        items: {  // id ordered book items
            ...
        }
    },
    taxonomies: {
        items: {  // id ordered taxonomy items
            ...
        }
    }
}

*/
export const getBooksRoot = (state) => state.books

export const getTaxonomiesRoot = (state) => state.taxonomies

It let us "hide" the state shape inside each ducks selectors.js file. Since each selector receive the whole state inside your ducks you simply have to import the corresponding rootSelector inside your selector.js files.

rootSagas.js compose all the sagas inside your ducks and manage complex flow involving many "ducks".

So in your case, the structure could be :

components/
containers/
ducks/
    Books/
        actionTypes.js
        actionCreators.js
        reducers.js
        selectors.js
        sagas.js
    Taxonomies/
        actionTypes.js
        actionCreators.js
        reducers.js
        selectors.js
        sagas.js
rootSelectors.js
rootReducers.js
rootSagas.js

When my "ducks" are small enough, I often skip the folder creation and directly write a ducks/Books.js or a ducks/Taxonomies.js file with all these 5 files (actionTypes.js, actionCreators.js, reducers.js, selectors.js, sagas.js) merged together.

like image 185
Pierre Criulanscy Avatar answered Sep 16 '22 16:09

Pierre Criulanscy