Reputation: 47
I'm trying to work with multiple levels of nested arrays with redux state and am running into complexity (or comprehension :)) issues. I've just started learning react (with redux) and think I am making this too complex and not modeling the state correctly.
(Working with Azure) my state looks like this
There is an array of tenants. Each tenant can have 1 or more subscriptions. Each subscription can have 1 or more resource-groups. Each resource-group can have 1 or more resources. Each resource can have 1 or more tags.
Looks something like this:
tenants: [
{
DisplayName: "blah",
DomainName: "blah.onmicrosoft.com",
TenantId: "72f988bf-1111-1111-1111-111111111111",
active: false,
subs: []
},
{
DisplayName: "blah2",
DomainName: "blah2.onmicrosoft.com",
TenantId: "57aa6e76-1111-1111-1111-111111111111",
active: true,
subs: [
{
subId: '444-555',
subName: 'SubName',
state: "enabled",
active: true,
resourceGroups: [
{
name:"one",
id: "/blah/123/456",
resources: [
{
name: "vm1",
type: "Microsoft.Compute/VirtualMachine"
},
{
name: "vm2",
type: "Microsoft.Compute/VirtualMachine"
}
]
},
{
name:"two",
id: "/blah/555/222",
resources: [
{
name: "vm3",
type: "Microsoft.Compute/VirtualMachine"
},
{
name: "vm4",
type: "Microsoft.Compute/VirtualMachine"
}
]
},
]
}
]
}
]
}
I'm struggling to get my head around how to track and modify state per item, for things like showing/not showing on the UI, button clicking etc. So for example if I want a nested menu, tracking what is and isn't showing and reflecting properly based on what's clicked etc. to collapse or expand a given menu. So for example if I click on the 'tenant' button/box/heading, it collapses all child items.
Do I make a function per state 'depth level', and pass around indexes? So for e.g. if I wanted to work with a VM and tags, I would pass tenant index, sub index, RG index etc? I think that would work but sounds terrible TBH.
Or do I somehow split the state into multiple reducers, perhaps one for tenants, one for subs etc? And do some for of key reference as a state property e.g. in a resourceGroup reducer, have a "sub" key which points to the subscription index?
Any advice or thoughts about how to tackle this would be great. Thank you.
edit: I thought I'd update what I ended up doing.
The state now looks like this:
resources: [
{
type: "tenant",
displayName: "Microsoft",
shortId: "72f988bf-1111-1111-1111-111111111111",
longId: "/blah/123/456/789",
isVisible: false,
info: {
active: false,
domainName: "microsoft.onmicrosoft.com",
},
children: []
},
{
type: "tenant",
displayName: "blahdy blah blah",
shortId: "57aa6e76-1111-1111-1111-111111111111",
longId: "/blah/123/456",
isVisible: false,
info: {
active: true,
domainName: "blah.onmicrosoft.com",
},
children: [2, 7]
},
{
type: "subscription",
shortId: '444-55522',
longId: "/blah/123/456/789",
displayName: 'SubName',
isVisible: false,
info: {
active: true,
state: "enabled",
},
children: [3,4]
}
]
The 'children' array for each property is a reference to the index which is it's 'nested' child.
From here, I am now able to recursively render a component, conditionally checking for children and if so, mapping into a recursive render.
Note: to recursively render a component which gets it's state mapped from redux, you have to define a const first, export that const, and use that const in your recursive call:
var ResourceWrapper = connect(mapStateToProps, mapDispatchToProps)(Resource);
export default ResourceWrapper;
Upvotes: 2
Views: 880
Reputation: 12174
Or do I somehow split the state into multiple reducers, perhaps one for tenants, one for subs etc?
For sure you need to split the state into multiple reducers (i.e Single Responsibility Principle)
Think of the store as a document tree. You can divide the entire tree into smaller trees which makes the sub-problem isolated in a way.
So basically you have tenants/subs/resourceGroups/resources
tree structure and directory structure.
Your action format should also match the directory structure (i.e. tenants/subs/add
to add subscription to a certain tenant or tenants/remove
to remove tenant). Having this format will be helpful for action filtering using redux-dev-tools
.
Thus, each folder has their own reducer like tenants
where it just handles the "tenant-slice" of the store. For tenant reducer, it can handle tenant-related UI actions that will affect a tenant-slice.
P.S, having a state structure similar to the API response is an advantage (in a sense that transformation is not anymore needed).
Upvotes: 1
Reputation: 96
Maybe this will help some:
When building React and Redux apps, it is common to make a folder for each component in the component folder. Each folder will have the Component.js file along with component.action.js and component.reducer.js. The reducers are then combined using combineReducers from react-redux package - my convention is to place this in the same folder as the store where it is then passed imported and passed into the store, which then is passed into the Provider also from react-redux, that wraps the App component: component.
The initial state of the app is declared as const initialState = {var1: value1, var2: value2}. This is passed into the reducer as the first argument to set the initial state and action as the second argument: cost reducer = (state = initialState, action) => {…}.
The reducers are usually a switch statement that switches on action.type. The action will have the other variables you specified on the action creator. The reducer returns the new state typically action.someVariable. The convention I like best to do this is: return {…state, someVariable: ‘newValue’, anotherVariable: ‘anotherValue’}.
You would manage wether a component is showing with a isVisible variable on that component. If you have a dropdown menu in the same component. It would have an action and an action creator. The action export const TOGGLE_MENU = ‘TOGGLE_MENU‘ and export const toggleMenu = () => ({ type: TOGGLE_MENU }); The reducer, when switching on TOGGLE_MENU (which you imported to the reducer) will return { …state, isVisible: !state.isVisible }. You would have specified isVisible: false in the initialState that you declared at above the reducer.
Upvotes: 0