How to efficiently select a redux store slice - reactjs

In a React/Hooks/ReduxToolkit application, what are the efficiency implications of selecting a slice of the redux store in the following two methods:
Store Structure:
const store: RootState = {
students,
courses,
}
students is a dictionary of students indexed by their id ... i.e.
students: [id: number]: Student
Similarly, courses is a dictionary of courses indexed by their id
In a component that is only concerned with rendering student information, what are the performance implication of directly selecting for the relevant slice in the component versus creating a memoized selector that the component then invokes?
Case 1 (direct selecting of slice in component):
const StudentReactComponent = () => {
const allStudents = useSelector((state: RootState) => state.students)
...
return some render logic that makes use of `allStudents`
...
}
Case 2 (selecting a slice via a memoized selector):
// This memoized selector would actually be imported from a `studentSelectors`
// module instead of being declared above the react component
const selectStudentsDictionary = createSelector((state: RootState) => state.students, students => students)
const StudentReactComponent = () => {
const allStudents = useSelector(selectStudentsDictionary)
...
return some render logic that makes use of `allStudents`
...

useSelector forces a component to re-render when the selector returns a new reference that is different than the previous reference: https://react-redux.js.org/api/hooks#equality-comparisons-and-updates . So, as written, the component will re-render whenever state.students changes.
The "memoized" selector in this example is actually useless. Memoization only matters when you have derived data. Any time you have a createSelector instance that just has x => x as its "output selector", you're using createSelector the wrong way, and this could likely just be a plain function.
As a related side note, I am actually planning to write a new "Selectors and Memoization" usage guide page for the Redux core docs in the near future. Until then, I'd suggest reading through my post Using Reselect Selectors for Encapsulation and Performance. It's a few years old, but still mostly relevant here.

Related

Call Hooks on component change in React Native

I'm working on an react native app.
This app use a database, the main component use 2 differents hook.
The first hook retrieves the results of a SQL query and store them in a variable.
The second hook creates a list from the first variable
Like this:
const [people, setPeople ] = useState([]);
useEffect (() => {
db.getAllPeople().then(row => setPeople(row))
},[])
const [listData, setListData] = useState([]);
useEffect(()=> {
setListData(
Array(people.length)
.fill('')
.map((_, i) => ({ key: `${i}`, name: `${people[i].name}`}))
)
}, [people]);
After that, my main component displays a SwipeList from the results.
Here is the problem. I am using another component to add an element to my database. When I return to my main component I would like this new element to be displayed. But the problem is that the 2 hooks are not called on the component change and the list therefore remains unchanged.
I've tried to use the useFocusEffect but it doesn't work in my case.
Any suggestions ?
I think the useState hook manages the state of the component itself, unless you are passing this state among your parent and child or using callbacks to set the state on the component that you want to render, you could use a single source of truth to handle the changes in data, react itself will notice this changes and therefore, render the changed screens, considering that you have asynchronous operations when querying the database, a combination of redux and redux saga may help you.
https://github.com/redux-saga/redux-saga
There're one issues with your current code, or potential issues
Your second useEffect might get called when people becomes an empty list, this will reset your list data. The cure is to put a if statement inside, ex.
useEffect(()=> {
if (!people) return;
setListData(...)
}, [people]);
To be honest, if these two lists are connected, you shouldn't use two hook. The best way is to define listData
const listData = (a function that takes people as input), ex.
const listData = people.map(v => v)
Of course, there might be a reason why you'd like to introduce more hook in complex situation, ex. useRef, useMemo.

Is it possible to create a React Hook factory function?

I have a React app that relies heavily on redux, react-redux, and redux-saga. I'm starting to experiment with React Hooks, and the useSelector and useDispatch hooks in react-redux, and I've run into an issue with how to essentially write a factory function to generate hooks for each of my redux nodes.
In general, I have a redux node for each API endpoint my app consumes. There are about 100 unique endpoints in this app, and so there are about 100 redux nodes. Each of those nodes then corresponds to one state-[node].js file, like state-users.js, etc. Those state files each encapsulate the endpoint they should call, trigger sagas to handle the HTTP lifecycle (start, fail, success, etc), and so on.
Over time, I've written code that abstracts much of the boilerplate away into utility functions, including functions that auto generate action creators, selectors, reducers, and the connect function. It's a bunch of code, and somewhat convoluted, but the gist looks something like this. First, I set up an array of objects describing actions that are possible for this redux node. A simplified version looks like this:
const messages = [
{ action: 'GET', creator: 'get', connect: true },
{ action: 'POST', creator: 'post', connect: true },
{ action: 'CLEAR', creator: 'clear', connect: true },
];
This describes that there will be three actions, get , post, and clear, and that they should be exposed in the connector. I have a set of common reducers (e.g. most get reducers are identical across nodes), so those are assumed here based on name.
Then I set up a list of selectors, like this:
const selectorKeys = ['data','pending','errors'];
...and then I have a factory function that I feed these arrays into, which looks something like this:
const { connector } = stateGenerators({
keyword: 'users', //redux node keyword
messages: messages,
selectorKeys: selectorKeys
})
This is a simplified version of how everything really works, but it's the crux of it. Again, all of the above code is abstracted into a state file, like state-users.js.
Then, in my class component, I just need to import the connector from state-users.js, like this:
import { connector } from 'state-users';
class Users extends Component {
componentDidMount() {
this.props.get();
}
componentWillUnmount() {
this.props.clear();
}
render () {
const { data } = this.props;
return (
<div>
{data.map()}
</div>
)
}
}
export connector()(Users)
This model does get clunky at times, but the nice part is that nearly all of the redux boilerplate is abstracted into common files, so my individual state files are, for the most part, really simple.
Now, to the question: is it possible to do something like this "factory function" approach with Hooks? So far in my experimenting I have not figured out how to do this. There are two fundamental issues:
First, you can't put hooks in loops, so I can't do this:
const selectors = {}
const reduxNodeKeyword = 'users';
['data','pending','errors'].map((selectorKey) => {
selectors[selectorKey] = useSelector((state) => state[keyword].selectorKey);
})
That code results in this error:
React hook "useSelector" cannot be called inside of a callback.
In practice, that means I can't just pass in an array of selector keys I'd like and then have it spit back my selectors.
Second, you can't put hooks inside conditionals. So since the first idea failed, I tried a different approach in my factory function, which looks something like this:
if (_.includes(stateSelectors, 'data')) {
result['data'] = useSelector((state) => state[keyword].data);
}
That results in this error:
React hook "useSelector" is called conditionally. React Hooks must be called in the exact same order in every component render
So that's also a bummer. But I think what I'm left with is that for each of my 100 Redux nodes, I would have to write a custom, and verbose, hook to more or less replicate connect.
I know, I know, hooks should encourage me to think differently, but I still need to fetch data from the server and provide it to components, and I need to follow that same basic pattern 100 times. Even if hooks makes my components more elegant (as I expect it would), the thought of writing 100 or so hooks out by hand, each with a fair amount of repeated data, rather than somehow auto-creating them using some sort of factory approach, gives me hives.
Help?
Thanks!
Not sure if this will be useful for others, but I found an approach that works for me. I can't put hooks in iterators, nor in if statements, but I do have common patterns all over the place. So the answer was to abstract those common patterns into generic hooks. For example, I have a hook called useCommonReduxListSelectors, which looks like this:
export const useReduxListCommonSelectors = (keyword: string) => {
return {
data: useSelector((state: any) => state[keyword].data),
errorMessage: useSelector((state: any) => state[keyword].errorMessage),
filters: useSelector((state: any) => state[keyword].filters),
pending: useSelector((state: any) => state[keyword].pending),
totalCount: useSelector((state: any) => state[keyword].totalCount)
};
};
I have lots of Redux state nodes that are responsible for handling lists returned from an API, and the data shape of most of those list endpoints is what you see above. So then the hook that a component would invoke uses useReduxListCommonSelectors, like this:
export const useReduxState = ({ id, defaultValues }) => {
const selectors = useReduxListCommonSelectors({
keyword:'users'
});
return {
...selectors,
};
};
And then obviously useReduxState can have any other data in there (actionCreators, custom selectors, etc) that is required by that node. So it allows me to abstract the most common patterns into reusable hooks, and also have the flexibility to add custom code to each useReduxState instance as needed.

Can I manipulate values within mapStateToProps

I'm learning React and Redux and found following problem that I can't get my head around, I have a config object in store that may looks like this:
{
general:{duration:100,strength:100},
gameA:{strength:50},
gameB:{duration:50}
}
Where general object will always be there and will have all properties, and it could have one or more game objects with all or part of overriding properties.
Now, games GameA and GameB are using duration and strength props so I can do:
const mapStateToProps = (state) => {
return {
duration: state.general.duration,
strength: state.general.strength
}
export default connect(mapStateToProps)(GameA);
but as my store example shows I could have different setup per game type which I want then override general setup. Can I then do this manipulation in the mapStateToProps function?
const mapStateToProps = (state) => {
let {duration, strength} = state.general;
if(state.gameA && state.gameA.duration) duration = state.gameA.duration;
if(state.gameA && state.gameA.strength) strength= state.gameA.strength;
return {
duration: duration,
strength: strength
}
Another pattern is to use reselect to compute derived values from state:
https://redux.js.org/docs/recipes/ComputingDerivedData.html#composing-selectors
Selectors offer the benefit of memoizing derived values which proves extremely useful given the sensitivity of react lifecycle methods (why make this calculation more than once if you don't have to?).
I find they are super useful for abstracting presentation logic from a component.
Here is a short and very simple example:
const mapStateToProps = state => ({
price: getPriceWithDollarSign(state),
})
// selectors/price.js
const getProduct = (state) => state.product // returns { price: 7.00 }
// i find it useful to immediately identify selectors with `get` so I know its a selector
export const getPriceWithDollarSign = createSelector(getProduct, (item) => {
// this will only be evaluated once for this specific item, future calls to this method
// will return the value cached by re-select (until the methods input changes)
return `$${item.price}` // item is value returned by getProduct
})
In your component, you'd end up with this.props.price -> '$7.00'
The beauty of reselect is in its ability to compose multiple selectors, making it easy to share and use other selectors within one another.
Check out https://github.com/reactjs/reselect for more information.
While usage of reselect is geared towards deriving values from redux state, you can use the library with any data structure / library / framework you please.

Component calls Reselect only at the first time it renders

I'm building Multiple Select component for user to select the seasons on the post. So use can choose Spring and Fall. Here I'm using reselect to track selected objects.
My problem is that my reselect doesn't trigger one it renders at the first time. This question looks pretty long but it has many console.log() lines for clarification. So please bear with me! :)
('main.js') Here is my modal Component. Data for this.state.items is seasons = [{id: '100', value: 'All',}, { id: '101', value: 'Spring',}, { ... }]
return (
<SelectorModal
isSelectorVisible={this.state.isSelectorVisible}
multiple={this.state.multiple}
items={this.state.items}
hideSelector={this._hideSelector}
selectAction={this._selectAction}
seasonSelectAction={this._seasonSelectAction}
/>
('main.js') As you can see I pass _seasonSelectAction to handle selecting items. (It adds/removes an object to/from the array of this.state.selectedSeasonIds). selectedSeasonIds: [] is defined in the state. Let's look at the function.
_seasonSelectAction = (id) => {
let newSelectedSeasonIds = [...this.state.selectedSeasonIds, id];
this.setState({selectedSeasonIds : newSelectedSeasonIds});
console.log(this.state.selectedSeasonIds); <- FOR DEBUGGING
console.log(newSelectedSeasonIds); <- For DEBUGGING
}
I confirmed that it prints ids of selected Item. Probably providing code of SelectorModal.js is irrelevant to this question. So let's move on... :)
('main.js') Here is where I called createSelector
function mapStateToProps(state) {
return {
seasons: SelectedSeasonsSelector(state)
}
}
export default connect(mapStateToProps, null)(...);
(selected_seasons.js) Finally, here is the reselect file
import { createSelector } from 'reselect';
// creates select function to pick off the pieces of states
const seasonsSelector = state => state.seasons
const selectedSeasonsSelector = state => state.selectedSeasonIds
const getSeasons = (seasons, selectedSeasonIds) => {
const selectedSeasons = _.filter(
seasons,
season => _.contains(selectedSeasonIds, season.id)
);
console.log('----------------------');
console.log('getSeasons');
console.log(selectedSeasons); <<- You can see this output below
console.log('seaons');
console.log(seasons);
console.log('----------------------');
return selectedSeasons;
}
export default createSelector(
seasonsSelector,
selectedSeasonsSelector,
getSeasons
);
The output for system console is below
----------------------
getSeasons
Array []
seaons
undefined
----------------------
Thank you for reading this whole question and please let me know if you have any question on this problem.
UPDATE As Vlad recommended, I put SelectedSeasonsSelector inside of _renderSeasons but it prints empty result like above every time I select something. I think it can't get state.seasons, state.
_renderSeasons = () => {
console.log(this.state.seasons) // This prints as expected. But this becomes undefined
//when I pass this.state in to the SelectedSeasonsSelector
selectedSeasons = SelectedSeasonsSelector(this.state)
console.log('how work');
console.log(selectedSeasons);
let seasonList = selectedSeasons.map((season) => {
return ' '+season;
})
return seasonList
}
state.seasons and state.selectedSeasonsIds are getting undefined
Looks like you are assuming that this.setState will change redux store, but it won't.
In a _seasonSelectAction method you are calling this.setState that stores selected ids in container's local state.
However selectors are expect ids will be be stored in global redux store.
So you have to pass selected id's to redux store, instead of storing them in a local state. And this parts are looks missing:
dispatch action
use reducer to store this info into redux store.
add mapDispatchToProps handler to your connect
I'm guessing here, but it looks like confusing terms:component local state is not the same as redux store state. First one is local to your component and can be accessed through this.state attributive. Second is global data related to all of your application, stored in redux store and could be accessed by getState redux method.
I so you probably have to decide, whether to stay with redux stack or create pure react component. If pure react is your choice, than you dint have to use selectors, otherwise you have to dispatch action and more likely remove this.state.

Using Redux with models

I've started using Redux with React and I absolutely love it. However, the problem I'm having currently is that besides state, I also have more information I need to store/use throughout my application.
In this specific case I have a model with state that's fetched from an API. This model also has some info about itself, e.g. how you display a property on the screen "name" => "Name of the blabla". I understand how to work with state using Redux, but I'm have trouble seeing what do with this other info that I still need propagated throughout the application but is not actually state.
According to Redux, the State is the only "source of truth". And it should not have duplication (which would lead to inconsistencies).
So your state should store the name, but not the computed label property.
Indeed, "Name of the blabla" is a function (in the mathematical sense) of your Name value, and if they differ (for example, if at some point name === 'foo' but the label is 'Name of the bar' instead of 'Name of the foo'), then you have a problem...
So what I would do, is just store the minimum in your state (name in that case), and compute the label directly in the Component, where you need it.
If you need that to be re-used, then create a Component that only does take your name as a prop, and render a string with "Name of the blablaba" (if name = blabla I suppose).
If you need more complex computation (say you have multiple labels, date calculations etc.), you can always create a function that takes your State in input, and spit out your "Model" in output with everything calculated.
Redux is very functional in nature, so you might as well embrace it :)
I know I'm kind of late to the party but I thought someone might use the answer. What has work for me this far after working with React for some years now is to have a structure that is sort of like this:
State: which sets the structure (or 'schemas') of my data.
Actions: make changes to this state. These actions can handle sync or async operations.
Sagas: they handle async actions.
Selectors: they handle the structure of the data that I need for the view/for the API.
Constants: other data that won't change through time and that makes no sense to add to my state.
So having said that the flow of my apps is something like this:
An ACTION is dispatched => If that action is async a SAGA is listening and it performs the fetch operation => This saga saves updates to the STATE => [React components layer from now on] => If my view needs the data from my state in a different format for whatever reason I send it through a SELECTOR which will change that format => Then I attach this new parsed data to my container component.
An other flow could be one in which you need static data that is not in your state. In that cause I would save it in an object in a separate file and would import it to my container component directly (I never import anything in my children/presentational components directly. Only other components. The data is handled in a separate layer than the components).
A third kind of flow I can think of right now is when you need to make a POST to your API and for whatever reason the data in your state needs some parsing before doing so. In that case I would do the same that in the first example but the other way around: dispatch an ACTION => that is handled by a SAGA => before doing the fetch I would bring my data already structured for my POST (sagas has a method called select to help you use selectors here) => then I would perform the async operation => update the state accordingly.
Just in case you don't know what I mean by selectors or sagas some links here:
Sagas: https://github.com/yelouafi/redux-saga
Selectors: https://github.com/reactjs/reselect
I think models are as necessary for a Redux based app as for any other system.
Models are the vocabulary of a system. Models bring sanity to the codebase. Without them a codebase looks like a series of insane distorted thoughts.
You can use state functions to fill in the need of models in ReactJS+Redux apps.
Just like models hold data and methods, these objects hold only the functions that can be applied to state.
Read here : https://medium.com/#nshnt/state-functions-for-modeling-with-redux-a9b9d452a631.
Here is the famous Redux TODO app example with state functions :
todo_reducer.js :
import TODO from './todo_state';
const todoListReducer = (state=TODO.initialState(), action)=>{
switch(action.type){
case 'ADD_TODO' :
return TODO.addTask(state, action.taskName);
case 'FINISHED_TODO':
return TODO.setFinished(state, action.taskID );
case 'PENDING_TODO':
return TODO.setPending(state, action.taskID );
default :
return state;
}
};
export default todoListReducer;
todo-state.js :
export default {
initialState: () => [],
addTask: (todoList, name)=> todoList.concat({id: todoList.length, name: name}),
setFinished: (todoList, taskId) => (
todoList.map(task=> task.id === taskId ? {...task, complete: true} : task)
),
setPending: (todoList, taskId) => (
todoList.map(task=> task.id === taskId ? {...task, complete: false} : task)
),
pending: todoList=> todoList.filter(task=> !task.complete)
};
I also use these state functions in component, if component need some manipulation of state.
todo_list.js :
import React from 'react';
import {connect} from 'react-redux';
import TODO from './todo_state';
const TodoList = ({tasks, showCompletedTasks, toggleTodo})=> {
const toListElement = (task) => (
<li key={task.id}>
<input type="checkbox" checked={task.complete} onChange={(e)=> toggleTodo(task)}/>
<label>{task.name} {task.complete ? "Complete" : "Pending"}</label>
</li>
);
const visibleTaskList =
(showCompletedTasks ? tasks
: TODO.pending(tasks)).map(toListElement);
return (
<ul className="todo-list">
{visibleTaskList}
</ul>
);
}
.....
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
Use Reselect
Reselect is a simple library that sits in your app. It's primary function is to aggregate data from your redux store.
Create a reselect function
Taken from https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c
import { createSelector } from 'reselect'
// selector
const getBar = (state) => state.foo.bar
// reselect function
export const getBarState = createSelector(
[ getBar ],
(bar) => bar
)
The idea is that you connect your component with redux-connect or map state to props but instead of using the store directly you pass the store to a selector. This selector will have a function that lets you aggregate data or transform it any way you like.
import React from 'react'
import { connect } from 'react-redux'
import { getBarState } from '../selectors'
const mapStateToProps = (state) => {
return {
bar: getBarState(state)
}
}
The advantage of this approach is that you can reuse a selector on any component quite easily. You manipulate your data before it ever reaches a component (Separation of concerns principal). This gives you 2 big advantages.
Firstly, your redux store can remain unpolluted with duplicate or calculated data.
Secondly, your components can be built to expect data structures that immediately make sense to them.
Conclusion
Reselect helps your React apps become more structured.

Resources