Using Redux with models - reactjs

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.

Related

How to efficiently select a redux store slice

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.

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.

React-Redux narrowing state for child component

Is there a way to pass some part of state (not the whole state) to the child component of some React component? So that mapStateToProps and getState method in redux middleware would use such a substate?
This is important for developing react-redux logic that is unaware of state structure it is used in. It could be then used in several app places or different applications. Such logic would behave as an independent module and satisfy the principle of encapsulation.
Such concept exist in Knockout.js ("with" operator) and in desktop WPF.NET (passing DataContext to child element).
Thank you!
One possible approach is to use selectors and/or namespaces.
With selector, you can encapsulate the logic of querying the state object.
However, you cannot totally hide redux state's shape from your react-redux logic, since it's subscribed to the whole store, by design. Still, we can use a namespace here.
Example:
// counter.js
const namespace = 'counter'
const actualReducer = (state, action) => action.type === 'INC' ? state + 1 : state;
export default { [namespace]: actualReducer }
export const selector = (state, ownProps) => state[namespace]
// configureStore.js
import reducer from './counter'
const store = createStore(reducer)
export default store
// Component.js
import { selector } from './counter'
const Component = props => <some markup />
export default connect(selector)(Component)
So what do we have? In brief:
Component.js is not aware of state shape and even the namespace;
configureStore.js is not aware of the counter namespace;
counter.js is the only place, that is aware of its namespace in the store.
One can also extract selector to separate file since it's the only place where we have to deal with original state object.
Note that namespace should be unique, or, to be re-usable from app to app, it can be imported or injected.
IMO, the idea of re-using react-redux stuff (i.e. connect(...)) is not very solid. Since usually, the state shape is not going to be the same from app to app
I'd encourage you to keep it explicit, so the underlying component is the real one who should have an API that is as unopinionated as possible.

Better way to structure react app?

I started my app without any state management dependencies and my app's state looked something like this:
state = {
...initState,
}
///some actions I need to perform
handleChange = (e) => {
this.setState(setDefaultValues(e));
this.setState(setBmr);
this.setState(setTdee);
this.setState(setTrainingAndRestDaysCal);
this.setState(setTrainingMacros);
this.setState(setRestMacros);
}
here I import my initState from separate file (to save some space). Then I have handleChange where I'm passing functions to multiple this.setState because my next state data depends on previous. I'm importing those functions from separate file (to save some space as well)
Then I came to the point where I realized I'm passing props all over the place so I introduced the new react's context API. Which works very well in my opinion. Kind of like a redux just without a big boilerplate. So this context API helped me with that prop drilling through the child components. To use the context API i had to do some changes to my state object, so it looks like this now:
state = {
...initState,
handleChange: (e) => {
this.setState(setDefaultValues(e));
this.setState(setBmr);
this.setState(setTdee);
this.setState(setTrainingAndRestDaysCal);
this.setState(setTrainingMacros);
this.setState(setRestMacros);
},
setTrainingTrueOrFalse: (isIt) => {
this.setState({ trainingToday: !isIt })
},
saveNewMeal: (meal) => {
const meals = this.state.meals
this.setState({
meals: { ...meals, meal }
})
}
Here I added my handleChange to my state so I can pass it via context api. Then I have created some new functions on the state an realized my state now is getting too messy.
I have a function on the state (handleChange ) that uses other functions imported from setStateFunctions.js
I have functions where logic is not extracted to setStateFunctions.js
On a high level my app has 2 main components: Calculator & MealPlanner
Calculator - calculates the macros and calories and passes the result
to MealPlanner
MealPlanner - uses data from calculator and creates meals
==================================================
What's the better approach to structure my app's state and functions?
Do I really need to introduce redux?
What would be my reducers structure?
How would this look like using just react's new context API?
Your app is sized right to go without adding redux for state management.
1) What's the better approach to structure my app's state and functions?
Let a top-level app maintain state that includes "handler" functions.
2) Do I really need to introduce redux?
Not needed. Keep your props organized and let the App handle the "events".
3) What would be my reducers structure?
Avoid it and go vanilla react.
4) How would this look like using just react's new context API?
Context feels overkill and possibly entangling (two components could drift on the understanding of how to use what is exposed as shared, global state in the context).
Let your composing App manage the state and pass props to the child components (Calculator and MealPlanner). You want two-way communication between those, so also pass "handling" functions that change the state within App to get the effect to ripple to the other via passed-in props. Something like the following:
class App extends React.Component {
state = {
...initState, // not sure if this is global or what, but assuming defined
handleCalculation: () => {
// do some state changes on this ala this.setState()
},
handlePlanning: () => {
},
};
render() {
return (
<div>
<MealPlanner {...this.state} />
<Calculator {...this.state} />
</div>
);
}
}
Let MealPlanner and Calculator set required propTypes accordingly.

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.

Resources