Best practice redux actions with flowtype? - redux

I want to write redux with flowtype, but I have questions about how to do that.
Redux with Flowtype
type ActionType =
| 'A'
| 'B'
;
// A generic type conforming to Flux Standard Action
type ActionT<A, P> = {|
type: A,
payload?: P | Error,
error?: boolean,
meta?: mixed
|};
type Action =
| ActionT<'A', string>
| ActionT<'B', number>
;
const error: Error = new Error('wrong');
const info = { href: '...' };
// ---- valid actions with flowtype ----
const action1: Action = {type: 'A', payload: 'hello' };
const action2: Action = {type: 'A', payload: error, error: true }; // The 'payload' could be an error.
const action3: Action = {type: 'A', payload: 'hello', meta: info }; // We can have 'meta'.
// ---- invalid actions with flowtype ----
const actionNG1: Action = {type: 'C', payload: 'hello' }; // Wrong 'type' value. The type 'C' is not allowed.
const actionNG2: Action = {type: 'B', payload: 'hello' }; // Wrong value type of 'payload'. It should be a number.
const actionNG3: Action = {type: 'A', payload: 'hello', threshold: 3 }; // Extra property 'threshold' is not allowed. It should conform to type ActionT.
I use ActionType instead of constants to check the valid type values.
The type ActionT conforms Flux Standard Action to ensure the structure of Redux actions.
The type Action declares the concrete types for all actions that we'll use in our App.
Question 1: how to ensure the first type passed to ActionT would be the type of ActionType (or at least, should be a string type)?
For example, adding a new type 'C' in not allowed, because ActionType only accepts 'A' and 'B'.
type ActionType =
| 'A'
| 'B'
;
type Action =
| ActionT<'A', string>
| ActionT<'B', number>
| ActionT<'C', number> // Should Raise an error
;
Does it make sense?
Question 2: how to write the reducers (and thunk) with flowtype and Immutable.js?
I've written a buildReducer to bind to a initial state.
type Action =
| ActionT<'SELECT_COUNTRY', string>
;
const buildReducer = (initialState, reducerMap) =>
(state = initialState, action) => {
const reducer = reducerMap[action.type];
return (reducer) ? reducer(state, action) : state;
};
const initialState = Immutable.fromJS({
selectedCountry: 'US'
})
const reducers = {
['SELECT_COUNTRY']: (state, action) => {
// how to know the type of payload is string type?
// how to ensure that `state.set(...)` gets right type of the value for the key?
return state.set('selectedCountry', action.payload)
}
}
const appReducer = buildReducer(initialState, reducers)
How can I check the payload type in the reducers for action type SELECT_COUNTRY with flowtype ?
How can I apply the payload value to the immutable state with flowtype check?

To answer question 1, you can use bounded polymorphism
// A generic type conforming to Flux Standard Action
type ActionT<A: ActionType, P> = {|
type: A,
payload?: P | Error,
error?: boolean,
meta?: mixed
|};
Notice how we bound the generic parameter A to ActionType
To answer question 2, you have to make sure to pull in immutable-js types here: https://github.com/facebook/immutable-js/blob/master/type-definitions/immutable.js.flow and then type your reducer functions.

Related

Pass extra arguments to thunk payload in redux toolkit

I'm using the createAsyncThunk method to handle an api request with RTK.
However, I can't get to pass extra arguments to the fulfilled response of the thunk. I can only get the data from the returned promise.
The returned promise has this data:
{ items: [ [Object], [Object] ], metadata: {} }
The action:
export const getGroupsBySchoolId = createAsyncThunk(
'groups/getGroupsBySchoolId',
async (schoolId, _thunkAPI) => {
const { items } = await fetch(someUrl); // simplified fetch request
return { items, schoolId }; // this won't work in the reducer, only if I unwrap() the promise in the component
},
);
in the slice the builder I'm trying to get the schoolId, but I only get the returned promise.
builder.addCase(getGroupsBySchoolId.fulfilled, (state, action) => {
// console.log(action);
const schoolId = action.payload.items.length > 0 ? action.payload.items[0].parentId : null; // i want to avoid this an get it from the payload
state.items[schoolId] = action.payload.items;
state.loading = false;
});
The output from console.loging the action, which is of course, the returned promise and the action type:
{
type: 'groups/getGroupsBySchoolId/fulfilled',
payload: { items: [ [Object], [Object] ], metadata: {} }
}
I could create a regular reducer and dispatch it once the promise has been resolved, but that sounds like an overkill that -I think- shoul be solved in the fulfilled builder callback.
Based on your last comment, I see what you're asking - you want to know how to get access to the thunk argument in the reducer.
In other words, given this:
dispatch(getGroupsBySchoolId(123))
You want to to be able to see the value 123 somewhere in the action when it gets to the reducer.
The good news is this is easy! For createAsyncThunk specifically, the thunk argument will always be available as action.meta.arg. So, this should work:
builder.addCase(getGroupsBySchoolId.fulfilled, (state, action) => {
// console.log(action);
const schoolId = action.meta.arg;
state.items[schoolId] = action.payload.items;
state.loading = false;
});

TS2322: Type 'T' is not assignable to type 'never'

I'm porting some JS to TS and have a simple app state module which allows me to set a property for later retrieval. When the ts compiler reaches the line state[propertyName] = value; inside the setAppState function, it throws the following error
TS2322: Type 'T' is not assignable to type 'never'.
I searched for this error but none of the other answers seem to address this case where I'm setting an object property using bracket notation.
Here's my code...
export interface appState {
context: typeof cast.framework.CastReceiverContext,
senderId: string,
requestId: number,
sessionId: string,
}
const state: appState = {
context: null,
senderId: null,
requestId: null,
sessionId: null,
};
export const getAppState = (propertyName: keyof appState): keyof appState => {
return state[propertyName];
};
export const setAppState = <T>(propertyName: keyof appState, value: T): void => {
state[propertyName] = value;
};
I found another question here that showed the correct way to write the types in the function signatures for the generic getter and setter, which solves the problem. The following now compiles.
export const getAppState = <K extends keyof appState>(propertyName: K): appState[K] => {
return state[propertyName];
};
export const setAppState = <K extends keyof appState>(propertyName: K, value: appState[K]): void => {
state[propertyName] = value;
};

How to execute buisness logic in NgRx reducers

Hi am using NgRx store for state management in my angular project.
My goal is to clear few state properties on action dispatch. The array of property names are passed to the action.
// Action
export const clearFields = createAction(
'[SC Base Data] Clear Fields',
props<{ fields: string[] }>()
);
// Reducers
on(SCActions.clearFields, (state: SCState,fields: string[]) => ({
...state,
SCData: {
...state.SCData
}
})),
/
How can iterate over fields array and set the state properties value as blank
If by "blank" you mean null, I believe what you're looking for is something along these lines:
on(SCActions.clearFields, (state: SCState, fields: string[]) => ({
// create an Object whose keys are all elements of fields and every value is null
const clearedState = {};
fields.forEach(field => {
clearedState[field] = null;
});
// return new copy of state nulling all fields from fields array
return {
...state,
...clearedState
};
}))

How to use Redux Promise Middleware with slices in Redux Toolkit?

I'm using the slices feature of Redux Toolkit. As you may know, one slice returns an action for every created reducer.
I want to use the redux-promise-middleware library, which for a given ACTION_TYPE, it creates three possible new actions: ACTION_TYPE_FETCHING, ACTION_TYPE_FULFILLED, and ACTION_TYPE_REJECTED. How do I handle this from the point of view of the slices?
You'd need to add the expected action types to the extraReducers section of createSlice, like:
// could use a predefined action constant if desired:
const actionFetching = "ACTION_TYPE_FETCHING"
const usersSlice = createSlice({
name: "users",
initialState,
reducers: {
// specific case reducers here
},
extraReducers: {
// could use computed key syntax with a separate type constant
[actionFetching ]: (state, action) => {}
// or the actual field name directly matching the type string
ACTION_TYPE_FULFILLED: (state, action) => {}
}
})

redux - how to create a generic reducer?

In react-redux, I'm trying to create a generic reducer, meaning a reducer with common logic that writes (with that logic) each time to a different section in the store.
I read Reusing Reducer Logic over and over, I just can't wrap my head around it. Let's say I have this state:
{
a: { b: { c: {...} } } },
d: { c: {...} }
}
a and d are two reducers combined with combineReducers() to create the store. I want section c to be managed with common logic. I wrote the reducer logic for c, I wrapped it to create a higher-order reducer with a name.
How do I create the a reducer with the c reducer with reference to its location (and also d accordingly)? Maybe in other words, how do I create a reducer with a "store address", managing his slice of the state, agnostic to where it is?
I sure hope someone understands me, I'm new to redux and react.
Reducer are now simple function and can be reuse somewhere else
const getData = (state, action) => {
return {...state, data: state.data.concat(action.payload)};
};
const removeLast = (state) => {
return {...state, data: state.data.filter(x=>x !== state.data[state.data.length-1])};
}
Action type and reducer function are now declared in an array
const actions = [
{type: 'GET_DATA', reducer: getData},
{type: 'REMOVE_LAST', reducer: removeLast}
];
Initial state for the reducer
const initialState = {
data: []
}
actionGenerators creates an unique Id using Symbol and assign that Id to actions and reducer function.
const actionGenerators = (actions) => {
return actions.reduce((a,c)=>{
const id = Symbol(c.type);
a.actions = {...a.actions, [c.type]: id};
a.reducer = a.reducer ? a.reducer.concat({id, reducer: c.reducer}) : [{id, reducer: c.reducer}];
return a;
},{});
}
reducerGenerators is a generic reducer creator.
const reducerGenerators = (initialState, reducer) => {
return (state = initialState, action) => {
const found = reducer.find(x=>x.id === action.type);
return found ? found.reducer(state, action) : state;
}
}
Usage
const actionsReducerCreator = actionGenerators(actions);
const store = createStore(reducerGenerators(initialState, actionsReducerCreator.reducer));
const {GET_DATA} = actionsReducerCreator.actions;
store.dispatch({type: GET_DATA});
Checkout my github project where I have a working todo application utilizing this implementation.
Redux-Reducer-Generator

Resources