I have a Redux application that shows a list of posts. The state is more or less this:
{
posts: [
{id: 1, title: 'My Post'},
{id: 2, title: 'Also this one'},
{id: 3, title: 'Other Post'}
],
visible_post_ids: [1, 2]
}
Whenever I load some posts I add them to posts, then I replace the content of visible_post_ids.
This is my action creator for loading posts:
function loadPosts (filters) {
return function (dispatch, getState) {
return fetch(`/posts.json?filters=${filters}`)
.then((response) => response.json())
.then((posts) => {
dispatch(postsLoaded(posts)) // Will update `posts`
const postIds = posts.map((post) => post.id)
dispatch(updateVisiblePosts(postIds)) // Will update `visible_post_ids`
})
}
}
My question is: is it idiomatic to dispatch two (or more) events from a thunk? Or should I dispatch only one and handle it in various reducers?
Quick answer : there is no problem to dispatch two or more actions from a thunk, I think it's a good practice,especially if API Call response contains answers to two completely different concerns.
I think it depends what you are trying to represent, in your case you can have one action that represent an add of new posts and two different reducers can catch it and do different tasks with it.
But you can see that as two different actions (your example) and it's great too.
As Sergey L said, in your case with a unique action (for your case) it can create an interesting "dependency"
If you don't consider scenario when it is possible to postsLoaded without calling updateVisiblePosts, it is better to handle the state change just in postsLoaded.
Especially if you need them to be in sync. For example, if you need a grantee that visible_post_ids does not contains Ids from not existing/loaded posts. Besides it minimizes the updates as each dispatch will cause processing in React.
On the other hand, having these actions separate can make code more clear as you have very simple implementation for each action.
Related
Say I have a music store app where the user searches for guitars. On initial page load, I fetch a few varieties of guitars to display: (acoustic, electric, and bass). Pages of guitar results are returned together from a single API call but will never be displayed together. Therefore, they must be filtered at some point. To view different categories of guitars, the user will toggle the category they view from a react component.
There seems to be two major ways I can approach this problem with immutable and redux.
In Strategy 1, I filter the data on category when it arrives, and store it separately in the redux store. When I want to retrieve the data, I specify the category in the selector.
In Strategy 2, all API data that comes in is stored in an aggregate List "all". When I want to retrieve a particular category of guitars, I used a selector to filter and display from the aggregated data.
STRATEGY 1:
// REDUCER
export const GuitarReducer = (state, action) => {
const { payload, type } = state;
switch (type) {
case "acoustic": {
let existing = // GET EXISTING
return state.set("acoustic",
existing.concat(payload.filter(result => (result.category === "acoustic")))
)
}
case "electric": {
let existing = // GET EXISTING
return state.set("electric",
existing.concat(payload.filter(result => (result.category === "electric")))
)
}
case "bass": {
let existing = // GET EXISTING
return state.set("bass",
existing.concat(payload.filter(result => (result.category === "bass")))
)
}
}
}
// SELECTOR
export const selectCategory = createSelector(
[getCategory, getGuitarReducer],
(category, guitarReducer) => {
return GuitarReducer.get(category);
}
);
STRATEGY 2:
// REDUCER
export const GuitarReducer = (state, action) => {
const { payload, type } = state;
...
let existing = // GET EXISTING
...
return state.set("all",
existing.concat(payload)
)
}
// SELECTOR
export const selectCategory = createSelector(
[selectAllGuitars],
(category, guitars) => {
return guitars.filter(guitar => (guitar.category = category));
}
);
Will one pattern give better performance than another? What pattern better follows best practices for redux?
I have heard that it is best to prefer selectors for computing derived data, and that memoization will cache the results to use when another action is performed on the data such as toggling between tabs. Because of this, it is not clear to me which strategy to prefer.
I think selectors mainly focused of not re-computing derived data in your components (and the benefit of reusing it across other components).
Both in your example are good practices, so I would reframe it as follows. Do you want your datastore to look like in choice one or choice two (original API response). Do you want it to lazily load (choice two), or load categories for all guitars.
Choice 1
Pros
Stores in datastore in format more useful to your application.
Choice two recomputes on category change, choice one is computed at start and most likely more performant.
Cons
No access to original API response.
Performs filtering and categorizing on API request instead of lazily (Honestly not a big problem).
Choice 2
Pros
Stores in datastore original API response.
Lazily computes the required guitar category.
Cons
Performs computation again on category change. (Note reselect only has a cache size of 1).
Memoizing also takes additional memory.
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.
Given the following (and assuming we cannot change the state's structure):
StoreModule.forRoot({
a: aReducer,
b: {
b1: b1Reducer,
b2: b2Reducer
}
});
and b1Reducer is dependent on the value of a (for example because it contains something like user info).
What is the most idiomatic way to access (read-only) a in b1Reducer?
The solution I came up with is using #ngrx/effects, dispatch another action with a that can be used in the reducer:
#Effect()
augmentAction$ = this.action$
.ofType(Actions.Action1)
.withLatestFrom(this.store$)
.switchMap(([action, state]:[Action, AppState]) => {
const a = state.a;
return [new Actions.Action2(a)];
});
This works, but it becomes hard to manage if almost every action needs to be redispatched if a is used in many reducers. Is there a better way to handle this?
I need help designing a simple app which allows user to rate videos using a form. My state is composed by 2 reducers, one that holds data about all ratable videos (in a normalized fashion) and another one that holds the form state:
{
videos: {
'video1Id': { id: 'video1Id', title: 'Cat video', duration: 120, ... },
'video2Id': { ... },
...
},
rateForm: {
'videoId': 'video1Id'
'userComment: 'A nice video about cat'
'formSubmitted': false
...
}
}
Note that, inside rateForm, I reference the video id instead of the video object. Problem is, how can I retreive the whole video object from my rateForm reducer ?
I feel like I'm following the best practice of Redux design but I'm stuck at this really simple use case. Any help appreciated.
Thanks
One thing to remember, reducer should be AS SIMPLE AS POSSIBLE. Only doing atomic operations on reducer level. From what I can tell you trying to retrieve the whole video object in your reducer just doesn't sound right.
Depending on your needs, usually, you don't need to fetch the whole video object if you just want to comment on it or rate it. But if you are 100% sure you have to, A good place to do this is in your action. Using Redux-Thunk, you will have access to the whole state object before you return your thunk. Example
function doSomethingToVideo (videoId, something) {
return (dispatch, getState) => {
const video = getState().videos[videoId]
// Do what ever
return somethingElse
}
}
Reference: Redux author's answer on a similar matter.
Accessing Redux state in an action creator?
I'm building an app where actions are performed as the user scrolls down. It would be nice if I could undo those actions as the user scrolls up again, basically turning scrolling into a way to browse through the time line of actions.
Is there a built-in way in Redux to do this? Or would I have to write middleware for this?
Is there a built-in way in Redux to do this? Or would I have to write middleware for this?
Middleware sounds like the wrong idea in this case because this is purely state management concern. Instead you can write a function that takes a reducer and returns a reducer, “enhancing” it with action history tracking along the way.
I outlined this approach in this answer, and it's similar to how redux-undo works, except that instead of storing the state, you can store actions. (Depends on the tradeoffs you want to make, and whether it's important to be able to “cancel” actions in a different order than they happened.)
I believe the idea is not so much "undo" as much as save a reference to the entire state tree each time an action passes through redux.
You would have a history stack made up of the application state at various times.
let history = [state1, state2, state3]
// some action happens
let history = [state1, state2, state3, state4]
// some action happens
let history = [state1, state2, state3, state4, state5]
// undo an action
let history = [state1, state2, state3, state4]
state = state4
To "undo" an action, you just replace the application state with one of the saved states.
This can be made efficient with data structures that support structural sharing, but in development we don't really need to consider resource constraints too much anyway.
I also wanted to create a simple undo functionality, but had already shipped an app with redux-storage that serializes and loads the state for every user. So to keep it backwards-compatible, I couldn't use any solution that wraps my state keys, like redux-undo does with past: [] and present:.
Looking for an alternative, Dan's tutorial inspired me to override combineReducers. Now I have one part of the state: history that saves up to 10 copies of the rest of the state and pops them on the UNDO action. Here's the code, this might work for your case too:
function shouldSaveUndo(action){
const blacklist = ['##INIT', 'REDUX_STORAGE_SAVE', 'REDUX_STORAGE_LOAD', 'UNDO'];
return !blacklist.includes(action.type);
}
function combineReducers(reducers){
return (state = {}, action) => {
if (action.type == "UNDO" && state.history.length > 0){
// Load previous state and pop the history
return {
...Object.keys(reducers).reduce((stateKeys, key) => {
stateKeys[key] = state.history[0][key];
return stateKeys;
}, {}),
history: state.history.slice(1)
}
} else {
// Save a new undo unless the action is blacklisted
const newHistory = shouldSaveUndo(action) ?
[{
...Object.keys(reducers).reduce((stateKeys, key) => {
stateKeys[key] = state[key];
return stateKeys;
}, {})
}] : undefined;
return {
// Calculate the next state
...Object.keys(reducers).reduce((stateKeys, key) => {
stateKeys[key] = reducers[key](state[key], action);
return stateKeys;
}, {}),
history: [
...(newHistory || []),
...(state.history || [])
].slice(0, 10)
};
}
};
}
export default combineReducers({
reducerOne,
reducerTwo,
reducerThree
});
For me, this works like a charm, it just doesn't look very pretty. I'd be happy for any feedback if this is a good / bad idea and why ;-)
There's no built-in way to do this.
but you can get inspired by how redux-dev-tools works (https://github.com/gaearon/redux-devtools). It basically have "time travel" functionality and it work by keep a track of all actions and reevaluating them each time. So you can navigate easily thorough all your changes.