How to organize Redux state for reusable components?

qbolec

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:

  • a RepoPage, which displays list of users who have starred a particular repo,
  • a UserPage, which displays a list of repos which are starred by particular user
  • a List, which is generic enough that it can display list of users or repos, provided the items and way to renderItem. In particular RepoPage uses User component to display each of users who starred the repo, and UserPage uses a Repo component to display each of starred repos.

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:

  • at the top level the key says what kind of component data is it (in the example it is called store.pagination)
  • then there is a branch for each particular type of context in which the component can be (store.pagination.starredByUser, store.pagination. stargazersByRepo)
  • then there are as many keys as there are unique contexts (store.pagination.starredByUser[login], store.pagination. stargazersByRepo[repo])

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:

  • User component remains intact
  • Repo component has a button which toggles its background color
  • the state of each Repo component is managed by Redux

(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:

  • it looks like in Elm the Actions (messages) are algebraic data types which can be nested in such a way, that a parent component can unpack an "outer envelope" of the message and deliver a inner action intended for child to the child reducer (updater).
  • since it is a convention in Redux to use a string as the type of action, a natural translation of the above idea is to use prefixing, and this seems to be what prism (foremly known as redux-elm) does: the action.type is comprised of substrings which tell the path through components' tree. OTOH in this comment the prism author tomkis explains that the most important part of Elm Architecture that Redux is missing is composition of actions
  • the two above approaches seem to be expanded versions of approaches described in Reusing Reducer Logic
  • I haven't fully grasped how redux-fly works internally, but it seems to use the payload, not the action.type to identify a component instance by its mounting path in the store which also corresponds to a path in the components tree because of the way it is constructed manually by components
  • WinAPI, which to me seems quite similar to Redux if you squint, uses unique hWnd identifier for each control, which makes it super easy to check if action was intended for you, and decide where should be your state in the store.
  • The above idea could probably lead to something described in Documentation suggestion/discussion: Reusing Reducer Logic where each type of component has its own flat subtree indexed by unique id.
  • Another idea descibed in the linked thread linked above is to write a reducer for a particular type of component once, and then let the reducer for the parent component call it (which also means, that the parent is reponsible to decide where in the store the state of the child is located - again, that seems similar to Elm Architecture to me)
  • A very interesting discussion More on reusability for custom components in which details of a proposal vary similar to the one above is presented
  • in particular above discussion contains a proposition by user nav, to organize the store tree recursively in such a way, that a state of a component is subtree in two kinds of branches: one for private stuff, and the other for "tables" of child components, where each class of child component has its own "table", and each instance of child has a unique key in that table, where its state is recursively stored. The unique keys which give access to these children are stored in the "private" section. This is really similar to how I imagine WinAPI :)
  • another elm-inspired proposition by user sompylasar from the same thread is to use actions which contain actions for children as a payload in a "matrioshka" style, which in my opinion mimick how algebraic types constructors are nested in Elm
  • redux-subspace was recommended in discussion about Global Actions for prism, as a library which is both Elm-inspired and lets you have global actions.
Tomasz Białecki

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.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How to organize state with redux and react-router?

How to share state between reducers in redux (how to organize redux)?

How to access nested Redux state in React components?

How to control state of dynamic components with React and Redux?

Sending Redux actions for reusable React components

Redux mapStateToProps state in components

React Native: this.state of reusable components not expected

How to handle reusable components in React?

How to handle global state data into deeply nested components in Redux?

How to update same redux state on two different components simultaneously?

React components lifecycle, state and redux

Redux - State is updating but React components are not

Node: How to restructure nested JSON data getting and organize reusable?

Where to store the state to make React components more reusable

How to make reusable React/MobX components?

How to make reusable components in Jetpack compose?

How to create reusable Components in .NET MAUI?

How better organize React-components interactions?

How to organize functionality across react components?

How to write Redux selectors that are both reusable and modular?

How to manage React state for reusable radio input?

How to organize flowtype in react + redux project

How to organize partial entities in normalized redux store?

How to organize large amounts of state in Haskell projects

Pass state from unrelated components using redux?

Best practice when passing Redux state into components

Create reducer to update state for different components in redux

Using redux / toolkit for state slicing between components

Can't access redux' state in components

TOP Ranking

  1. 1

    Failed to listen on localhost:8000 (reason: Cannot assign requested address)

  2. 2

    How to import an asset in swift using Bundle.main.path() in a react-native native module

  3. 3

    Loopback Error: connect ECONNREFUSED 127.0.0.1:3306 (MAMP)

  4. 4

    pump.io port in URL

  5. 5

    Spring Boot JPA PostgreSQL Web App - Internal Authentication Error

  6. 6

    BigQuery - concatenate ignoring NULL

  7. 7

    ngClass error (Can't bind ngClass since it isn't a known property of div) in Angular 11.0.3

  8. 8

    Do Idle Snowflake Connections Use Cloud Services Credits?

  9. 9

    maven-jaxb2-plugin cannot generate classes due to two declarations cause a collision in ObjectFactory class

  10. 10

    Compiler error CS0246 (type or namespace not found) on using Ninject in ASP.NET vNext

  11. 11

    Can't pre-populate phone number and message body in SMS link on iPhones when SMS app is not running in the background

  12. 12

    Generate random UUIDv4 with Elm

  13. 13

    Jquery different data trapped from direct mousedown event and simulation via $(this).trigger('mousedown');

  14. 14

    Is it possible to Redo commits removed by GitHub Desktop's Undo on a Mac?

  15. 15

    flutter: dropdown item programmatically unselect problem

  16. 16

    Change dd-mm-yyyy date format of dataframe date column to yyyy-mm-dd

  17. 17

    EXCEL: Find sum of values in one column with criteria from other column

  18. 18

    Pandas - check if dataframe has negative value in any column

  19. 19

    How to use merge windows unallocated space into Ubuntu using GParted?

  20. 20

    Make a B+ Tree concurrent thread safe

  21. 21

    ggplotly no applicable method for 'plotly_build' applied to an object of class "NULL" if statements

HotTag

Archive