Redux - I don't understand "Task-Based Updates" example - redux

In link: Task-Based Updates
I don't understand below code:
import posts from "./postsReducer"; // missing this code??
import comments from "./commentsReducer"; // missing this code??
and why should do that?
const combinedReducer = combineReducers({
posts,
comments
});
const rootReducer = reduceReducers(
combinedReducer,
featureReducers
);
only featureReducers is okie? not need combindedReducer? anh what is postsReducer code, commentsReducer code?
Thanks for helps!

Unfortunately that example is a little confusing (one of the few places in the normally solid Redux docs). If you go here and check out the 'third approach', you'll see this concept explained a little better.
Essentially, postReducer and commentReducer are there to handle actions that modify only posts or only comments--that is, things that do not require changes to multiple tables (e.g posts AND comments). featureReducer is the task-based reducer that handles actions that require updates to multiple tables.
A very simplified example:
const postReducer = (state = {}, action) => {
return state
}
const commentReducer = (state = {}, action) => {
return state
}
const combined = combineReducers({
posts: postReducer,
comments: commentReducer
})
const createComment = (state, action) => {
switch(action.type) {
case 'CREATE_COMMENT':
// update multiple tables (linked example actually does an ok job ok demonstrating this)
return updatedState
default:
return state;
}
}
const rootReducer = reduceReducers( combined, createComment )
In this example, the first two reducers just create the state shape. The third, which depends on the first two to set the state up for it, updates multiple tables across the redux store.
Your state shape will look like this:
{
posts: {},
comments: {}
}
If you're confused about reduceReducers, just try to think of it like combineReducers, only it doesn't affect state shape.

Related

Get current state inside the payload

In react, if we want to make a calculation which is based on a property set inside the state, we can write the setState as follows:
const [counter, setCounter] = useState(5)
// instead of this:
// setCounter(counter + 1)
// you can do this:
setCounter((previousState)=> previousState + 1)
This is one of the fundamentals in react.
I am having a complex problem in my code, let's say, so I have no idea how to explain it in a simple question, but let's try to hide all that for now and see if it requires posting the details later 😁.
Is there a similar thing in redux toolkit where I can for instance do something as follows:
import { setCounter } from 'features/posSlice'
const MyComponent = () => {
const dispatch = useDispatch()
dispatch(setCounter((previousState) => previousState + 1))
}
Now you might ask, why you don't implement this inside the reducers? so by doing something as follows:
import { createSlice } from '#reduxjs/toolkit'
const posSlice = createSlice({
name: 'pos',
initialState: {
counter: 5
},
reducers: {
incrementBy = (state, { payload }) => {
state.counter = state.counter + payload
}
}
})
I would say, okay… that's a good question, the reason is, not easy to explain because this is not the real example.
Just for now, let's make it simple:
I would like to know if there's a method to get the current state among all these async calls TO get the correct..current..state in order to build upon it. from inside the controllers, not the models.
So, something like this inside the component, not the slice:
import { setCounter } from 'features/posSlice'
const MyComponent = () => {
const dispatch = useDispatch()
dispatch(setCounter((previousState) => previousState + 1))
}
Is it possible?
thanks 🌹
https://github.com/reduxjs/redux-toolkit/issues/3122#issuecomment-1404879054
I'm sorry, but the question is really "why you don't implement this
inside the reducers".
Caclulation of new state should always happen in a reducer - you
should only pass stuff you need for that calculation into the payload,
never the new value. Please give the Redux Style Guide a good read.
That said, you can do this in a thunk, although I would really
recommend against it.
dispatch((dispatch, getState) => {
dispatch(setCounter(getState().yourCounterValue + 1)) })
So simply do it as
import { useDispatch } from 'react-redux'
// inside a component
const dispatch = useDispatch()
dispatch((dispatch, getState)=>{
dispatch(getState().pos.counter + 1)
})

changing state with RTK Query

I'm learning about RTK Query and really confused. I'd be happy if someone could point me towards the right direction. My question is how one can manipulate the state of the application store the same way as it is done when using createAsyncThunk and setting up extraReducers.
export const asyncApiCall = createAsyncThunk("api/get_data", async (data) => {
const config = {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
};
const res = await axios.get( "http://apiserver/path_to_api",data,config );
return res['data']
} )
export const mySlice = createSlice({
name:"mySliceName",
initialState:{
data: [],
loadInProgress: false,
loadError: null,
extraData: {
// something derived based on data received from the api
}
},
extraReducers: {
[asyncApiCall .pending]: (state) => {
state.loadInProgress = true;
},
[asyncApiCall .fulfilled]: (state,action) => {
state.loadInProgress = false;
state.data = action.payload;
state.extraData = someUtilFunc(state.data)
},
[asyncApiCall.rejected]: (state) => {
state.loadInProgress = false;
state.loadError= true;
},
}
})
Now I'm replacing it with RTK Query. My current understanding is that RTK Query automatically generates hooks for exposing data received from the api and all the query-related info like if it's pending, if an error occurred etc.
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react'
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: builder => ({
getData: builder.query({
query: () => '/get_data'
}),
setData: builder.mutation({
query: info => ({
url: '/set_data',
method: 'POST',
body: info
})
})
})
})
export const { useSendDataMutation, useGetDataQuery } = apiSlice
If I want to store some additional data that may be affected by the api calls should I create another slice that will somehow interact with the apiSlice, or is it possible to incorporate everything in this existing code? I'm sorry for possible naivety of this question.
The short answer is that RTK Query is focused on purely caching data fetched from the server. So, by default, it stores exactly what came back in an API call response, and that's it.
There are caveats to this: you can use transformResponse to modify the data that came back and rearrange it before the data gets stored in the cache slice, and you can use updateQueryData to manually modify the cached data from other parts of the app.
The other thing to note is that RTK Query is built on top of standard Redux patterns: thunks and dispatched actions. Every time an API call returns, a fulfilled action gets dispatched containing the data. That means you can also apply another suggested Redux pattern: listening for that action in other reducers and updating more than one slice of state in response to the same action.
So, you've got three main options here:
If the "extra data" is derived solely from the server response values, you could use transformResponse and return something like {originalData, derivedData}
You could just keep the original data in the cache as usual, but use memoized selector functions to derive the extra values as needed
If you might need to update the extra values, then it's probably worth looking at listening to a query fulfilled action in another slice and doing something with it, like this silly example:
import { api } from "./api";
const someExtraDataSlice = createSlice({
name: "extraData",
initialState,
reducers: {/* some reducers here maybe? */},
extraReducers: (builder) => {
builder.addMatcher(api.endpoints.getPokemon.matchFulfilled, (state, action) => {
// pretend this field and this payload data exist for sake of example
state.lastPokemonReceived = action.payload.name;
}
}
})

React-redux dispatch action onclick using hooks and redux toolkit

Fairly new to redux, react-redux, and redux toolkit, but not new to React, though I am shaky on hooks. I am attempting to dispatch an action from the click of a button, which will update the store with the clicked button's value. I have searched for how to do this high and low, but now I am suspecting I am thinking about the problem in React, without understanding typical redux patterns, because what I expect to be possible is just not done in the examples I have found. What should I be doing instead? The onclick does seem to capture the selection, but it is not being passed to the action. My goal is to show a dynamic list of buttons from data collected from an axios get call to a list of routes. Once a button is clicked, there should be a separate call to an api for data specific to that clicked button's route. Here is an example of what I currently have set up:
reducersRoutes.js
import { createSlice } from "#reduxjs/toolkit";
import { routesApiCallBegan } from "./createActionRoutes";
const slice = createSlice({
name: "routes",
initialState: {
selected: ''
},
{... some more reducers...}
routeSelected: (routes, action) => {
routes.selected = action.payload;
}
},
});
export default slice.reducer;
const { routeSelected } = slice.actions;
const url = '';
export const loadroutes = () => (dispatch) => {
return dispatch(
routesApiCallBegan({
url,
{...}
selected: routeSelected.type,
})
);
};
createActionRoutes.js
import { createAction } from "#reduxjs/toolkit";
{...some other actions...}
export const routeSelected = createAction("routeSelection");
components/routes.js:
import { useDispatch, useSelector } from "react-redux";
import { loadroutes } from "../store/reducersRoutes";
import { useEffect } from "react";
import { routeSelected } from "../store/createActionRoutes";
import Generic from "./generic";
const Routes = () => {
const dispatch = useDispatch();
const routes = useSelector((state) => state.list);
const selected = useSelector((state) => state.selected);
useEffect(() => {
dispatch(loadroutes());
}, [dispatch]);
const sendRouteSelection = (selection) => {
dispatch(routeSelected(selection))
}
return (
<div>
<h1>Available Information:</h1>
<ul>
{routes.map((route, index) => (
<button key={route[index]} className="routeNav" onClick={() => sendRouteSelection(route[0])}>{route[1]}</button>
))}
</ul>
{selected !== '' ? <Generic /> : <span>Data should go here...</span>}
</div>
);
};
export default Routes;
Would be happy to provide additional code if required, thanks!
ETA: To clarify the problem - when the button is clicked, the action is not dispatched and the value does not appear to be passed to the action, even. I would like the selection value on the button to become the routeSelected state value, and then make an api call using the routeSelected value. For the purpose of this question, just getting the action dispatched would be plenty help!
After writing that last comment, I may actually see a couple potential issues:
First, you're currently defining two different action types named routeSelected:
One is in the routes slice, generated by the key routeSelected
The other is in createActionRoutes.js, generated by the call to createAction("routeSelection").
You're importing the second one into the component and dispatching it. However, that is a different action type string name than the one from the slice - it's just 'routeSelection', whereas the one in the slice file is 'routes/routeSelected'. Because of that, the reducer logic in the slice file will never run in response to that action.
I don't think you want to have that separate createAction() call at all. Do export const { routeSelected } = slice.actions in the slice file, and dispatch that action in the component.
I'm also a little concerned about the loadroutes thunk that you have there. I see that you might have omitted some code from the middle, so I don't know all of what it's doing, but it doesn't look like it's actually dispatching actions when the fetched data is retrieved.
I'd recommend looking into using RTK's createAsyncThunk API to generate and dispatch actions as part of data fetching - see Redux Essentials, Part 5: Async Logic and Data Fetching for examples of that.

Reducers in redux starting to look redundant - any way to combine them in a nicer way?

Using redux, I have a bunch of actions and a bunch of reducers that coincide with each of the action types.
Each of the actions map to a different part of the state being updated (all the action creators are primarily for fetching data from an API, for example, which maps to some part of the state). Our reducers currently look rather silly (simplified to A, B, C, for the sake of example):
export const rootReducer = combineReducers({
somePartOfStateA: someAReducer,
somePartOfStateB: someBReducer,
somePartOfStateC: someCReducer,
... (and many other reducers)
});
function someAReducer(state = [], action) {
switch (action.type) {
case FETCH_SOME_A:
return action.payload;
default:
return state;
}
}
function someBReducer(state = [], action) {
switch (action.type) {
case FETCH_SOME_B:
return action.payload;
default:
return state;
}
}
function someCReducer(state = [], action) {
switch (action.type) {
case FETCH_SOME_C:
return action.payload;
default:
return state;
}
}
// and a bunch of pretty much identical reducers continue below
From my understanding, the purpose of having them split is so that each reducer handles a part of the state's namespaces. The resulting reducers are simple, but pretty much the same thing over and over. Is there a recommended way to consolidate all of these reducers per piece of state?
Reducer is just a function. You could use higher order function to make reducer for you.
const makeSingleActionReducer = type => (state, action) =>
action.type === type ? action.payload : state
export const rootReducer = combineReducers({
somePartOfStateA: makeSingleActionReducer(FETCH_SOME_B)
...
})
Also you could go further by creating a config {stateKey: actionType, ...} and loop over it.

Redux: dispatch function to store?

How is it possible to save a function as state in redux store?
Example:
I pass a function as parameter to a redux-thunk dispatcher function and i want to save this filter function in my redux store:
export const SET_FILTERED_USERS = 'SET_FILTERED_USERS';
export function setFilteredUsers(filter) {
return (dispatch, getState) => {
const allUsers = getState().users.allUsers;
const filteredUsers = allUsers.filter(filter);
dispatch({
type: SET_FILTERED_USERS,
data: {
filteredUsers,
filter
}
});
const activeUser = getState().users.activeUser;
if (activeUser && !_.isEmpty(filteredUsers) && filteredUsers.indexOf(activeUser._id) === -1) {
dispatch(setActiveUser(filteredUsers[0]));
} else {
dispatch(setActiveUser(allUsers[0]));
}
}
}
In ReduxDevTools i can see, "filter" is not dispatched and saved in store. Is there a way to do this?
Thanks
Update: my shortend reducer:
import {
SET_FILTERED_USERS
} from '../actions/users';
import assign from 'object-assign';
export const initialState = {
filter: null,
filteredUsers: null
};
export default function (state = initialState, action = {}) {
const {data, type} = action;
switch (type) {
case SET_FILTERED_USERS:
return assign({}, state, {
filteredUsers: data.filteredUsers,
filter: data.filter
});
default:
return state;
}
}
As Sebastian Daniel said, don't do that. Per the Redux FAQ, that breaks things like time-travel debugging, and is not how Redux is intended to be used: Can I put functions, promises, or other non-serializable items in my store state?
What you could consider as an alternative is storing some kind of description of the filtering you want. As a sort of relevant example, in my current prototype, I'm creating a lookup table of dialog classes that I might want to show, and when I need to show one, I dispatch an action containing the name of the dialog type. My dialog manager class pulls that "dialogType" field out of state, uses that to look up the correct dialog component class, and renders it.
The other question, really, is why you actually want to store a function in your state in the first place. I see what you're trying to do there, but not actually what you're hoping to accomplish with it.

Resources