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.
Related
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.
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.
I saw someone write this code for a reducer:
export default function reducer(state, action) {
switch(action.type) {
case 'ADD_TODO':
Object.assign({}, state, {
todos: [{
text: action.text,
completed: false,
}, ...state.todos]
})
default:
return state;
}
}
Why not just do this?
case 'ADD_TODO':
return [{text: action.txt, completed: false}, ...state.todos]
What's the difference?
In Redux, there's a concept of Splitting Reducers which is used for better organization when it comes to deducing the new state. When you split up a reducer into multiple different reducers, then combine them with combineReducers, you essentially give each reducer a slice of state corresponding to the respective state it manages. For example, I could create a todo reducer to only every manage todos and only be aware of the todos inside state, and nothing else.
In the first snippet you've posted, the reducer is a 'master' reducer of sorts. The reducer function in that example is the reducer that handles everything in state, not just todos, and is aware of everything in state. That's why you need this code:
Object.assign({}, state, {
todos: [{
text: action.text,
completed: false,
}, ...state.todos]
})
This creates a copy of state to prevent mutation (via Object.assign), then updates the todos property of the cloned state with a new todo. In this 'master' reducer, the reducer is managing all state and thus must clone state and update the todo property.
In the second example, it would not work in this same reducer. That's because you're returning just an array as the new state. That would mean, whenever you added a todo, state would just become an array. Not good! Your second snippet would only work if you split the master reducer into more than one, and you had a separate reducer managing only todos. That means, that todo reducer would only get its respective 'slice of state'. It would only be aware of the todos property inside state, and nothing else in state:
export default function todos(state, action) { //This reducer is only aware of todos in state, nothing else.
switch(action.type) {
case 'ADD_TODO':
return [{text: action.txt, completed: false}, ...state.todos] //This will modify only the `todos` property of state, not the whole state
}
}
And in your master reducer file:
import todos from './your/todo/reducer/file';
combineReducers({
todos
});
Here, todos's state argument will only ever receive the todos property in state, it's slice. Nothing else. The first snippet would have received the whole entire state object as the state argument.
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 want to filter an array on search SEARCH_TEXT is an on change action
what I'm confused with is how i return the state when the delete key is pressed and the text now becomes empty, i figure i could use initial state in the else statement but my inclination is this is wrong? when i return just state it has all ready been manipulated in the if statement.
simple example.
thanks in advance.
const initialState = ['hello', 'wahhh', 'yo'];
export default function searchSimple(state = initialState, action) {
switch (action.type) {
case SEARCH_TEXT:
if(action.text.length > 0){
return state.filter(item =>
item.startsWith(action.text)
)
}
else {
return state
}
Remember always that the state is your "source of truth". Be wary of eliminating state on the basis of a temporary filter. Once you do so those items are gone. (The only way to get them back is to reset your state to the initialState, which may not be ideal.)
A better approach is to keep your items list as is, and simply store the search text.
const initialState = {
searchText: '',
items: [ 'hello', 'wahhh', 'yo' ]
};
export default function searchSimple(state = initialState, action) {
switch (action.type) {
case SEARCH_TEXT:
return Object.assign({}, state, {
searchText: action.text
});
}
}
Whilst your state won't contain the filtered list, it tells you everything you need to know to construct the filtered list.
Assuming you're using React, your "smart component" can be setup with the following mapStateToProps() function:
function mapStateToProps(state) {
const { items, searchText } = state.searchSimple;
return {
filteredItems: items.filter((item) => item.startsWith(searchText))
};
}
Should you need this filtered list in more than one place, consider creating a "selector" function, as demonstrated in the Redux shopping cart example.
https://github.com/reactjs/redux/blob/master/examples/shopping-cart/src/reducers/cart.js
It would look something like this:
export function filteredItems(state) {
const { items, searchText } = state.searchSimple;
return items.filter((item) => item.startsWith(searchText));
}
For a more advanced approach to selectors, check out the reselect library.
https://github.com/rackt/reselect
IMO, the right place to filter data is not directly in the reducers but in the selectors.
From redux docs:
Computing Derived Data
Reselect is a simple library for creating memoized, composable selector functions. Reselect selectors can be used to efficiently compute derived data from the Redux store.
I'm currently using selectors to filter and sort data.
No data repetition in the state. You don't have to store a copy of the filtered items.
The same data can be used in different components, each one using a different filter for example.
You can combine selector applying many data computations using selector that you already have in the application.
If you do right, your selectors will be pure functions, then you can easily test them.
Use the same selector in many components.