I have a below redux state
state:{
prop1:
prop2:
prop3:
}
Each property value comes from a dropdown element on the UI.
What would be the best way to update the state when onChange of any of these inputs. Update the whole object when any of these inputs change or write a separate case statement in reducer to update each prop individually? Any thoughts or suggestions would be helpful.
You can dispatch action with type of action and payload as input field name and value and then change that particular field only. And then you can Reselect that particlar property only which will further memoize value for that field, thus no-render of component when other props change happens.
So my approach would be.
dispatch({
type: 'UPDATE_PROFILE_FIELD',
payload: {
'field_name': 'first_name',
'value': 'John Doe',
}
});
Then on reducer:
switch (action.type) {
case 'UPDATE_PROFILE_FIELD':
const { field_name, value } = action.payload;
// field_name can be first_name, last_name, phone_number
const newState = Object.assign(oldState, { [field_name]: value });
...
...
// Update new state as desired
return newState;
}
Then using Reselect, we can select only that field from particular state.
const profileSelector = (state) => state.profile
const getFirstName = (profileSelector, profile) => profile.first_name
Now, we can use getFirstName() in our components.
Also, I reocmmend using immutable state (see Immerjs), changing original object will lead to mutation issues that can lead to other issues like unnecessary re-render, un-synced react states etc.
In my own opinion,
From the performance view, I think it is more efficient to update the whole Object.
From the bum's law (LOL), it is also better to update the whole Object, you need less code In your reducer.
Note: Unless you need a little more complex logic, I think it is Ok update the whole object.
If you're using any of these Object keys as a prop or in the state logic in other components you need to take in mind that this could trigger some re-renders.
Related
I'm basically wondering if there is a difference in performance, or any other pros and cons, between the following 2 code snippets. Are there any objective reasons to use one over the other, or is it just personal preference?
const prop1 = useSelector((state) => state.appModel.prop1);
const prop2 = useSelector((state) => state.appModel.prop2);
const prop3 = useSelector((state) => state.appModel.prop3);
const prop4 = useSelector((state) => state.appModel.prop4);
const {
prop1,
prop2,
prop3,
prop4,
} = useSelector((state) => ({
prop1: state.appModel.prop1,
prop2: state.appModel.prop2,
prop3: state.appModel.prop3,
prop4: state.appModel.prop4,
}));
The second option instinctively feels like it might be more performant, because it only uses useSelector once, but then I wonder if one property changing may cause more re-renders because they're all grouped together.
UPDATE:
Also wondering if this even shorter version has any pros and cons?
const { prop1, prop2, prop3, prop4 } = useSelector((state) => state.appModel);
Apologies if this is a duplicate, I tried searching but wasn't entirely sure of the terminology to search for, and couldn't see any matching examples.
The individual one is much better performance wise. Your second approach creates a new object every time the selector is called, so when useSelector compares the before and after it always looks like it has changed. As a result, your component is forced to render on every change to the redux store, even if nothing happened to prop1-prop4.
If you want to do #2, you need to make a memoized selector, so that it returns the same object if none of the individual values have changed. You can read more about memoized selectors here: https://redux.js.org/usage/deriving-data-selectors#optimizing-selectors-with-memoization
(While it's good to learn about memoized selectors, for your case i would recommend sticking with approach #1)
Also wondering if this even shorter version has any pros and cons?
const { prop1, prop2, prop3, prop4 } = useSelector((state) => state.appModel);
That's better than #2, because now it will only need to rerender when appModel changes. If the only things in appModel are prop1-prop4, then this will be basically the same as #1. If there are extra properties in appModel that you don't care about, then this may result in a few extra renders when appModel changes due to those other properties.
If prioritizing performance efficiency from top to bottom, I think it would be like this:
const prop1 = useSelector((state) => state.appModel.prop1);
...
const {
prop1,
...
} = useSelector((state) => ({
prop1: state.appModel.prop1,
...
})
const { prop1,... } = useSelector((state) => state.appModel);
Always try to return primitive values, such as string, number, boolean, etc. when it is possible from callback of useSelector (or use memoizing techniques, see below). Because when you return an object value (object, array, array-like object, etc.) from callback function of useSelector, it causes a new re-rendering of that component, no matter whether any state was changed in your redux tree related to those states that you returned inside of every time new object or not. So, in cases 2 and 3 you are returning a value of object type (assuming prop1, ... is not object type value), therefore your component's props changes always and it re-renders it self by nature of React, even if rendering happens in the parent component of that component.
So, you need somehow memoize your object values to achieve better app performance, which helps to avoid unnecessary re-renderings. So, to do it you can pass shallowEqual function as a second paratmeter to useSelector from react-redux library. Of course, you can write your custom function to compare as well.
import { shallowEqual, useSelector } from 'react-redux'
const selectedData = useSelector(selectorReturningObject, shallowEqual)
Regarding the your use case, you can read more right below (including other memoize options to reference to the same object value, when component re-calls useSelector):
You may call useSelector() multiple times within a single function
component. Each call to useSelector() creates an individual
subscription to the Redux store. Because of the React update batching
behavior used in React Redux v7, a dispatched action that causes
multiple useSelector()s in the same component to return new values
should only result in a single re-render.
With useSelector(), returning a new object every time will always
force a re-render by default. If you want to retrieve multiple values
from the store, you can:
Call useSelector() multiple times, with each call returning a single
field value
Use Reselect or a similar library to create a memoized
selector that returns multiple values in one object, but only returns
a new object when one of the values has changed.
Use the shallowEqual
function from React-Redux as the equalityFn argument to useSelector()
Equality Comparisons and Updates in React-Redux
I've seen a pattern of using props in of CompositionAPI very often,
that is use toRefs to make all entries of props ref.
I'm kind of confused by it.
For exmaple, from the Vue 3 official guide:
export default {
props: {
user: {
type: String,
required: true
}
},
setup(props) {
const { user } = toRefs(props)
//... use user's value
}
}
I have 2 questions in 2 scenearios:
when the props.user is already reactive
Its value will change if it's changed in any ancestors, so why we need to use toRefs? Doesn't it already reactive?
if it's not reactive, it's just a primitive string value
Does making it reactive means we're going to change its value?
I think making a object reactive also imply that its value is welcomed to be changed.
But all the guides and linters warn us that we'd better not to change the props value.(for not writing to the computed value or something)
If I can change the props value directly in the component, I no longer need to emit the changes to parent component everytime.
It's very convenient but I don't know whenther it is a good idea to change the props value after we're sure it becomes reactive?
Since props aren't supposed to be mutated, this is useful for a specific case that is explained in the documentation; a ref that is computed from a prop needs to be passed as an argument:
const { user } = toRefs(props)
// or
// const user = computed(() => props.user)
someFunction(user);
Where a function makes use of composition API or just needs an argument to be passed by reference rather than by value due to the way it works, e.g.:
function someFunction(val) {
setTimeout(() => {
console.log('Up-to-date value:', unref(val));
}, 1000);
}
My parent component <Room/> build children components <RoomSensor/>, when parent build these children I also send to the <RoomSensor/> uuid, by this uuid I fetch sensor data from a backend.
Store is array of objects.
// Parent <Room/>
return props.sensors.map((uuid, index) => {
return <RoomSensor key={index} uuid={uuid}/>
})
// Children <RoomSensor/>
const RoomSensor = props => {
useEffect(() => {
console.log("useEffect")
props.fetchSensor(props.uuid)
}, [props.uuid])
console.log(props.sensor)
return (
<div className={"col-auto"}>
<small><b>{props.sensor.value}</b></small>
</div>
)
}
let mapStateToProps = (state, props) => {
return {
sensor: filterSensor(state, props.uuid)
}
}
let mapDispatchToProps = {
fetchSensor,
}
export default connect(mapStateToProps, mapDispatchToProps)(RoomSensor)
// Selectors
export const getSensor = (state, uuid) => {
return _.filter(state.sensors, ["uuid", uuid])[0]
}
export const filterSensor = createSelector(
getSensor,
(sensor) => sensor
)
And I cannot understand two things:
When I do refresh I get.
TypeError: Cannot read property 'uuid' of undefined
I understand that there is no data in the state yet, that's why such an error occurs. Is it possible not to render the component until the data comes from the server?
If I comment <small><b>{props.sensor.value}</b></small> no errors occur, data appears in the store, then I uncomment this line and voila everything works. But in the console I see too many component rerende. What am I doing wrong? Is there something wrong with the selector?
In general, I want each sensor component to render independently of the others.
The following is based on a few assumptions derived from the shared code and output:
Currently, there's a hard-coded list of 4 sensor UUIDs.
createSelector is from the reselect package.
_ references an import of the lodash package.
"Is it possible not to render the component until the data comes from the server?"
The short answer to this is yes. There're several approaches to achieve this, so you'll want to evaluate what fits best with the structure of the app. Here're 2 approaches for consideration:
Retrieve the list of sensors from the server. Initialize the store with an empty list and populate it upon getting data back from the server.
In getSensor, return a default value if the uuid isn't in the list.
Either way, I'd recommend adding default state to the store. This will help reduce the amount of code required to handle edge cases.
Here's a rough example of what the new reducer and selector for (1) might look like:
export const storeReducer = (state, action) => {
let nextState = state;
if (!state) {
// State is uninitialized, so give it a default value
nextState = {
sensors: [],
};
}
switch (action.type) {
case 'RECEIVE_SENSORS':
// We received sensor data, so update the value in the store
nextState = {
...nextState,
sensors: action.sensors,
};
break;
default:
break;
}
return nextState;
};
export const getSensors(state) {
return state.sensors;
}
The action upon receiving the data from the server, could look something like:
dispatch({
sensors,
type: 'RECEIVE_SENSORS',
})
"...in the console I see too many component rerende[rs]"
Without additional context, it's hard to say exactly why the re-renders are happening, but the most likely cause is that each call to props.fetchSensor(props.uuid) changes the data in the store (e.g. if it's overwriting the data).
From the console output you shared, we see that there're 16 re-renders, which would happen because:
Each of the 4 instances of RoomSensor calls fetchSensor
This results in 4 updates to the store's state
Each update to the store's state causes React to evaluate each instance of RoomSensor for re-render
Hence, 4 state updates x 4 components evaluated = 16 re-renders
React is pretty efficient and if your component returns the same value as the previous run, it knows not to update the DOM. So, the performance impact probably isn't actually that significant.
That said, if the above theory is correct and you want to reduce the number of re-renders, one way to do it would be to check whether the data you get back from the server is the same as what's already in the store and, if so, skip updating the store.
For example, fetchSensor might be updated with something like:
const existingData = getSensor(getState(), uuid);
const newData = fetch(...);
// Only update the store if there's new data or there's a change
if (!existingData || !_.isEqual(newData, existingData)) {
dispatch(...);
}
This would require updating getSensor to return a falsey value (e.g. null) if the uuid isn't found in the list of sensors.
One additional tip
In Room, RoomSensor is rendered with its key based on the item's index in the array. Since uuid should be unique, you can use that as the key instead (i.e. <RoomSensor key={uuid} uuid={uuid} />). This would allow React to base updates to RoomSensor on just the uuid instead of also considering the order of the list.
Redux is successfully storing and updating state. The reducers are seemingly working correctly. I'm able to use this.props.dispatch. However, when it actually comes to detailing that information (i.e. this.props.array I always seem to get undefined.
Reducer:
export default function array(state = {}, action) {
switch (action.type) {
case "UPDATE_ARRAY":
state.array = action.array
return state;
default:
return state;
}
}
State-aware component:
constructor(props) {
super(props);
this.state = {
array: array
}
}
--
self.props.dispatch({
type: 'UPDATE_ARRAY',
array: array
})
--
const mapStateToProps = (state) => {
return {
messages: state.messages,
array: state.array
};
};
export default connect(mapStateToProps)(Component);
This only seems to be able to save state btw when I define an empty array. This doesn't seem right, I thought the intention of Redux was a self-contained store? Updating a variable seems to defeat the purpose a bit.
Would appreciate any help.
export default function array(state = {}, action) {
switch (action.type) {
case "UPDATE_ARRAY":state={
...state,
array:action.array
}
return state;
default:
return state;
}
}
you should always update your state immutably,instead of mutating the current application state ,you should create another object and return that.State should be immutable ,only way to change the state is to create a new one.This helps to improve the performance of the application.
I am not sure if you application has more than one reducer or not, if it has, than you must be using combine reducer method .So to access state.array in mapsStateToProps is like this
const mapStateToProps = (state) => {
return {
messages: state.{reducer_name}.message,
array: state.{reducer_name}.array
};
};
in place of 'reducer_name' you have to specify the reducers_name which you have define in combine reducer
And last mapStateToProps return array ,in props not in component state.
which you can access in this way {this.props.array},you cant set component state in componentDidMount and in componentWillRecieveProps (in case of aysnc action).
Your component will receive array as a field in its props field. Your code assumes it's in the state field. So instead of:
this.state = {
array: array
}
you would just access this.props.array wherever in your code you need to use the array. You don't need to put it in the local state at all. Usually, you would use it in the render function, like in this example:
render()
{
return <div>The array contains {this.props.array.length} items.</div>
}
I wonder if you're confusing local state with the Redux store's state? Local state is what you get/set when you access this.state in your component code. Every component can have its own state object that it can read from and write to.
The Redux store's state is what's passed in to mapStateToProps. It's usually the entire state object of all the combined reducers in your top-level reducer (though if you only have one reducer function and are not using combineReducers, then the store state is identical to that single reducer's state).
I suggest choosing more descriptive variable names, so that your code will be more readable. It's hard to understand what your intentions are for your code with such generic names. For example, you could name your reducer something that indicates what it's for, like bookListReducer, and name the array you want to store and retrieve for what will go inside it, like books. Naming both your reducer and all your variables array makes it harder to read your code. This will help anyone who reads your code in the future - including, most importantly, you!, as well as future Stack Overflow readers of your future questions (and perhaps this one if you edit it).
I am not sure the following is your issues, but hope these will help:
export default function array(state = {}, {type, array}) {
switch (type) {
case "UPDATE_ARRAY":
return {...state, array};
default:
return state;
}
}
Your reducer should be pure, which you had is mutating the state.
constructor(props) {
super(props);
this.state = {
array: array // what is array?
}
}
Above constructor is not right. You should be able to access the array from this.props.array as your mapStateToProps
Do a console.log(this.props) in your render function or ComponentWillReceiveProps, see if you can something :)
I'm learning react-redux form the todo example in the docs and don't see why the id of the nextTodo is held in the actions rather than the reducer. Shouldn't this be considered state because it changes overtime as more todos are added? To me, the purpose of an action is to grab some input from the user and transform it to an action, not generate state. It is the reducer's job to create state and change it according to the actions given to it.
Action Code
let nextTodoId = 0
export const addTodo = (text) => {
return {
type: 'ADD_TODO',
id: nextTodoId++,
text
}
}
Reducer code
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false
}
...
}
That's because the reducer is expected to be a pure function. That is, if you run it multiple times with the same parameters, it would return the same result, and the state of the rest of the application would not change.
For that reason, the reducer can't determine the ID, as if it did, it would cause repeated runs to have different results (i.e. different return values).
The reducer's job isn't to create state. It's job is to get the existing state and a delta (i.e. an action) and return a new state. And it should do so reliably.