I'm using the pattern described here that show us how to reuse reducer logic for other similar purposes.
So, my reducer code is like the code below:
function ContentFilterReducer(entity = ''){
initialState.groupFilter = entity;
return function ContentFilterReducer(state = initialState, action)
{
// is the entity that we want to update?
if (action.item !== undefined && action.item.groupFilter !== entity)
return state;
switch (action.type) {
case ContentFilterTypes.ADD_ITEM:
return {
// we set the
groupFilter: action.item.groupFilter,
listObjects : state.listObjects.push(new Map({
id: action.item.id,
description: action.item.description,
imgSrc: action.item.imgSrc
}))
}
default:
return state;
}
}
}
My combinedReducer describe a reducer for each purpose, as we can see below:
const SearchReducers = combineReducers({
// contains all allowed filters to be selected
UsersContentFilterReducer : ContentFilterReducer(Types.users),
OrganizationsContentFilterReducer : ContentFilterReducer(Types.organizations)
})
Everything is working great, however I'd like to know, how to connect it in a React component using the connect function from React-Redux?
As we can see, I can define the reducer setting an entity (a simple char like 'a', 'o', etc) and, to call the specific reducer, I need only set the entity in my action. And now, the problem is how to connect a specific reducer for a specific presentational component?
The code below is my HOC container that connect the reducer to a specific component, however, the code is the old version, without defining wich reducer should call.
const mapStateToProps = (state, action) => {
return {
contentList: ContentFilterReducer(state.ContentFilterReducer, action)
}
}
/**
*
* #param {contains the action that will be dispatched} dispatch
*/
const mapDispatchToProps = (dispatch) => {
return {
onAddClick: (groupFilter, filterDescription, operator, value) => {
dispatch(AddFilter(groupFilter, filterDescription, operator, value));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ContentFilterField)
You don't connect a reducer. You connect a component to the Redux store. I won't name my state xxxReducer, it's a little bit confusing.
I'm not sure what your app looks like, for a simple case, you just need to: (connect both state)
const mapStateToProps = (state) => {
return {
userContentList: state.SearchReducers.UsersContentFilterReducer,
organizationContentList: state.SearchReducers.OrganizationsContentFilterReducer,
}
}
If you want to switch between usersContent and organizationsContent dynamically based on your component's state, what you need is a selector function.
This is the official redux example: https://github.com/reactjs/redux/blob/master/examples/shopping-cart/src/reducers/index.js#L10-L26
These functions are selectors, you import and use them to get the state you want.
So you will create something like getContentList and it accepts a type like Types.users
const mapStateToProps = (state) => {
return {
// suppose you save current type saved in SearchReducers.type
contentList: getContentList(state.SearchReducers.type)
}
}
Also, the second parameter of mapStateToProps is ownProps not action.
Related
We have written a React app using Redux via Redux Toolkit. So far so fine.
Now the React app shall be rendered into multiple different elements (each element shall get a new app instance) on the same page.
The rendering part is straight forward: We just call ReactDOM.render(...) for each element.
The Redux part again brings some headache.
To create a new Redux store instance for each app instance, we call the configureStore function for each React app instance. Our slices look similiar to this:
import { createSlice } from '#reduxjs/toolkit'
import type { RootState } from '../../app/store'
// Define a type for the slice state
interface CounterState {
value: number
}
// Define the initial state using that type
const initialState: CounterState = {
value: 0,
}
const counterSlice = createSlice({
name: 'counter',
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
}
},
});
export const increment = (): AppThunk => async (
dispatch: AppDispatch
) => {
dispatch(indicatorsOrTopicsSlice.actions.increment());
};
export const decrement = (): AppThunk => async (
dispatch: AppDispatch
) => {
dispatch(indicatorsOrTopicsSlice.actions.decrement());
};
// Other code such as selectors can use the imported `RootState` type
export const selectCount = (state: RootState) => state.counter.value
export default counterSlice.reducer
Please note, that currently we create and export each slice statically and only once. Here comes my first question: Is this actually valid when creating multiple store instances or do we actually need to create also new slice instances for each app/store instance?
For the simple counter example provided, doing not so, seems to work, but as soon as we use an AsyncThunk as in the example below the whole thing breaks.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], isLoading: false, hasErrors: false },
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
builder.addCase(fetchUserById.pending, (state, action) => {
state.isLoading = true;
});
builder.addCase(fetchUserById.rejected, (state, action) => {
state.isLoading = false;
state.hasErrors = true;
});
builder.addCase(fetchUserById.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload);
state.isLoading = false;
state.hasErrors = true;
});
},
});
I believe the breaking starts here because of interdifferences between the events fired from dispatching the AsyncThunk.
Thereby I think the solution is to call the createAsyncThunk function for each app/store/slice instance. Are there any best practices for doing so? Of course this breaks the beauty and functionality of static exports and requires kind of a mapping, hence I'm asking.
My original suspicion that the AsyncThunk-part was responsible for the interferences between the stores of the different React app instances was wrong.
The source was something different not visible in the examples provided in my question.
We use memoized selectors via createSelector from reselect. Those were created and exported like the rest statically which in fact is a problem when working with multiple store/app instances. This way all instances use the same memoized selector which again doesn't work correctly thereby, since in the worst scenario the stored values of the dependency selectors are coming from the use from another store/app instance. This again can lead to endless rerenderings and recomputations.
The solution I came up with, is to create the memoized selectors for each app instance freshly. Therefore I generate a unique id for each app instance which is stored permanently in the related Redux store. When creating the store for an app instance I create also new memoized selectors instances and store them in a object which is stored in a static dictionary using the appId as the key.
To use the memoized selectors in our components I wrote a hook which uses React.memo:
import { useMemo } from "react";
import { useSelector } from "react-redux";
import { selectAppId } from "../redux/appIdSlice";
import { getMemoizedSelectors } from "../redux/memoizedSelectors";
// Hook for using created memoized selectors
export const useMemoizedSelectors = () => {
const appId = useSelector(selectAppId);
const allMemoizedSelectors = useMemo(() => {
return getMemoizedSelectors(appId);
}, [appId]);
return allMemoizedSelectors;
};
Then the selectors can be used in the components like this:
function MyComponent(): ReactElement {
const {
selectOpenTodos,
} = useMemoizedSelectors().todos;
const openTodos = useSelector(selectOpenTodos);
// ...
}
and the related dictionary and lookup process would look like this:
import { createTodosMemoizedSelectors } from "./todosSlice";
/**
* We must create and store memoized selectors for each app instance on its own,
* else they will not work correctly, because memoized value would be used for all instances.
* This dictionary holds for each appId (the key) the related created memoized selectors.
*/
const memoizedSelectors: {
[key: string]: ReturnType<typeof createMemoizedSelectors>;
} = {};
/**
* Calls createMemoizedSelectors for all slices providing
* memoizedSelectors and stores resulting selectors
* structured by slice-name in an object.
* #returns object with freshly created memoized selectors of all slices (providing such selectors)
*/
const createMemoizedSelectors = () => ({
todos: createTodosMemoizedSelectors(),
});
/**
* Creates fresh memoized selectors for given appId.
* #param appId the id of the app the memoized selectors shall be created for
*/
export const initMemoizedSelectors = (appId: string) => {
if (memoizedSelectors[appId]) {
console.warn(
`Created already memoized selectors for given appId: ${appId}`
);
return;
}
memoizedSelectors[appId] = createMemoizedSelectors();
};
/**
* Returns created memoized selectors for given appId.
*/
export const getMemoizedSelectors = (appId: string) => {
return memoizedSelectors[appId];
};
I have a todo app that does all 4 crud operations but I can't filter them based on their current status here's the app on codesandbox.
import { SET_VISIBILITY_FILTER } from "../actionTypes";
const initialState = {
filters: ["SHOW_ALL"]
};
const visibilityFilter = (state = initialState, { type, payload }) => {
switch (type) {
case SET_VISIBILITY_FILTER:
return { payload };
default:
return state;
}
};
export default visibilityFilter;
Any explanations will be appreciated.
I have also checked other react redux todo app github repos but most of them are old and it didn't look like they were writing in the best possible way, so I am trying to find a better way (and so far failing at it)
A few issues
filters is an array in the initial state, but you send single values there after in your action, and you also use it a single value when filtering with it.
you expect payload in your reducer but the data you dispatch does not wrap things in payload
dispatch({
type: SET_VISIBILITY_FILTER,
filter
});
in continuation to the above you should use the already defined action setFilter for setting a filter, which correctly wrap the data in a payload property.
fixing these 3 issues, you get https://codesandbox.io/s/problems-with-redux-forked-hv36h which is working as intended.
What you are doing is an anti-pattern when you mutate the redux state variable inside the component like this:
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case "SHOW_ALL":
return todos;
case "SHOW_COMPLETED":
return todos.filter((t) => t.completed);
case "SHOW_ACTIVE":
return todos.filter((t) => !t.completed);
default:
return todos;
}
};
Instead what you should do, listen to the SET_VISIBILITY_FILTER action on toDoReducer.js:
//import SET_VISIBILITY_FILTER action
case SET_VISIBILITY_FILTER:
let toDoClone = [...state.todos]
//if(filter = something)
toDoClone.filter(t => //your condition)
return {
...state,
todos: toDoClone
}
In my react application, I have three parallel components. In my first component, I am doing an API call and based on the response I am routing the flow to Validated or Non-Validated Component.
Once the user is routed to validated component, there is a button on click of which user should be redirected to another component which should display the data in API response (first component) as key value pair. I am using Redux for state management.
The issue I am facing is the data is dispatched as an empty object from the store. I am not sure where I am going wrong but when I am debugging the app, I see the the action is not getting dispatched to the store and it's always returning me the initial state.
action.js-
export const setPoiData = (poiData) => dispatch => {
console.log('inside actions');
dispatch({
type: SET_POI_DATA,
payload: poiData
})
}
Reducer.js-
const initialState = {
poiData: {},
}
const reducerFunc = (state = initialState, action) => {
switch (action.type) {
case SET_POI_DATA:
console.log('inside poi reducers');
return {...state,poiData: action.payload}
default: return {...state}
}
}
Component 1-
//API call
Detail Component-
To get the data from store I am doing something like below-
componentDidMount() {
console.log(this.props.poiData)
}
function mapStateToProps(state) {
return {
poiData: state.poiData,
}
}
const mapDispatchToProps = dispatch => ({
setPoiData(data) {
dispatch(setPoiData(data));
}
})
I am not sure where I am going wrong. Can someone suggest me how to proceed ahead on this?
inside componentDidMount() you must call action like this this.props.setPoiData(<your data here>);
In my React app, my appReducer manages global stuff such as notifications, user info, etc.
One of the modules in the app is the inventory module which has its own reducer i.e. inventoryReducer. And in the redux store, I combine all the reducers.
When a user makes an inventory entry, in addition to handling the inventory transaction, I want to display an on-screen notification which is handled in the appReducer. How do I update the state of displayNotification which is under appReducer from the inventoryReducer?
The following is my app reducer:
import 'babel-polyfill';
import * as types from '../actions/actionTypes';
const initialState = {
displayNotification: {}
};
export default (state = initialState, action) => {
switch (action.type) {
case types.DISPLAY_NOTIFICATION :
return Object.assign({}, state, {
displayNotification: action.value
})
default: return state
}
}
And this is the inventoryReducer:
import 'babel-polyfill';
import * as types from '../actions/actionTypes';
const initialState = {
inventory: []
};
export default (state = initialState, action) => {
switch (action.type) {
case types.SET_INVENTORY :
return Object.assign({}, state, {
inventory: action.inventoryItem
})
case types.DISPLAY_NOTIFICATION :
return Object.assign({}, state, {
app.displayNotification: action.value // <-- Is this how I access `displayNotification` which is managed by the `appReducer`?
})
default: return state
}
}
My update inventory action needs to dispatch both SET_INVENTORY and DISPLAY_NOTIFICATION. I'm trying to understand how I can update displayNotification from inventoryReducer where displayNotification is actually managed by the appReducer.
Following up with what azium said:
I think what you're trying to do is the wrong approach. What's stopping you from a) listening to SET_INVENTORY in your appReducer or b) dispatch both actions from your component?
As far as I understand, in Redux each reducer is allocated a slice of the entire state object and their operations are restricted in that slice. They are not allowed to access the state slice managed by any other reducer, and they shouldn't do that.
The concept description of Redux is that it is a predictable state container. But when I look at what we are trying to achieve in this question, if we were to access/modify state managed by another reducer-B in our reducer-A, the predictability and maintainability of the app are compromised according to me.
Without compromising on anything or moving undesired logic into our components, we can achieve what we need.
Option 1
Inside appReducer
you create a type SET_INVENTORY which does what DISPLAY_NOTIFICATION does. You can have multiple subscriptions for the single action that dispatches type SET_INVENTORY (in appReducer and inventoryReducer).
As shown below, in appReducer, if the action type is either SET_INVENTORY or DISPLAY_NOTIFICATION, the reducer updates the key displayNotification.
export default (state = initialState, action) => {
switch (action.type) {
case types.SET_INVENTORY :
case types.DISPLAY_NOTIFICATION :
return Object.assign({}, state, {
displayNotification: action.value
})
default: return state
}
}
Option 2
Create a method that couples the dispatching of two actions,
let's say you have an action
function setInventory(inventoryItem) {
return {
type: types.SET_INVENTORY,
inventoryItem
};
}
and another action
function displayNotification(value) {
return {
type: types.DISPLAY_NOTIFICATION,
value,
};
}
create a thunk to couple them:
export function notifyAndSetInventory(notify, inventoryItem) {
return dispatch => {
dispatch(displayNotification(notify));
return dispatch(setInventory(inventoryItem));
};
}
In Redux's official document there's a chapter called 'Beyond combineReducers'. It mentioned sharing data between slice reducers.
Sharing data between slice reducers
I personally prefer the third solution mentioned in the link, which is adding a third customized reducer to handle the "special" cases where data needs to be shared across slices, then use reduce-reducers to combine the new customized reducer and the original combined reducer (i.e. appReducer + inventoryReducer).
const crossSliceReducer = (state, action) => {
if (action.type === 'CROSS_SLICE_ACTION') {
// You can access both app and inventory states here
}
return state;
}
// Combine the reducers like you did before
const combinedReducer({app: appReducer, inventory: inventoryReducer});
// Add the cross-slice reducer to the root reducer
const rootReducer = reduceReducers(combinedReducer, crossSliceReducer)
I am trying to follow this code in redux-saga
export const getUser = (state, login) => state.entities.users[login]
export const getRepo = (state, fullName) => state.entities.repos[fullName]
Which is then used in the saga like this:
import { getUser } from '../reducers/selectors'
// load user unless it is cached
function* loadUser(login, requiredFields) {
const user = yield select(getUser, login)
if (!user || requiredFields.some(key => !user.hasOwnProperty(key))) {
yield call(fetchUser, login)
}
}
This getUser reducer (is it even a reducer) looks very different from what I would normally expect a reducer to look like.
Can anyone explain what a selector is and how getUser is a reducer and how it fits in with redux-saga?
getUser is not a reducer, it is indeed a selector, that is, a function that knows how to extract a specific piece of data from the store.
Selectors provide an additional layer such that if you altered your store structure and all of a sudden your users were no longer at state.entities.users but instead at state.users.objects.entities (or whatever) then you only need to update the getUser selector and not every place in your app where you were making a reference to the old location.
That makes them particularly handy when it comes to refactoring your Redux store.
Selectors are getters for the redux state. Like getters, selectors encapsulate the structure of the state, and are reusable. Selectors can also compute derived properties.
You can write selectors, such as the ones you saw in redux-saga. For example:
const getUsersNumber = ({ users }) => users.length;
const getUsersIds = ({ users }) => users.map(({ id }) => id);
etc...
You can also use reselect, which is a simple “selector” library for Redux, that memoize selectors to make them more efficient.
Selectors are functions that take Redux state as an argument and return some data to pass to the component.
const getUserData = state => state.user.data;
Why should it be used?
One of the main reasons is to avoid duplicated data in Redux.
Your data object shape keeps varying as your application grows, so rather than making changes in all the related component.It is much recommended/easier to change the data at one place.
Selectors should be near reducers because they operate on the same state. It is easier for data to keep in sync.
Using reselect helps to memoize data meaning when the same input is passed to the function, returns the previous result rather than recalculating again.So, this enhances your application performance.
function mapStateToProps (state) {
return {
user: state.user,
}
}
initialState of reducer by user store
const initialState = {
isAdmin:false,
isAuth:false,
access:[1,2,5]
};
class AppComp extends React.Component{
render(){
const {user: { access:access}} = this.props;
const rand = Math.floor(Math.random()*4000)
return (<div>
{`APP ${rand} `}
<input type="button" defaultValue="change auth" onClick={this.onChangeUserAuth} />
<p>TOTAL STATUS COUNT IS {access.length}</p>
</div>)
}
}}
but you can use selector
var getUser = function(state) {
return state.user
}
const getAuthProp = createSelector(
getUser,
(user) => user.access
);
function mapStateToProps (state) {
return {
// user: state.user,
access: getAuthProp(state)
}
}
Main Problem is this component use all user: state.user and any changes in user (etc isAdmin ,isAuth, access) runs rerender this component which need only part of this store - access!!!
In Redux, whenever an action is called anywhere in the application,
all mounted & connected components call their mapStateToProps
function. This is why Reselect is awesome. It will just return the
memoized result if nothing has changed.
In the real world, you will most likely need the same certain part of
your state object in multiple components.
https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c
The createSelector function provided by Reselect implements the most basic way to derive a selector from previous selectors. The simplest use case is to derive a selector from a single other selector. In this case, the parameters to createSelector are the input selector and a function transforming the result of that selector into the result of the new selector. For example
var getProducts = function(state) {
return state.products
}
import {getProducts} from '../app/selectors'
import {createSelector} from 'reselect'
export const getProductTitles = createSelector(
getProducts,
(products) => products.map((product) => product.get('title'))
)
This is equivalent to (ignoring memoization):
import {getProducts} from '../app/selectors'
export const getProductTitles = (state) => {
return getProducts(state).map((product) => product.get('title'))
}
The createSelector function can combine data from multiple selectors as well as from a single selector. We can pass any number of selectors to createSelector, and their results will be passed to the function passed as the final argument. For a (somewhat contrived) example:
const isInCheckout = createSelector(
getIsShippingPage,
getIsBillingPage,
getIsConfirmationPage,
(isShipping, isBilling, isConfirmation) =>
isShipping || isBilling || isConfirmation
)
is equivalent to
const isInCheckout = (state) => {
return (
getIsShippingPage(state) ||
getIsBilingPage(state) ||
getIsConfirmationPage(state)
)
}
common pattern when writing mapStateToProps functions with selectors is to return an object with each key storing the result of a particular selector. The createStructuredSelector helper function in Reselect lets us write this pattern with the minimum of boilerplate. For example, if we writ
const mapStateToProps = createStructuredSelector({
title: getProductTitle,
price: getProductPrice,
image: getProductImage
})
it is equivalent to
const mapStateToProps = (state) => {
return {
title: getProductTitle(state),
price: getProductPrice(state),
image: getProductImage(state)
}
}
https://docs.mobify.com/progressive-web/0.15.0/guides/reselect/