For example, my app contains the two list: colors & my favorite colors. How to create the re-usable filter-module for this two lists?
The problem is the actions in redux commiting into global scope, so filter-reducer for colors and filter-reducer for favorite colors reacting to the same actions.
I try something like high-order functions that receive the module-name and returned new function (reducer) where classic switch contain module + action.type.
But how make scoped actions or scoped selectors? Best-practise?
Maybe Redux-Toolkit can solve this problem?
High-order reducer
High-order action
High-order selector
Well described here
Name your function like createFilter and add a parameter like domain or scope
after this in switch check it e.g
export const createFilters = (domain, initState) => {
return (state = initState, { type, payload }) => {
switch (type) {
case: `${domain}/${FILTER_ACTIONS.ADD_FILTER}`:
...
and for actions create something like this
const createFilterActions = (domain: string) => {
return {
addFilter: (keys: string[]) => {
return {
type: `${domain}/${FILTER_ACTIONS.ADD_FILTER}`,
payload: keys
}
},
updateFilter: (key: string, state: any) => {
return {
type: `${domain}/${FILTER_ACTIONS.FILTER_UPDATED}`,
payload: { key, state }
}
},
}
}
Related
I am using redux toolkit for my state management with TypeScripts. The StateSlice reducers expect a return type, I tried to type it as what the expected return object should be but its just does not work the only way it works if i set the return type as any
for example in my StateSlice a one of the reducer looks like this.
deleteCard: (state, { payload }: PayloadAction<DeleteCard>): any => { return {
cards: [...state.cards.filter((card) => card["id"] !== payload)], }; },
As you can see the return type is set as any... i have gone through a lot of google articles for example redux toolkit reducer return type and the only thing i can find is how to type the payload using PayloadAction
So the question is how do i type reducers return type
You can just write mutating logic in RTK reducers and not return anything: Writing Reducers with Immer
But even if you return something, you should just not annotate a return type. It's already set by createSlice and it will warn you if you return something wrong it will warn you without any need for an annotation.
Correct code would be either
// preferred immer-mutable approach
deleteCard: (state, { payload }: PayloadAction<DeleteCard>) => {
const index = state.cards.findIndex((card) => card["id"] == payload))
if (index != -1) {
state.cards.splice(index, 1)
}
},
or
deleteCard: (state, { payload }: PayloadAction<DeleteCard>) => {
return {
cards: [...state.cards.filter((card) => card["id"] !== payload)],
};
},
What I'm trying to achieve:
I have a NextJS + Shopify storefront API application. Initially I set up a Context api for the state management but it's not that efficient because it re-renders everything what's wrapped in it. Thus, I'm moving all state to the Redux Toolkit.
Redux logic is pretty complex and I don't know all the pitfalls yet. But so far I encounter couple problems. For example in my old Context API structure I have couple functions that take a couple arguments:
const removeFromCheckout = async (checkoutId, lineItemIdsToRemove) => {
client.checkout.removeLineItems(checkoutId, lineItemIdsToRemove).then((checkout) => {
setCheckout(checkout);
localStorage.setItem('checkout', checkoutId);
});
}
const updateLineItem = async (item, quantity) => {
const checkoutId = checkout.id;
const lineItemsToUpdate = [
{id: item.id, quantity: parseInt(quantity, 10)}
];
client.checkout.updateLineItems(checkoutId, lineItemsToUpdate).then((checkout) => {
setCheckout(checkout);
});
}
One argument (checkoutId) from the state and another one (lineItemIdsToRemove) extracted through the map() method.
Inside actual component in JSX it looks and evokes like this:
<motion.button
className="underline cursor-pointer font-extralight"
onClick={() => {removeFromCheckout(checkout.id, item.id)}}
>
How can I declare this type of functions inside createSlice({ }) ?
Because the only type of arguments reducers inside createSlice can take are (state, action).
And also is it possible to have several useSelector() calls inside one file?
I have two different 'Slice' files imported to the component:
const {toggle} = useSelector((state) => state.toggle);
const {checkout} = useSelector((state) => state.checkout);
and only the {checkout} gives me this error:
TypeError: Cannot destructure property 'checkout' of 'Object(...)(...)' as it is undefined.
Thank you for you're attention, hope someone can shad the light on this one.
You can use the prepare notation for that:
const todosSlice = createSlice({
name: 'todos',
initialState: [] as Item[],
reducers: {
addTodo: {
reducer: (state, action: PayloadAction<Item>) => {
state.push(action.payload)
},
prepare: (id: number, text: string) => {
return { payload: { id, text } }
},
},
},
})
dispatch(todosSlice.actions.addTodo(5, "test"))
But 99% of the cases you would probably stay with the one-parameter notation and just pass an object as payload, like
dispatch(todosSlice.actions.addTodo({ id: 5, text: "test"}))
as that just works out of the box without the prepare notation and makes your code more readable anyways.
I'm trying to build a hook which would return a selected subset of key-value pairs from an object. Assuming that the hook has access to an object which looks like this:
const stores = { someStore: { someField: 'fieldValue' } }
how can we pass a callback to a hook to select fields from the stores object?
The ideal scenario would be to have the hook work like this:
const {selectedField} = useStores(stores => ({ selectedField: stores.someStore.someField }))
The goal of this hook would be to replace MobX #inject(stores => ({...})) in my codebase.
Just run the selector against the stores
function useStores(selector) {
const stores = { someStore: { someField: 'fieldValue' } };
return selector(stores);
}
const { selectedField } = useStores(stores => ({ selectedField: stores.someStore.someField }));
console.log(selectedField);
I have a redux state of the following form, which is managed in slices using combineReducers:
interface AppState {
foos: Foo[];
bars: Bar[];
bazs: Baz[];
}
These are related in the following way:
One Foo has many Bar. One Bar has many Baz. Their structures are as follows:
interface Foo {
id: string;
name: string;
}
interface Bar {
id: string;
name: string;
fooId: string;
}
interface Baz {
id: string;
name: string;
barId: string;
}
I have the regular thunks/actions setup for each part of the state, i.e DELETE_FOO_REQUEST, DELETE_FOO_FAILURE DELETE_FOO_SUCCESS and other CRUD options for each of the entities.
My delete foo thunk looks like this:
function deleteFoo(fooId) {
return async (dispatch, getState) => {
dispatch(deleteFooRequest());
await api.deleteFoo(fooId);
dispatch(deleteFooSuccess(fooId);
// omitted error handling for brevity
}
}
The thing is: when I delete a Foo on my api/backend it also deletes all related Bars and Bazs. Now how do I handle this while using redux-thunk conventions?
Do I create more actions of the form DELETE_BARS_FOR_FOO and dispatch those in the same thunk? Or do I reuse DELETE_BAR_SUCCESS and use it in a loop?
Option A
function deleteFoo(fooId) {
return async (dispatch, getState) => {
dispatch(deleteFooRequest());
await api.deleteFoo(fooId);
const barIds: string[] = selectBarsForFoo(fooId);
dispatch(deleteFooSuccess(fooId);
dispatch(deleteBarsForFoo(fooId);
for (const barId of barIds) {
dispatch(deleteBazForBar(barId));
}
// omitted error handling for brevity
}
}
Option B
function deleteFoo(fooId) {
return async (dispatch, getState) => {
dispatch(deleteFooRequest());
await api.deleteFoo(fooId);
const barIds: string[] = selectBarsForFoo(fooId);
dispatch(deleteFooSuccess(fooId);
for (const barId of barIds) {
dispatch(deleteBarSuccess(barId));
}
// followed by a similar loop for the bazs of each bar
// omitted error handling for brevity
}
}
In option B case I am reusing an action meant for something else technically. In both actions I'm dispatching in a loop which would impact performance as well. I am using react-redux however and can use the batch() api so no worries there.
Are these my only two options while using redux-thunk or is there a superior/conventional way of going about this?
Instead of complicating your actions is it an option to just deal with this in your reducer (as suggested in comments). The following way you can still use combineReducers but have a combined reducer that gets {foo,bar,bas} as state:
const fooBarBaz = combineReducers({
foo:fooReducer,
bar: barReducer,
baz:bazReducer,
});
const combined = (state,action)=>{
//handle foo remove success action, state is {foo,bar,baz}
}
export default function reducer(state,action){
return combined(fooBarBaz(state,action),action)
}
First, here's my HOC:
export default function connectField({
nameProp = 'name',
valueProp = 'value',
dispatchProp = 'dispatch'
}: ConnectOptions) {
return compose(
getContext(contextTypes),
connect((state, ownProps) => {
const path = [namespace,...getPath(ownProps),...toPath(ownProps[nameProp])];
const value = getOr('', path, state);
return {
[valueProp]: value
};
}, (dispatch,ownProps) => { // <----------- mapDispatchToProps
const path = [...getPath(ownProps),...toPath(ownProps[nameProp])];
return {
[dispatchProp]: value => dispatch({type: ActionTypes.Change, payload: {path, value}})
};
}, (stateProps, dispatchProps, {[FIELD_PATH]: _, ...ownProps}) => {
return {...stateProps, ...dispatchProps, ...ownProps};
}, {
areMergedPropsEqual: (a,b) => {
let eq = shallowEqual(a,b);
console.log('areMergedPropsEqual',a,b,eq);
return eq;
},
}),
withContext(contextTypes, props => {
return {[FIELD_PATH]: [...getPath(props), props[nameProp]]};
}),
);
}
In the middle there is my mapDispatchToProps. That's causing areMergedPropsEqual to return false every time because it's creating a new action creator every time.
I can't figure out how to memoize this bit:
value => dispatch({type: ActionTypes.Change, payload: {path, value}})
Such that I get back the same function instance every time.
There's some notes in the docs about "per-instance memoization" which is what I want, but I can't quite make heads or tails of what I'm supposed to do here.
To be clear, I know how to memoize a function. However, I don't want a use a big cache with infinite history. It's unnecessary memory consumption. I just need a cache size of 1 like how reselect does it. The problem is that I can't create the "selector" directly inside connectField because that still creates a single shared instance -- i.e., all "connected fields" will share the same cache and they'll overwrite each other, negating the benefit. It has to be per component instance. This is specific to React-Redux's connect method. There's a syntax for it so that you can create your selector at the right spot, and it will only get ran once per instance. I'm just having trouble deciphering the API -- do they expect a function that returns a function that returns an object? Or an object with propnames as keys and functions as values? What does that function return? i.e., the docs aren't clear about all the different variations that are accepted for the mapDispatchToProps option.
If you already have lodash, you have a memoize function that allow to transform any function into a memoized function. This memoized function will calculate the return value for a given parameter and will then always return this same return value each you supply the same parameter.
You can use it like this for example :
import { memoize } from 'lodash'
const makeChangeDispatcher = memoize( (dispatch, path) =>
value => dispatch({type: ActionTypes.Change, payload: {path, value}})
)
...
(dispatch,ownProps) => { // <----------- mapDispatchToProps
const path = [...getPath(ownProps),...toPath(ownProps[nameProp])];
return {
[dispatchProp]: makeChangeDispatcher(dispatch, path)
};
}
...
You can see more infos on the lodash documentation of memoize