Redux react root reducer slowing down - reactjs

I‘m building an application using react and redux. One functional feature is to filter results based on the state.
So in each class Component I had something like this:
filteredResults = this.filterResults(this.props.value1, this.props.value2,...)
Now I thought that there should be a performance gain if I just add filteredResults to the redux state and introduce a RootReducer. Actually, that slowed down the application. Any Idea why that happened? For me, it is a bit counterintuitive since filteredResults is calculated right now many times.
PS: This is how my RootReducer looked like:
import {_getFilteredResults} from "../components/utils";
const createFilterRootReducer = reducer => (state, action) => {
let reduced_state = reducer(state, action);
let filteredResults = _getFilteredResults(reduced_state.value1, reduced_state.value2, reduced_state.value3, reduced_state.results);
return {...reduced_state, filteredResults:filteredResults}
};
export default createFilterRootReducer;
That was applied on myRootReducer
const rootReducer = combineReducers({
searching: ReducerSearching,
roomOption: ReducerZimmer,
maxPrice: ReducerMaxPrice,
minPrice: ReducerMinPrice,
internalMaxPrice: ReducerInternalMaxPrice,
Address: ReducerAddress,
Results: ReducerResults,
activePage: ReducerActivePage,
favoriteResults:ReducerLikedItems,
resultSort: ReducerSort,
acceptedCookie: ReducerCookies,
headerHeight: ReducerHeaderHeight,
mapView: ReducerMapView,
rehydrate: ReducerRehydrate
});
export default createFilterRootReducer(rootReducer);
One more thing: I was also using 'redux-persist' to persist the state!

Well, it would be helpful if you could provide some additional infos and the code you are using.
I believe there are at least 3 ways to do what you want to achieve.
1. Use createSelector from reselect
The easiest way in my opinion is having a selector to calculate the filtered results based on the actual state of your whole results and the filtering values.
import { createSelector } from 'reselect';
const getResults = state => state.results; // e.g. array of strings
const getFilter = state => state.filter; // e.g. an input value
export const filteredResults = createSelector( getResults, getFilter, ( results, filter ) => {
return results.filter( item => item.indexOf( filter ) != -1 );
});
Now you can provide filteredResults via connect and mapStateToProps to your components without worrying of filtering your results every time.
Note that createSelector is smart enough to perform an equality check on its parameters in order to recompute the function only when any of its parameter changes.
Pros
You don't need to store the filtered results in the state tree
Cons
Since the filtered results are not stored in the tree, you will need to import this selector everywhere you want to access this data.
You won't be able to access it in a direct way e.g. state.xyz.filteredResults
2. Store the filtered results in the tree when storing anything that would change them.
You can calculate and store the filteredResults in the same tree branch where ( and when ) you store the results and the input value.
This is possible only if your original results are store in the same location.
const initialState = {
results: [],
filteredResults: [],
filter: ''
}
const filterResults = ( results, filter ) => results.filter( item => item.indexOf( filter ) )
const myReducer = handleActions({
[SET_RESULTS]: (state, action) => ({
...state,
results: action.payload,
filteredResults: filterResults( action.payload, state.filter )
}),
[SET_FILTER]: (state, action) => ({
...state,
filter: action.payload,
filteredResults: filterResults( state.results, action.payload )
})
}, initialState)
Pros
You will be able to manually access your filteredResults since they are stored in the tree.
Cons
You will need to make sure every action changing anything realted the the filtered results will update them.
You will be storing extra data in the store ( results + filtered results ).
3. Use Redux-Thunk and store the filtered results
This is a different approach that would work.
Basically you can store the filtered results in a different part of the tree when you input, but in order to do so, you will need Redux Thunk middleware
export const onChangeInputValue = ( value ) => {
return (dispatch, getState) => {
const results = getResults( getState() )
const filteredResults = results.filter( item => item.indexOf( value ) )
dispatch( setInputValue( value ) )
dispatch( setFilteredResults( filteredResults ) )
}
}
Pros
you can store the filtered results in a different part of the tree to where the results or the input value are stored.
Cons
you need an extra action for storing the filtered results.
you are dispatching 2 actions separately that might cause an extra rendereing.
Final consideration
You can choose what is the most suitable and viable way depending on your needs.
I personally like to use the selectors (1) and recompute data since that allows me to separate the logic from the actions and reducers.

Related

How to extract selectors from adapter from queries with arguments

I'm trying to extract selectors from queries in my apiSlice as said in this documentation: https://redux.js.org/tutorials/essentials/part-8-rtk-query-advanced
The documentation put this example:
const usersAdapter = createEntityAdapter()
const initialState = usersAdapter.getInitialState()
export const extendedApiSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
getUsers: builder.query({
query: () => '/users',
transformResponse: responseData => {
return usersAdapter.setAll(initialState, responseData)
}
})
})
})
export const { useGetUsersQuery } = extendedApiSlice
// Calling `someEndpoint.select(someArg)` generates a new selector that will return
// the query result object for a query with those parameters.
// To generate a selector for a specific query argument, call `select(theQueryArg)`.
**// In this case, the users query has no params, so we don't pass anything to select()**
export const selectUsersResult = extendedApiSlice.endpoints.getUsers.select()
const selectUsersData = createSelector(
selectUsersResult,
usersResult => usersResult.data
)
export const { selectAll: selectAllUsers, selectById: selectUserById } =
usersAdapter.getSelectors(state => selectUsersData(state) ?? initialState)
Now, i need to have selectById selector (that by default takes userid as second parameter).
I can't manage to have a working selectById selector when my query looks like this
endpoints: builder => ({
getUsers: builder.query({
query: (applicationId) => `/application/${applicationId}/users`,
transformResponse: responseData => {
return usersAdapter.setAll(initialState, responseData)
}
})
})
How do I extract selectById selector from adapter and how i use it in a component with useSelector when I have this kind of query with arguments?
Thanks anyone that will help me
I feel like you mismatch concepts a bit, so it leads to confusion.
First of all, let's clarify, that's selectors you a using- it's not a selector to your state in usersAdapter, but to RTK-Q's own state.
By handling a response in transformResponse you are just copying the data from it to your's, usersAdapter's state.
So, considering that, you should be specific about which state you are going to select from. If you want it from the "final destination", i.e. from the adaptor's state, it's should be done via:
const usersSelectors = **usersAdapter**.getSelectors(
(state) => state.users // or something like that
)
Otherwise, using the selectors from apiSlices like extendedApiSlice from your example - you are fetching the data from RTK-Q's cached state, which may not contain some old data after the cache invalidation. If it's still your goal, the limitation is that RTK-Q's store isn't a normalized store you may expect, with ids and values, but rather the key-value pairs, where keys are your requests, and the values - last results (users arrays in your case). So, if you have no API endpoint defined for selecting a particular user by ID, you won't be able to select it from RTK-Q's state directly. But you may select the cached users by applicationId, and find your user by id in the result array. I bet it's not what you actually want, so most probably you need just to prepare selectors for your own store, as I've mentioned above.

Redux useSelector with id field

I need your advice on filtering data with a selector. Suppose I have the following entities in my application. 1 organization has multiple devices which look the following in my state shape:
state {
devices: {
byId: [ 1 { name: device1 } ]
}
organizations: {
byId: [
1 { name: org1, devices: [1,2,3] }
]
}
}
Now I want to filter the devices inside the organization. This is something that I want to do with a selector. My selector looks like the following:
const selectDevice = (id: any) => (state: State) => state.devices.byId[id]
export const selectOrganizationDevices = (id: number) => (state: State) => {
const organization = state.organizations.byId[id] || []
return organization.devices.map(id => selectDevice(id)(state))
}
This should be working fine but my selector got called before I have dispatched the data from redux. I suppose that there's something wrong with my reducer or the component I've created.
My reducer looks like this:
return produce(state, draftState => {
switch (action.type) {
...
case OrganizationActionTypes.FETCH_DEVICES_SUCCESS: {
draftState.byId = action.payload.entities.organizations
draftState.allIds = Object.keys(action.payload.entities.organizations)
draftState.loading = false
break;
}
...
default: {
return state
}
}
})
My functional component looks like the following:
function Devices() {
const dispatch = useDispatch();
const devices = useSelector(selectOrganizationDevices(1))
useEffect(() => {
dispatch(fetchOrganizationDevices(1))
}, [])
const columns = []
return (
<Layout headerName={"Devices"}>
<Table dataSource={devices} columns={columns}/>
</Layout>
)
}
The error I get now is organization.devices is undefined which says that the array of devices in the state is empty. It seems that useSelector is called before dispatch. How can I prevent redux of doing this? Or what should be changed in my code?
Yes, the useEffect hook runs after the first render. useSelector will run during the first render. So, your component code needs to safely handle the case where that data doesn't exist yet.
Also, don't put a hardcoded array/object literal in a selector like that, as it will be a new reference every time and force your component to re-render every time an action is dispatched. Either extract that to a constant outside of the selector, or use a memoization library like Reselect to create the selector.
Finally, you should be using our official Redux Toolkit package, which includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once. It also has a new createEntityAdapter API that helps you manage normalized state in the store.

How to share/split a Redux Store between multiple generic components?

I have a generic component called "VendorResults". I am passing a string prop down to each of these generic components such as "Microsoft", "Apple", etc.
<ScrollView>
<SearchResults/>
<VendorResults vendor={"microsoft"}/>
<VendorResults vendor={"oracle"}/>
</ScrollView>
Within this generic component, I am passing the vendor prop as a parameter to my Redux-Thunk actions as such:
componentDidMount() {
const {vendor} = this.props;
this.props.getVendorInformation(vendor);
}
An API call kicks off, and Thunk actions are dispatched. The data eventually makes its way to the Reducer and store. However, When I have more than one generic Vendor component, whichever async call finishes last, appears to take precedent over all the others. For example, if oracle finishes loading last, the microsoft component's state will change and show oracle data.
Actions
export function getVendorInformation(vendor) {
const url = `${VENDOR_URL}api/search/${vendor}`;
return dispatch => {
dispatch(getVendor());
fetch(url)
.then(blob => blob.json())
.then(data => {
dispatch(getVendorSuccess(data))
})
.catch(e => {
console.log(e);
dispatch(getVendorError(e.message))
});
};
Reducer
export default function(state=initialState, action){
switch (action.type){
case FETCHING_VENDOR: return {payload:[], fetching: true}
case FETCH_VENDOR_SUCCESS: return {payload: action.payload.data}
case VENDOR_ERROR: return {payload:[], error: true, ...state}
}
return state;
}
My Question:
I want to maintain this pattern of generic/reusable Vendor components - I do not want a new component for each vendor. The same goes for actions/reducers; unique vendor actions/reducers would get messy.
How can I share/split/partition a single Redux store into vendor specific chunks to maintain seperation of state but still benefit from one flow. Thank you!!
You need to pass vendor to reducer via action and re-do structure of your state. If list of vendors is pre-determined and not very long, it probably will be less messy to just create separate actions/reducers.
Otherwise, you need to have nested reducer:
const supportedActions = [FETCHING_VENDOR, FETCH_VENDOR_SUCCESS, VENDOR_ERROR];
const initialVendorState = {data:[], fetching: false, error: false};
const vendorReducer = (state = initialVendorState, action) => {
switch (action.type){
case FETCHING_VENDOR: return {data:[], fetching: true}
case FETCH_VENDOR_SUCCESS: return {data: action.payload.data}
case VENDOR_ERROR: return {...state, data:[], error: true}
}
return state;
}
const reducer = (state = {}, action) => {
if (supportedActions.includes(action.type)) {
const s = {};
s[action.payload.vendor] = vendorReducer(state[action.payload.vendor], action);
return {
...state,
...s
};
}
return state
}
export default reducer;
And your action creators should take vendor as parameter and pass it to reducer:
const fetchVendorSuccess = (vendor, data) => ({
type: FETCH_VENDOR_SUCCESS,
payload: {
vendor,
data
}
});
In your connect function you will need to use smth like data: (state[vendor] || {}).data to avoid errors if state does not have any info about that vendor
However, When I have more than one generic Vendor component, whichever async call finishes last, appears to take precedent over all the others. For example, if oracle finishes loading last, the microsoft component's state will change and show oracle data.
You are seeing Oracle data because after fetching the vendor data you are overwriting the entire vendor state with the latest array of vendor items.
case FETCH_VENDOR_SUCCESS: return {payload: action.payload.data}
To avoid this, you would need to merge the previous state with the new state.
Solution depends on what each vendor response looks like. As Gennady suggested, you can use an object and make each vendor a property on the object.
Using a flat array to store all the different vendor items presents challenges. How would you determine if a vendor has already been fetched?
To avoid overwriting the previous vendor, you would need to merge the new state with previous state. E.g.
case FETCH_VENDOR_SUCCESS: return [...state.data, ...payload.data]

How to use redux saga in editable table efficiently

I have a multi page react application in which one endpoint has to show data in tabular form. Show I take GET_INFO action on componentWillMount of that endpoint. Now I have a reducer called table_info which has table_data array and shouldTableUpdate boolean in it.
My table is editable with edit and delete icon in every row. I am facing problem in update, on update I call reducer with action UPDATE_TABLE_ROW and if success than I do something like following :
//reducer.js
const initialState = {
table_data:{}, shouldTableUpdate:false;
}
export default function myReducer(state=initialState, action){
switch(action.type){
case UPDATE_SUCCESS:
// how to handle edited row here?
// also when I print my state of this reducer
// state becomes nested, so if one does lots of updates
// it will be become very heavy...
return {...state, shouldTableUpdate:true}
}
}
Can you tell how to handle update, delete, add on table using redux saga efficiently ? On googling I get naive examples only, so came to SO.
Note: Can't show the actual code as it's for my company project. Sorry for that.
Thanks.
Can you tell how to handle update, delete, add on table using redux saga efficiently ?
Well you can plainly manipulate the state object using a reducer only.
Comments:
table_data is a list and not an object.
I don't think you'll be needing shouldTableUpdate since state change in store will trigger a component update if state field is mapped in mapStateToProps.
So here's a basic template of adding, updating and deleting items via reducer.
const initialState = {
table_data: [],
};
export default function myReducer(state=initialState, action){
switch(action.type) {
case ADD_ITEM:
return {
...state,
table_data: [
...state.table_data,
action.item, // item to be added
]
};
case UPDATE_ITEM:
let updatedItem = action.item;
// do something with updatedItem
return {
...state,
table_data: table_data.map(e => (
e.id === updatedItem.id ? updatedItem : e
)),
};
case DELETE_ITEM:
const index = state.table_data.findIndex(e => e.id === action.item.id);
const numItems = state.table_data.length;
return {
...state,
table_data: [
// exclude index
...table_data.slice(0, index),
...table_data.slice(index+1, numItems),
]
};
default:
return state;
}
}

What are selectors in redux?

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/

Resources