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.
Related
I am diving into Redux toolkit and made a simple project to understand its concepts better.
function App() {
//..
return(
<UserList /> // uses submittedUserSelector
<UserInformation /> // uses selectedUserSelector
);
}
The basic idea is that you can either add a user to UserList or select one from the list which will be shown in UserInformation. Both the submitted and selected user are managed by different reducers in the same store.
export const rootReducer = combineReducers({
form: formReducer, // have submitted user selector
user: userReducer // have selected user selector
});
// formReducer.tsx
export const submittedUserSelector = (state:RootState)=>state.form.submittedUser; //object
// selectedUserReducer.tsx
export const selectedUserSelector = (state:RootState)=>state.user.selectedUser; //object
According to the official documentation and I quote:
When an action is dispatched, useSelector() will do a reference
comparison of the previous selector result value and the current
result value. If they are different, the component will be forced to
re-render. If they are the same, the component will not re-render.
So I was expecting that when I selected a user which dispatches an action in userReducer
would result in re-rendering UserList as well (as submittedUserSelector returns an object). That didn't happen, however.
Why? Does redux-toolkit figure out which components in the tree are using a particular selector and only evaluate those? Is that somewhere in the documentation?
Selectors will be called on every state change and only cause a component rerender if the selector result changes from the last render - so usually, unrelated components will not be rerendered.
Keep in mind that general React component rendering still applies - if a parent component rerenders, all child components will be rerendered.
This has nothing to do with redux-toolkit though - it is the normal behaviour of the react-redux hooks. See https://react-redux.js.org/api/hooks
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.
I am new to react/redux. I am using container pattern with redux store (which store immutablejs objects like Map/Set...).
As I know, React component has a method called shouldComponentUpdate for props shadow comparison. Immutable will help much on this calculation. So My question is should I do sequence algorithm in redux container like below?
export default connect(
(state) => ({
data: state.getIn(['data', 'map'])
.filter((obj, key) => state.getIn(['user', 'selectedDataSet']).has(key))
.toSet(),
}),
)(MyComponent);
or is it better to put this logic in componentWillReceiveProps()?
You should keep this functionality in your mapStateToPropsFunction (the first parameter to the connect for those who don't know). I like to keep props passed to my components as limited as possible. i.e I only send in props that will be needed by the component or it's children. This way your component doesn't have to know how to handle extra props or have any special logic.
Another benefit to keeping the logic in mapStateToProps is that your component could extend PureComponent (class MyComponent extends React.PureComponent) to get a free shouldComponentUpdate with shallow prop checking!
That being said, I am sure there are other reasons you might want to keep this in the child component.
Edit: On ImmutableJS
React pairs very well with Immutable JS. An === comparison is still all that is needed to determine if a deeply nested object has changed (the rule that any changes create an entirely new object make this possible).
You should pass everything that the react component needs in order to render. If the component depends a list for example, pass the whole list into the component and then filter it out before rendering.
In your example:
render() {
const data = dataProp
.filter((obj, key) => state.getIn(['user', 'selectedDataSet']).has(key))
.toSet()
return (
<div>{data}</div>
);
}
This assumes your connect looks something like this (note that this may not be 100% correct syntax).
export default connect(
(state) => ({
dataProp: state.getIn(['data', 'map'])
}),
)(MyComponent);
In my React/Redux app I am often facing with the problem of implementing components with state which should be used throughout the app.
Let's take simple popup component as an example with open/close state which can be reused in any page.
Here is two possible approaches I found:
Use setState and "local" reducer (I use recompose.withReducer which is just syntax sugar for React's native setState) to manage its state. It looks easy and reusable until you need change the component's state in the other part of your page (close popup in out case). And you cannot just call some redux action to change the state.
Keep the component's state in the Redux store. With such approach we can call closePopupAction({ id }) in any place of the components tree to change it's state.` But we need somehow put its reducer (which i want to keep in the popup's folder) to the root reducer when the component is mounted and delete it when the component is unmounted. Plus we can have multiples popups in the page and each of them have its own state.
Did anybody face with a similar problem ?
I think you should keep state of component in redux. You can create reducer for this component and use combineReducers function in this way:
rootReducer = combineReducers({
moduleA: combineReducers({
popupA: popupReducer("id1"),
popupB: popupReducer("id2")
}),
moduleB: combineReducers({
popupA: popupReducer("id3")
})
})
});
then when you call closePopupAction("id1") reducer can check id and change proper part of state.
PS: Id should be provided in better way :)
You could mount each component's state to its own slice of the store.
So your closePopupAction actions would be called with that mount path:
closePopupAction({ mountPath: 'popups.popup1' })
and you would need only one reducer to handle all closePopupAction actions, which could be registered once at startup:
(state, { type, mountPath }) => {
if (type === 'CLOSE_POPUP_ACTION') {
// manipulate with the slice at `mountPath`, e.g.
return _.set(_.cloneDeep(state), `${mountPath}.isOpen`, false)
}
// ...
}
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.