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:
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:
store.pagination
)store.pagination.starredByUser
, store.pagination. stargazersByRepo
)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 intactRepo
component has a button which toggles its background colorRepo
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:
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 actionsaction.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 componentshWnd
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
. There is no “right” answer for this. Some users prefer to keep every single piece of data in Redux, to maintain a fully serializable and controlled version of their application at all times. Others prefer to keep non-critical or UI state, such as “is this dropdown currently open”, inside a component's internal state.
Yes it can be easily manipulated.
This pattern allows you to have multiple states and use a common reducer to update each state based on an additional parameter inside the action object.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With