qbolec
qbolec

Reputation: 5134

How to organize Redux state for reusable components?

TL;DR: In case of a reusable component which has some complicated logic for managing its own state (think: a facebook comment textarea with autocompleter, emoji etc) how does one use store, actions and reducers to manage the state of multiple instances of this component spread across whole website?

Consider the real-world example from the official redux repo. In it we have:

Assume that I really want all of the state to be in Redux.

In particular, I want the state of every List on every RepoPage and UserPage to be managed by Redux. This is already taken care of in the example, by a clever three-level deep tree:

I feel that these three levels correspond also to: component type, parent type, parent id.

But, I don't know how to extend this idea, to handle the case in which the List component itself had many children, with a state worth tracking in Redux.

In particular, I want to know how to implement a solution in which:

(I'm happy to use some extensions to Redux, which still use reducers, but don't want to go with "just keep it in React local state", for the purpose of this question)

My research so far:

Upvotes: 17

Views: 4543

Answers (1)

Tomasz Białecki
Tomasz Białecki

Reputation: 1111

I will try to explain one of idea which is inspired by Elm lang and has been ported to Typescript:

Let's say we have very simple component with the following state

interface ComponentState {
   text: string
}

Component can be reduced with the following 2 actions.

interface SetAction {
    type: 'SET_VALUE', payload: string
}

interface ResetAction {
    type: 'RESET_VALUE'
}

Type union for those 2 actions (Please look at Discriminated Unions of Typescript):

type ComponentAction = SetAction | ResetAction;

Reducer for this should have thw following signature:

function componentReducer(state: ComponentState, action: ComponentAction): ComponentState {
    // code
}

Now to "embed" this simple component in a larger component we need to encapsulate data model in parent component:

interface ParentComponentState {
    instance1: ComponentState,
    instance2: ComponentState,
}

Because action types in redux need to be globally unique we cannot dispatch single actions for Component instances, because it will be handled by both instances. One of the ideas is to wrap actions of single components into parent action with the following technique:

interface Instance1ParentAction {
    type: 'INSTNACE_1_PARENT',
    payload: ComponentAction,
}

interface Instance2ParentAction {
    type: 'INSTNACE_2_PARENT',
    payload: ComponentAction,
}

Parent action union will have the following signature:

type ParentComponentAction = Instance1ParentAction | Instance2ParentAction;

And the most important thing of this technique - parent reducer:

function parentComponentReducer(state: ParentComponentState, action: ParentComponentAction): ParentComponentState {
    switch (action.type) {
        case 'INSTNACE_1_PARENT':
            return {
                ...state,
                // using component reducer
                instance1: componentReducer(state.instance1, action.payload),
            };
        //
    }
}

Using Discriminated Unions additionally gives type safety for parent and child reducers.

Upvotes: 5

Related Questions