Loading static constants in React+Redux - reactjs

I'm using server side rendering for my React-Redux application. And I want at application startup to load some constants, for example list of cities with corresponding IDs:
[
{
name: "London",
id: 1
}
...
]
I think it's better to put this data into store on server side and provide it to client using window.__INITIAL_STATE__ as suggested here http://redux.js.org/docs/recipes/ServerRendering.html
This constants will be read-only, and I want to preload them just for data normalization purposes. For example later I can just retrieve list of users with city IDs, instead of users with city names: {name: "Alex", cities: [1,2]}
The problem is that if I put them into store, then I forced to create reducer for this constants, otherwise I'm getting this error:
Unexpected key "cities" found in preloadedState argument passed to
createStore. Expected to find one of the known reducer keys instead:
"colors". Unexpected keys will be ignored.
So I'm searching for some elegant way to handle this situation.
For now I have 2 ideas how to handle it:
Create empty reducer which always will return default state
export const cities = (state = [], action={ type: null }) => {
return state
}
Send from server initial actions with payloads, and execute them on client at startup:
// server.js
window.INITIAL_ACTIONS = [
{ type: "REQUEST_LOGIN_SUCCESS", user: {userId, userName, userEmail, etc} },
{ type: "REQUEST_CITIES_SUCCESS", [..listOfCities] },
]
And in my client-index.js, dispatch those actions right after creating the store:
//client-index.js
window.INITIAL_ACTIONS.forEach(store.dispatch)
So, is one of my approaches is good? Or may be you know some other, more elegant solution?
Thanks.

We do something similar with a dummy "settings" reducer. i.e.
const rootReducer = combineReducers({
...
settings: (state = {}) => state,
...
});
This gives us a convenient place to store all our app config.
Just make sure you key your initial state in the same manner. i.e.
window.__INITIAL_STATE__ = {
...
settings: { ... },
...
};
Some may object to this practise, but I think it's sound. Though settings may be constant, it is nonetheless state. It conforms to the redux practice of a single state object. (Besides, there may come a future point where the settings state slice will be dynamic and require a "real" reducer.)

Related

one action updates multiple reducers performance issue

In the current app I have set up multiple redux reducers to store data. Lets just call them 'user-reducers' and 'pet-reducers'. The 2 are very similar, they both have a posts[]. Now whenever user likes a post inside posts[], an action 'likeDone' is fired. Now I have 2 choices of implementing the redux update:
Option 1: both 'user-reducers' and 'pet-reducers' listens to 'likeDone'. IMO, this is inefficient in the later stages when I have more similar reducers and all of them listing to one action.
Option 2: change 'likeDone' into 2 more explicit actions ex. 'likeUserPostDone' 'likePetPostDone' and each reducer reacts to the matched action. This way, reducer updates seem more efficient but there will be more action types later on which will end up with lots of 'switch - case' and I'm not sure if that is a good practice.
Thanks for reading and please tell me what's the best for my case.
Have both reducers listen to your action if they both change when that action occurs. All redux reducers efficiently listen to all dispatched actions. They ignore actions if it doesn't change state.
const petPostReducer = (prev = defaultValue, action = {}) => {
const { type, payload} = action;
switch (type) {
case "LIKE_DONE": {
const {id} = payload || {};
const oldItem = prev[id] || {}
const newItem = {...oldItem, like: true}
return {
...prev
[id]: {...updateItem, [id]: newItem};
}
default: {
return prev; // Ignore all other actions != LIKE_DONE
}
}
};
It is fine to create more explicit actions, but you usually want to do so when you have different actions. (There are always exceptions)
When possible, avoid logic in your reducers.
If pets and user data are very similar, consider changing your state shape and use one reducer posts.
console.log(state.posts) =>
{
0: {type: 'user' like: false},
1: {type: 'pet' like: false},
2: {type: 'user' like: true},
}
Please avoid premature optimization. I often do things that are less efficient in favor of simple, concise, immutable, readable and useful code. If you are targeting a feature phone or find a real performance bottleneck, you will need to measure with performance monitoring tools. The JavaScript engine optimizations are continuously improving and results are often surprising.

Handling children state in an arbitrary JSON tree

This is more of a brainstorming question as I can't really seem to come up with a good solution.
I have a component which renders a tree based on some passed JSON (stored at the top level). Each node of the tree can have 0..n children and maps to a component defined by the JSON of that node (can be basically anything is the idea). The following is just an example and the names don't mean anything specific. Don't pay too much attention to the names and why a UserList might have children that could be anything.
JSON: {
data: {}
children: [
{
data: {}
children: []
},
{
data: {}
children: []
},
{
data: {}
children: [
{
data: {}
children: []
},
...etc
]
},
]
}
const findComponent = (props) => {
if (props.data.name === "userSelector") {
return <UserSelectorNode {...props}>;
} else if (props.data.name === "userInformation") {
return <UserInformationNode{...props}>; // example of what might be under a userSelectorNode
}
...etc
};
// render a user selector and children
const UserSelectorNode = (props) => {
const [selected, setSelected] = React.useState([])
// other methods which can update the JSON when selected changes...
return (
<div>
<UserSelector selected={selected}/> // does a getUser() server request internally
<div>
{props.data.children.map((child) => findComponent(child))}
<div>
</div>
);
};
This tree can be modified at any level (add/remove/edit). Adding/Editing is easy. The problem is remove operations.
Some children components use existing components which do things like getting a list of users and displaying them in a list (stored in state I have no access to). When a node on the tree is removed new components are made for every node that has to shift (JSON at the index is now different), which can be a lot. This causes a bunch of requests to occur again and sometimes state can be lost entirely (say the page number of a table to view users).
I'm pretty sure there is no way for the "new" UserSelector created when the JSON shifts to keep the same state, but I figured I may as well ask if anyone has dealt with anything similar and how they went about designing it.
The only way I can think of is to not actually reuse any components and re implement them with state stored somewhere else (which would suck), or rewrite everything to be able to take internal state as well as an external state storage if required.
EDIT: Added Sandbox
https://codesandbox.io/s/focused-thunder-neyxf. Threw it together pretty quick to only get a single layer of remove working which shows the problem.

redux, normalizr, access store mapDispatchToProps

If I have a data object like below
{
items: [
{id: 1, selected: false},
{id: 2, selected: false}
]
}
Running this through the normalizr would get me something like
{
entities: {
1: {selected: false},
2: {selected: false}
},
result: {
items: [1, 2]
}
}
Typically I would have an Items component that maps the state to props like this:
const mapStateToProps = (state) => {
return state.result;
};
The problem I have is on the Items component, I would have a save button that needs to send the state of the store to the server.
I haven't seem to be able to find a good way to access the store to denormalize the data before sending it to the server.
1) I prefer not to include the Entities in MapStateToProps because there is no need for the items component to know about it.
2) MergeProps also seems suboptimal due to performance issues.
Right now I'm accessing the store directly by simply importing the store and accessing it in my dispatch methods which seems to work, but that breaks the separation of concern, is there a better way to do this?
Judging from the description of your problem, I believe you send the store's data by a method inside Save button component, or another React component. If you do that, you must expose the sending data to that component no matter what.
An alternative solution is to handle the API call by a middleware. Libraries such as redux-saga or redux-thunk can handle your case neatly. They are designed to handle async flows, and provide access to the store.

Better syntax to update nested objects on redux

Given a reducer example like the following
_({
expandAbility: (state, a: { which: string }) => ({
...state,
report: state.report && {
waitingForIt: false,
content: state.report.content && {
...state.report.content,
interaction: {
expandAbilities: !state.report.content.interaction.expandAbilities.contains(a.which)
? state.report.content.interaction.expandAbilities.add(a.which)
: state.report.content.interaction.expandAbilities.remove(a.which)
}
}
}
}),
})
(state type given below just for question context purposes)
const initialState = {
report: undefined as
| {
waitingForIt?: boolean
content?: {
supportedFightIds: number[]
deathCut: number
reportInfo: any
deathsFull: any
deathsByPlayer: any
deathsByAbility: any
interaction: {
expandAbilities: Set<string>
}
}
}
| undefined,
error: undefined as Error | undefined
}
Is there any kind of trick or "flavor-of-the-moment" library which would allow me to write a reducer update operation like expandAbility in a shorter way? (besides maybe creating some vars to reference inner paths)
There are lots of immutable update utilities out there, check out some options at https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities and see what would be the best fit for you.
For starters check out Immutability-helper or immer.
So there are two things you could do to help simplify this. The first thing I like to do is move the logic out of the reducer and instead just pass in a value and say set expandAbilities to action. expandAbilities.
The second is actually something we do at work. We use immutableJS and wrote a single reducer that handles all of our state calls because you can give it a path of the parts of state that need to be updated and the value to update it with so we extracted that out and now it is easy to say dispatch(actions.update({path: ['report', 'content', 'interaction', 'expandAbilities'], value: '123' }))
You can even expand this so you can pass in a list of values that need to be updated and even preform validations around the data.

How do I access Redux items using selectors after I've normalized my store?

I'm a bit confused on how I'm supposed to use selectors after I've normalized my Redux store.
I have the following setup for my store:
const DEFAULT_STATE = {
allId: [],
locations: {}
};
With the following for my reducer:
handleActions({
['UPDATE']: (state, action) => {
let newID = state.allId.length;
const allId = [...state.allId, newID];
const locations = {
...state.locations,
[newID]: action.payload
};
return {allId, locations};
}
}),
...
I figured I would want something like this for my component:
function mapStateToProps(state) {
return {
share: callMySelector(state)
};
}
But I don't see how my selector would do anything except return the location associated with the most recent ID. I'm thinking that normalizing is also not that great here - because I wouldn't end up searching by ID in a regular case.
The power of selectors is that it moves filtering logic away from the component consuming the data, and away from the actions/reducer into reusable functions. You mentioned getting the most recent location. From the update logic in the reducer, we'd just make a selector that grabs the last item.
function selectLatestLocation(state) {
const latestId = state.allIds[state.allIds.length - 1];
return state.locations[latestId];
}
This assumes the location data is structured with the location id as the key.
{
1: { id: 1, name: "USA" },
2: { id: 2, name: "Europe" }
}
In this case, normalizing the data isn't doing much. But let's say requirements change, and now we only want Europe locations. We could have another state property called europeIds that contains all Europe location ids.
function selectEuropeLocations(state) {
return state.europeIds.map(id => state.locations[id]);
}
Using selectors with normalized Redux state make it really easy to change how data is filtered. Now like you said, some cases might not need to be normalized. It is really up to the project, and what is being accomplished. But, it's definitely worth it for data that needs to be memoized, or filtered in different ways!

Resources