Dynamic combine of reducers - javascript

I have a dashboard with widgets and they depend on the current user. So we don't know which all widgets are going to load at compile time. That said , the state will also have dynamic fields for these widgets.I have a reducer for each widget. How do i combine all the reducers of required widgets ?
My state may look like :
{ dashboardId: 12,
widgetList:{
w1: {
widgetTitle:'widget 1'
data:[]
},
w2:{
widgetTitle:'widget 2'
data:[]
}
}
}
Now i want to combine Reducers:
combineReducers({
w1:widget1Reducer,
w2:widget2Reducer
})
Now, the issue is we dont know which all widgets are going to load for the current dashboard.

You don't need to combine multiple reducers using the helper. Create a widgetList reducer that will update to specified widget using the appropriate widget reducer:
function widgetList(state = {}, action) {
switch (action.type) {
case WIDGET_A_FOO:
case WIDGET_A_BAR:
return {
...state,
[action.id]: widgetA(state[action.id], action),
}
case WIDGET_B_BAZ:
return {
...state,
[action.id]: widgetB(state[action.id], action),
}
case DELETE_WIDGET:
return _.omit(state, action.id)
default:
return state
}
}
In the above example, action.id holds the widget ID. widgetA and widgetB are the reducers of widget A and widget B.

Related

Delayed prop update when using React Hooks and Redux

I have a React class component where the view depends on a global state called props.filtered_table_data stored in Redux Store.
I use useReducer to apply filters to the state
const init_filter = { OWNER: '', TEAM: '' }
const [filter, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'OWNER':
return { ...state, OWNER: action.option }
case 'TEAM':
return { ...state, TEAM: action.option }
case 'RESET':
return init_filter
default:
return state
}
}, init_filter)
These filters get applied when I select through a dropdown.
<Select
className="select-filter"
type="owner"
onItemSelect={(optionItem) => { handleFilter('OWNER', optionItem); ; filterData()}}
>
</Select>
And filterData() is the function that handles the filtering of the data. It creates a new array, filters the data based on the filter and updates it at the end by doing
props.filterTable(newArray)
filterTable is just a redux action that updates the props.filtered_table_data.
The filtering works well, but there is a delay in the update.
When I select a new filter, the previous filter gets applied to the data.
How can I fix this issue?

How to make reducers reusable?

I have 2 apps inside my CRA that use the same UI but do slightly different things. at the moment I am using lots of similar reducers for the 2 apps and am wondering the best way to make them reusable and not duplicate the code?
app1:
export default (state: App1State = initialState, action: ReducerAction) => {
switch (action.type) {
case APP_1.ACTION:
return {
...state,
id: 123,
app1SpecificState: 'app1'
}
default:
return state
}
}
app2:
export default (state: App2State = initialState, action: ReducerAction) => {
switch (action.type) {
case APP_2.ACTION:
return {
...state,
id: 123,
app2SpecificState: 'app1'
}
default:
return state
}
}
so to combine them I'm thinking of doing something like this:
export default (state: App1State = initialState, action: ReducerAction) => {
if (process.env.APP_NAME === 'app2') {
(state as App2State) = App2State
}
switch (action.type) {
case APP_1.ACTION:
return {
...state,
id: 123,
app1SpecificState: 'app1'
}
case APP_2.ACTION:
return {
...state,
id: 123,
app2SpecificState: 'app2'
}
default:
return state
}
}
I'm wondering if a) this makes sense b) this is a good idea c) there is a better way to achieve this?
I also don't want to repeat the if statement across all my "shared" reducers so would be good to abstract this if possible. probably a HOC, if that's even possible here?
The redux documentation has recipes for that, check it out:
Reusing reducer logic
If you need to create new parts of redux store dynamically, you can also add more reducers later:
Add reducer dynamically
In order to update your store dynamically you should dispatch your data with its property name like this:
dispatch({
type: APP.ACTION,
payload: "app1",
specificState: "app1SpecificState"
})
dispatch({
type: APP.ACTION,
payload: "app2",
specificState: "app2SpecificState"
})
and then in your reducer you can use like this:
case APP.ACTION:
return {
...state,
id: 123,
[action.specificState]: action.payload
}
It is the same principle as updating the state dynamically.

Can a Redux action affect multiple parts of the state tree?

What is the consensus on an action affecting multiple parts of the state tree in Redux?
For example:
const ADD_POST = 'POST/ADD';
function postsReducer(state = initialState, action = {}) {
// switch ...
case ADD_POST:
return {
...state,
...action.result.post
}
}
function anotherReducer(state = initialState, action = {}) {
// switch ...
case ADD_POST:
return {
...state,
post_id: action.result.post.id
}
}
I'm seeking advice on:
Actions affecting multiple parts of the redux store/state
Yes, absolutely. It’s the whole reason why actions exist: to separate what happened from the component’s point of view from what actually happens in terms of state change.
Yes, it's ok. If that's what you want to happen.

dispatch action triggering, but redux store not updating

Currently in my React-Redux app, I would like to use React to set the state of show to either false, or true. When set to true, the app will initialize. (There are multiple components, so it would make sense to do this using react/redux.)
However, my current issue is that even though I have connected my app using react redux, and my store using provider, the dispatch action will be called, without updating the store(I am using redux dev tools to double check, as well as in app testing).
I have attached the code I feel is relevant, however, the entire code base is available as a branch specifically made for this question here. I have spent quite sometime on this(actually an understatement) and any contributions would be greatly appreciated.
Component Relevant Code
hideBlock(){
const{dispatch} = this.props;
dispatch(hideBlock);
}
return(
<div className = "works">
<button id="show-block" type="button" className="show-hide-button" onClick={this.showBlock}>show</button>
<button id="hide-block" type="button" className="show-hide-button" onClick={this.hideBlock}>Hide</button>
</div>
);
function mapStateToProps(state) {
const {environment} = state;
return{
environment
}
}
export default connect(mapStateToProps)(Form);
Action
import * as types from "../constants/ActionTypes";
export function showBlock(show) {
return {
type: types.SHOW,
show: true
};
}
export function hideBlock(hide) {
return {
type: types.HIDE,
show: false
};
}
Reducer
import * as types from "../constants/ActionTypes";
const initialState = {
show: false
};
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
return Object.assign({}, state, {
type: types.HIDE
});
case types.SHOW:
return Object.assign({}, state, {
type: types.SHOW
});
default:
return state;
}
}
Thank you, and once again any help is very much so appreciated.
So, I asked a co-worker for help and it turns out that I was returning my action as an object instead of a function. So, for instance, changing the following code:
hideBlock(){
const{dispatch} = this.props;
dispatch(hideBlock);
}
to
hideBlock(){
const{dispatch} = this.props;
//change hideBlock to hideBlock()
dispatch(hideBlock());
}
solved the issue. Thanks Andrew!
It looks like state.show is set in initialState but never modified in any of the cases inside the reducer. The action has show: true, but the reducer never uses this to update the state.
Here's a simplified reducer that updates state.show based on the action's show field:
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
case types.SHOW:
return Object.assign({}, state, {
show: action.show
});
default:
return state;
}
}
Alternatively, because action.show and action.type have the same data, you could remove show from the actions and rely on the action type:
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
return Object.assign({}, state, {
show: false
});
case types.SHOW:
return Object.assign({}, state, {
show: true
});
default:
return state;
}
}

Dynamic keys using Immutable.js

I'm using Redux, but NOT React to provide a bit of context. I have a situation where I would like to store some data about pages a user has visited, but it seems like I need to set my schema prior to setting any new state in a reducer.
function reducer(state, action) {
switch(action.type) {
case STORE_CATEGORY:
return Object.assign(
{}, state,{
[action.category] : {
pages: [],
filters: {}
}
}
);
}
}
The above function works, but I want to translate updating the state with an undetermined set of keys using immutable.js where the keys will be named after the category visited. Thanks.
As I understand, the Map from Immutable would be good for you issue
const initalState = Immutable.Map();
function reducer(state = initalState, action) {
switch(action.type) {
case STORE_CATEGORY:
return state.set(action.category, {
pages: [],
filters: {}
});
}
}

Categories

Resources