Flow doesn't infer type correctly - redux

I define an multiple subtypes in the Action creator in redux:
Action creator:
export type Action = { type: "SELECT", index: number } | { type: "OTHER" };
Reducer:
module.exports = (state: string = "", action: Action): string => {
switch (action.type) {
case "SELECT":
return action.index;
default:
return state;
}
};
but if I define SELECT in a constant const select = "SELECT" and implement it in the code above I obtain an error message:
property `index`. Property not found in object type
Note: Taking flow-pattern as it is F8 app:
https://github.com/fbsamples/f8app/blob/master/js/actions/types.js
How should implement it by avoiding having "SELECT" keyword both in the action and in the reducer?

You would normally have another constant with the action type, which would be used for both your action and reducer.
const SELECT = 'SELECT';
or even better (to avoid any conflicts):
const SELECT = 'redux/<module>/SELECT';
In the same action file or in another (that's totally up to you). Then just export like export const SELECT, and import like import { SELECT } from './constants'.
You should also take a look at redux-ducks, might be of your interest.
EDIT:
They use bitwise OR to group all possible different action cases. That helps testing whether the code is syntactically correct with flow (by setting their dispatched actions to match type Action). See their full explanation here.
That does not take away the fact that they have to dispatch the action with their desired action type though. login

Related

Redux Toolkit says this snippet(19 lines) is shorter code (vs. original 12) confusion

I am reading this page getting into react-redux https://redux.js.org/introduction/getting-started
I am very confused looking at the Basic Example which has 12 lines of code(excluding usage, imports, and comments)
Then I read this line on the "Redux Toolkit Example" which below the code states "Redux Toolkit allows us to write shorter logic that's easier to read, while still following the same Redux behavior and data flow." However, this example is 19 lines of code(excluding usage, imports, and comments)
The usage in both examples is 3 lines of code. Could someone explain this to me?
Perhaps when it scales, the redux toolkit example does save more code? Honestly, I find the Basic Example MUCH easier to read and maintain. NOTE: I am a complete newb which leads me to believe the basic example may be better as we ramp up and hire developers. This allows them to ramp up more quickly(but I am only a single data point amongst newbs)
thanks,
Dean
You're right about the lines of code in the example. Perhaps that simple counter example doesn't do justice to how much code Redux Toolkit can save because they aren't adding all the "bells and whistles" in their non-toolkit version.
This section is called "getting started with Redux" rather than "migrating to Redux Toolkit" so I suspect they don't want to overwhelm the user by introducing best practices like action creator functions which aren't strictly necessary. But you're not seeing the "write less code" benefit because most of the code that you can remove with the Toolkit is coming from things that weren't in the example in first place.
Action Creators
One of the main benefits of the createSlice function is that it automatically creates the action name constants and action creator functions to go along with each case in the reducer.
This example is just dispatching raw actions directly with string names store.dispatch({ type: 'counter/incremented' }). Most devs would not do this because of how fragile and inflexible it is.
An alternate version of the non-toolkit example, what you would see in most code, looks more like this:
// action name constants
const INCREMENTED = 'counter/incremented';
const DECREMENTED = 'counter/decremented';
// action creator functions
// usually most take some arguments
const incremented = () => ({
type: INCREMENTED,
})
const decremented = () => ({
type: DECREMENTED,
})
// reducer function
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case INCREMENTED:
return { value: state.value + 1 }
case DECREMENTED:
return { value: state.value - 1 }
default:
return state
}
}
If you want to include typescript types it gets even worse.
Immutability
The reducer itself could get really lengthy if you are trying to do immutable updates on deeply nested data.
Here's an example copied from those docs on how to safely update the property state.first.second[someId].fourth
Without Toolkit
function updateVeryNestedField(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue
}
}
}
}
}
With Toolkit:
const reducer = createReducer(initialState, {
UPDATE_ITEM: (state, action) => {
state.first.second[action.someId].fourth = action.someValue
}
})
configureStore
The Toolkit configureStore actually does save a step vs the Redux createStore function when you are combining more than one reducer. But again this example fails to show it. Instead the Toolkit version is longer because we set a reducer property rather than just passing the reducer.
A typical Redux app uses the combineReducers utility to combine multiple reducers as properties on an object:
import {createStore, combineReducers} from "redux";
const rootReducer = combineReducers({
counter: counterReducer,
other: otherReducer
});
const vanillaStore = createStore(rootReducer);
With the Toolkit you can just pass your reducers map directly without calling combineReducers.
import {configureStore} from "#reduxjs/toolkit";
const toolkitStore = configureStore({
reducer: {
counter: counterReducer,
other: otherReducer
}
});
Which is roughly the same amount of code. But it also includes some default middleware which would be extra lines in the non-toolkit example.

React Redux accessing dynamically filtered state in mapStateToProps - rerendering woes

I have a functional component, that is passed instructions on what to pull from the redux store.
Using mapStateToProps=(state, ownProps), I can happily pull the required items from state (store) - but, at a cost of any changes in the entire state tree triggering rerunning mapStateToProps and a gazillion rerenders.
Let me unpack.
Here's a snapshot of part of the store:
{
settings: {...stuff...},
projects: [...stuff...],
definitions: [...stuff...],
themes: [...stuff...],
surfaces: {
'6': { <--- VARIABLE PASSED TO COMPONENT
surface: {
STRIP: [..stuff..],
GLOBAL: { <--- CATEGORY PASSED TO COMPONENT
DISPLAY: {...stuff...},
ASSIGNMENT: { <--- LIST OF REQUIRED OBJECTS HAS
A_TRACK: { SUBCATEGORY AND TARGET (A_TRACK etc...)
value: 0,
type: 'switch',
label: 'TRACK'
},
A_SEND: { <--- ANOTHER OBJECT I NEED TO GET
value: 0,
type: 'switch',
label: 'SEND'
},
A_PAN: {
value: 0,
type: 'switch',
label: 'PAN'
},
},
FADER_BANKS: {...stuff...},
STATUS: {...stuff...},
LOTS_MORE_STUFF
My parent component passes the required instructions to the child.
<RefMixerGroup
portId = {this.props.portId}
items={[
{parent: 'GLOBAL', group: "ASSIGNMENT", target: "A_TRACK"},
{parent: 'GLOBAL', group: "ASSIGNMENT", target: "A_SEND"},
]
}
/>
mapStateToProps is pretty simple:
const mapStateToPropy = (state, ownProps) => {
return {
groupItems: getItemsFromState(state.surfaces[ownProps.portId].surface, ownProps.items)
}
}
and the work is done in a simple function:
const getItemsFromState = (subState, items)=>{
let groupItems=[]
for (let i = 0; i < items.length; i++) {
const item = items[i];
const base = subState[item.parent];
let groupItem = base[item.group][item.target]
groupItems.push({...groupItem, target: item.target})
}
return groupItems
}
But because I am creating this array of matches, I think redux thinks I should be subscribing to every item in the tree...when I only want changes on the found elements, in this case:
surfaces[6].surface[GLOBAL][ASSIGNMENT][A_TRACK]
surfaces[6].surface[GLOBAL][ASSIGNMENT][A_SEND]
I tried using reselect and the rereselect instead of my getItemsFromState function above,
but all with the same result. Any change in that tree, starting with surfaces[6] triggers mapsStateToProps and a rerender.
There must be way around this, but I can't figure it out. I tried using areStatesEqual but it only provides nextState and prevState, and I need ownProps to compute equality. I possibly could use areStatePropsEqual, but that only works AFTER recomputing mapStateToProps unnecessarily.
There must be a way!
getItemsFromState is creating a new groupItems array reference every time it runs. It will be called after every dispatched action. Since connect re-renders any time any of the fields returned by mapState have changed to a new reference, your code is forcing React-Redux to re-render every time.
This is specifically why you should use memoized selectors to only return new derived data references if the input references have changed, typically with Reselect's createSelector. If your use of Reselect isn't helping here, it's likely that your selectors aren't being set up correctly, but I'd need to see specific examples to give advice there.
It's also why components should subscribe to the smallest amount of data that they actually need.
If you are using a function component, I'd suggest using useSelector instead of connect as well.

How do I define a reducer at the top level?

I have two reducer files that handle the two areas of state in my module...
export interface MyState {
people: PeopleState;
pets: PetState;
}
export const reducers: ActionReducerMap<MyState> = {
people: peopleReducer,
pets: petsReducer
};
This works fine. However, I have some actions that need to update both areas of the state. I'd like to handle this by having a reducer file that deals with things at the MyState level and can update people and pets. I'd like to keep both the existing reducers that handle this at the lower levels.
I can't see how to register the top level reducer. The way the code is now, any reducer added to the ActionReducerMap must be added as a property inside MyState rather than handling MyState as a whole.
Anyone got any ideas?
Thanks
Nick
I guess that the only solution here is to use metaReducer, check this article: https://netbasal.com/implementing-a-meta-reducer-in-ngrx-store-4379d7e1020a
Description: metaReducer is a kind of reducer that stays above other reducers. It should have it's own actions, can have it's own effects. You can use it in such way:
export function metaReducer( reducer ) {
return function reduce( state: ParentState, action: MetaReducerAction ) {
switch(action.type)
case PARENT_ACTION:
return {
...state,
people: peopleReducer(state.people, new DoSthWithPeople()),
pets: petsReducer(state.pets, new DoSthWithPets())
}
default:
return reducer(state, action);
}
}
Where:
interface ParentState {
pets: PetsState,
people: PeopleState
}
type MetaReducerAction = ParentAction <-- this has type PARENT_ACTION
So the workflow is straight forward. In a place that you want a action to update both people and pets states, you need to dispatch PARENT_ACTION, then the actions DoSthWith... are going to be triggered on both slices of state. If you dispatch different action (the action of type that is not handled by the metaReducer, so sth different to PARENT_ACTION) then it will allow other reducers to handle the action (check whats in default section).
The last part is configuration, it should look like this:
StoreModule.forFeature(compose(metaReducer, combineReducers)(reducers))
Where reducers is just:
const reducers = {
pets: petsReducer,
people: peopleReducer
}
Edit: formatting

Reusability of reducers at different levels of app state structure

For example we have reducer photos, which handles array of photos via actions ADD_PHOTO and REMOVE_PHOTO. And if we have arrays users and posts, they both have field for array of photos.
So, in order to avoid code duplicates I'm going to do the following:
Create reducer user = combineReducers(..., photos, ...)
Create actionCreator updateUser
const updateUser = (id, subAction) => ({
type: UPDATE_USER,
payload: {
id,
subAction
}
})
Create reducer users (Here I'm using Immutable.js)
function users(state = List(), action) {
switch (action.type) {
//...
case UPDATE_USER:
const { id, subAction } = action.payload
const index = state.findIndex(user => user.id == id)
return state.updateIn(
[index, 'photos'],
state => photos(state, subAction)
)
break
//...
default:
return state
}
}
And then I'm going to use all of it like this:
dispatch(updateUser(id, addPhoto(url)))
Is this a correct solution of my problem?
Why not simply dispatch both in the place where this is initiated by the user?
dispatch(updateUser(id));
dispatch(addPhoto(url));
I haven't come across this pattern you're applying before. It seems a bit unusual that one reducer is responsible for reducing state of another. Creates a dependency between them that doesn't feel very pure. I'm not even sure one reducer should be able to/can see the state of another.
So no idea about "correct", but I'd say it's not ideal to do it your way. I'd try dispatching both sequentially or maybe in a sort of meta-action that takes care of nested updates and dispatches actions to multiple reducers.

How to avoid using literal strings to narrow disjoint unions in flow

All the examples I find online for narrowing the disjoint union in flowtype uses string literals, like the official one. I would like to know if there is a way to check against a value from an enum like:
const ACTION_A = 'LITERAL_STRING_A';
const ACTION_B = 'LITERAL_STRING_B';
type ActionA = {
// This is not allowed
type: ACTION_A,
// type: 'LITERAL_STRING_A' is allowed
dataA: ActionAData,
}
type ActionB = {
// This is not allowed
type: ACTION_B,
// type: 'LITERAL_STRING_B' is allowed
dataB: ActionBData,
}
type Action = ActionA | ActionB;
function reducer(state: State, action: Action): State {
// Want to narrow Action to ActionA or ActionB based on type
switch (action.type) {
// case 'LITERAL_STRING_A': -- successfully narrow the type
case ACTION_A: // doesn't work
// action.dataA is accessible
...
}
...
}
Unfortunately you can't do these because strings are ineligible as type annotations.
If there is any other way around this that doesn't force typing the string literals everywhere I would love to know.
If there isn't a way around this, also accept suggestions on a higher level how to not need to define these disjoint sets for redux actions.
I'm not in my best shape right now, so sorry if I read your question wrong. I'll try to help anyway. Is this what you're looking for?
const actionTypes = {
FOO: 'FOO',
BAR: 'BAR'
}
type ActionType = $Keys<actionTypes> // one of FOO, BAR
function buzz(actionType: ActionType) {
switch(actionType) {
case actionTypes.FOO:
// blah
}
This should work. Sorry if my syntax is a bit off.
If you're asking how to avoid listing all action types in type Action = ActionA | ActionB then sorry, I don't know, I think this is the way you do it. If I recall correctly, a slightly nicer syntax for defining long unions was recently introduce in Flow:
type Action =
| ActionA
| ActionB
| ActionC
Also, if you don't need individual action types, you can just do
type Action =
| {type: ACTION_A; dataA: ActionAData;}
| {type: ACTION_B; dataB: ActionBData;}
The better way would be to use string literal types for const values:
Try flow...
const ACTION_A:'LITERAL_STRING_A' = 'LITERAL_STRING_A';
const ACTION_B:'LITERAL_STRING_B' = 'LITERAL_STRING_B';

Resources