Redux state / subcomponent render triggers in react? - reactjs

I have a react component called contacts and subcomponents called 'comments' and 'card'. All three are connected to the contactReducer
If I wrote a reducer for contacts as follows:
const reducer = ( state = null, action = null ) => {
switch (action.type) {
case GET_CONTACT:
return [
...state,
contact: action.payload
]
case GET_CARD:
return [
...state,
card: action.payload
]
case GET_COMMENTS:
return [
...state,
comments: [...action.payload]
]
default:
return state;
}
}
export default reducer;
1.a)
Does the parent component render each time the subcomponent receives updated props because they are all connected to the contactReducer?
1.b)
If I'm going about this the wrong way how would you recommend it be handled?
2)
after each action has update the contactReducer once do I end up with the following object or am I way off?
contactsReducer = {
contact : {someData},
card : {someData},
comments: [someData, someData, ...]
}

Any object that receives a new object as a prop or if their state changes will re-render in accordance with react's diffing algorithm. That is assuming you don't overwrite the componentShouldUpdate method.
1a - the parent will receive the new state from the reducer and re-render anything that should change.
1b / 2 - In your current implementation, you will have a single array with a mix of objects with keys varying from contact, card, and comments. You should look into redux's functionality combineReducers and create a reducer for each of those keys. I would strongly recommend you watch a video series on Redux to see the design patterns, it will go a long way - video

Related

How to re-use components that change state with React & Redux

I am coming across a reusability conundrum in my React/Redux app. I have coded reusable React components, such as dropdowns and sliders, and I am storing the state values in my Redux Store. It is dawning on me now that anytime any JSX instance of the component changes state, all other instances of that component also change state. They all begin to mimic each other, whereas I need them to operate independently. I also want to initialize state differently in the different instances of the component, and can't figure out a way to do this. For example, for a slider, I want the beginning and ending value of the slider range to be different in different instances of the component. This would be easy to do through React's normal props system, but I am getting confused on how to do it via Redux.
I've researched this a bit and it seems the solution is to add unique IDs to each instance of the component in the Redux store. But I need specifics on where to add the ID (the reducer? the action creator? the component?) and how to have everything flow so that the right component is getting their state changed.
This is my reducer:
const INITIAL_STATE = {
slots: 100,
step: 5,
start: 35,
end: 55,
};
const sliderReducer = (state=INITIAL_STATE, action ) => {
switch (action.type) {
case "MIN_SELECTED":
console.log("Min Selected");
return {
...state,
start: action.payload
};
case "MAX_SELECTED":
console.log("Max Selected");
return {
...state,
end: action.payload
};
default:
return state;
}
}
export default sliderReducer;
this is my action creator:
//Slider Minimum Value Selection Action Creator
export const selectMinValue = (value) => {
return (dispatch) => {
dispatch({
type: "MIN_SELECTED",
payload: value
});
};
};
//Slider Maximum Value Selection Action Creator
export const selectMaxValue = (value) => {
return (dispatch) => {
dispatch({
type: "MAX_SELECTED",
payload: value
});
};
};
the component code is too long to paste, but it's event handlers for onDrag events and JSX for the slider essentially.
You could give each instance a unique id. This would allow you to pass the id to the redux store where you could hold an object or array that would store each instance's data based off on the unique key. You could hold another redux variable that handles unique id generation.
For example you could have a state value for each reusable component called "id" initialized to "null". In the redux store, you could have another "id_counter" that keeps track of the next usable id to assign to a reusable component. During component initialization, you can make a call to redux to retrieve the most recent "id_counter" and then increment "id_counter" by 1. If you start "id_counter" at 0, you can have id's that can access a zero-based array. So, then you can have another item in the redux store, "data", that is an array that grows with each new id that is added. This "data" variable can hold data for each of your reusable components.

React DOM not updated when prop from redux store changes

This is driving me crazy for hours now... I have a module that displays a list that is fetched from a server and loaded into the redux store on button press. That works properly. I mention this as this is the reason why I don't understand the following behavior.
This object array from the store is mapped into my component with
const mapStateToProps = (state) => {
return {
extracted_templates: state.extracted_templates
}
}
And used in the render() as follows... I removed some other DOM parts to keep it simple
render(){
return(
<div className="main-container">
{Object.values(this.props.extracted_templates).length > 0 ?
<ExtractedTemplatesList templates={Object.entries(this.props.extracted_templates)} clickHandler={this.clickHandler} /> : '' }
</div>
);
}
The clickHandler modifies the store using the same action as the fetch function uses.
clickHandler(action, id, parent){
console.log(action+" "+parent)
switch(action){
case 'dismiss':
let new_template_list = this.props.extracted_templates
delete new_template_list[id]
// console.log(new_template_list)
this.props.dispatch(setExtractedTemplates(new_template_list))
break;
default:
break;
}
}
Everything is called correctly, the store updates correctly (as I can see in my web-dev console) but this time the DOM doesn't get updated.
For completeness, here's the action and the reducer implementation
action:
export const setExtractedTemplates = (templates) => ({
type: actions.SET_EXTRACTED_TEMPLATES,
payload: templates
});
reducer:
case actions.SET_EXTRACTED_TEMPLATES:
console.log({action})
return {
...state,
extracted_templates: action.payload
}
You're mutating the existing data, and you're putting the exact same object back into the store:
let new_template_list = this.props.extracted_templates
delete new_template_list[id]
this.props.dispatch(setExtractedTemplates(new_template_list))
Both of those are bugs. You should never mutate data from the store, and the result of an action should be new data in the store.
This is one of the reasons why we recommend putting as much logic as possible into reducers. Also, you should be using our official Redux Toolkit package, which would both catch this accidental mutation here, and simplify the update logic in a reducer.
Try this:
clickHandler(action, id, parent){
console.log(action+" "+parent)
switch(action){
case 'dismiss':
let new_template_list = {...this.props.extracted_templates} //make a new copy
delete new_template_list[id]
// console.log(new_template_list)
this.props.dispatch(setExtractedTemplates(new_template_list))
break;
default:
break;
}
}
You modified the same object saved in the redux store. This is potentially dangerous because you changed the state without using a reducer. When React did the shallow comparison, it didn't see difference so UI was not updated. You can make a copy before save it to store.
Further more you can modify your reducer in this way:
case actions.SET_EXTRACTED_TEMPLATES:
console.log({action})
return {
...state,
extracted_templates: [...action.payload] //make a new copy
}

Redux - get initial state from the backend - which is the proper way to do it?

I would like to get some variables from the backend (ie showTutorial) for the initial state in a reducer.
I was thinking about using just a call in axios from the reducer, but I am unsure if that is the best way of doing it.
This is how it is done right now:
import { UNSET_TUTORIAL, SET_FILTER_BLOCKS } from "../actions/types";
const initialState = {
showTutorial: true, // <-- instead of initializying to true, do the axios call
filterBlocks: "ALL"
};
export default (state = initialState, action) => {
switch (action.type) {
case UNSET_TUTORIAL:
return { ...state, showTutorial: false };
case SET_FILTER_BLOCKS:
return { ...state, filterBlocks: action.payload };
default:
return state;
}
};
I do not want to use redux-persist as I would like to understand first the proper way of doing it.
There is no difference between how you do this and how you update state from a backend for any other component.
You should have an action triggered on a top level app component (or any component created at application launch) that loads data from the backend and dispatches the action with the results from the backend. Your reducer then updates state based on this action.
The triggering of the action on your top level app component can be done using a lifecycle event when the component mounts, typically getDerivedStateFromProps()
The best way to do it is to call the relevant action from App.js, and render 'loading' until the data is properly fetched. Obviously, in case of error you will not continue to render components but display an error message.
Reducers should never call actions, this is breaking the redux flow.

Reusable component using React Redux mapStateToProps

A React component OilBarrel connected my redux store to create a container OilBarrelContainer:
// ---- component
class OilBarrel extends Component {
render() {
let data = this.props.data;
...
}
}
// ---- container
function mapStateToProps(state) {
let data = state.oilbarrel.data;
...
}
const OilBarrelContainer = connect(mapStateToProps)(OilBarrel)
// ---- reducer
const oilbarrel = (state = {}, action) => {
let data = state.data;
}
const storeFactory = (server = false, initialState = {}) => {
return applyMiddleware(...middleware(server))(createStore)(
combineReducers({oilbarrel, otherReducer1, otherReducer2}),
initialState
)
}
I find it strange that mapStateToProps() receives the top level state object (the entire state of the application), requiring me to traverse state.oilbarrel.data, when the reducer (conveniently) only receives the branch of the state that belongs to this component.
This limits the ability to reuse this container without knowing where it fits into the state hierarchy. Am I doing something wrong that my mapStateToProps() is receiving the full state?
That is the mapStateToProps behavior. You have to think redux state as a single source of truth (by the way, that is what it really is) independently of the components you have in project. There is no way out, you have to know the exactly hierarchy of you especific data in the state to pass it to your container component.
No this is intentional, because you may want to use other parts of the state inside your component. One option is to keep the selector (mapStateToProps) in a separate file from your component, which will help you reuse the selector, if you app is very large and complex you can also checkout libraries such as reselect which helps you make your selectors more efficient.
Dan Abramov offers a solution for this in his advanced redux course under Colocating Selectors with Reducers.
The idea is that for every reducer, there is a selector, and the selector is only aware of it's reducer structure. The selectors for higher level reducers, wrap the lower level reducer, with their part of the state, and so on.
The example was taken from the course's github:
In the todos reducer file:
export const getVisibleTodos = (state, filter) => {
switch (filter) {
case 'all':
return state;
case 'completed':
return state.filter(t => t.completed);
case 'active':
return state.filter(t => !t.completed);
default:
throw new Error(`Unknown filter: ${filter}.`);
}
};
In the main reducer file:
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
Now you can get every part of your state without knowing the structure. However, it adds a lot of boilerplate.

Using class properties in React render()

Consider this example:
class App extends Component {
constructor() {
super();
this.person = new Person("Tim", 23);
this.state = {
name: this.person.name
}
}
changeName() {
this.person.setName("Jane");
this.person.setAge(22);
setState({name: this.person.name});
}
render() {
return (
<div>
<div>Your name is: {this.state.name}</div>
<div>Your age is: {this.person.age}</div>
<div><button onClick={this.changeName.bind(this)}>Change Name</button></div>
</div>
)
}
}
What I'm querying here is when a variable should be added to the state. In this example, although this works age isn't in the state.
I run into this a lot when working with objects, I'm not sure if it's best practise to add any rendered object property to the state, or if I should only worry about adding properties to the state if they're potentially going to be updated. I'm quite sure what I'm doing in this example would be bad, as age is updating, but isn't being reflected in the state.
Any ideas on the "correct" way to do this?
React doesn't dictate how you manage your data. If you're using an object with getters/setters, then it might be simpler to store the entire object in state:
changeName() {
this.person.setName("Jane");
this.person.setAge(22);
this.setState({person: this.person});
}
In this manner, your object continues to be responsible for the data, and whatever internal processing this implies, while the resultant object itself is stored in the component state.
That said, using data objects like Person, while possible, is not idiomatic React. I would recommend using something like Redux, and setting up unidirectional data flow. This means creating a reducer to manage your state, and using action creators to communicate with the Redux store.
You can initialize your object's default values in the reducer. This is returned by default from the Redux store.
Your reducer would listen for an UPDATE_PERSON action, which would carry the payload for the entire updated Person object. This would be stored in state, as below:
reducers/person.js
const UPDATE_PERSON = 'UPDATE_PERSON';
const initialState = {
name: "Tim",
age: 23
}
const personReducer(state = initialState, action) {
switch (action.type) {
case UPDATE_PERSON:
return {
...state,
name: action.payload.name,
age: action.payload.name
}
default:
return state;
}
}
Your action creator is a simple function with a type property and some kind of payload:
(presumably) actions/person.js
export const updatePerson(data) {
return {
type: UPDATE_PERSON,
payload: data
}
}
You then connect the Redux store to your component, and use the action creator to dispatch the action to the store:
import { connect } from 'react-redux';
import * as PersonActionCreators from '../actions/person';
class App extends Component {
changeName() {
this.props.updatePerson({name: "Jane", age: 22});
}
render() {
return (
<div>
<div>Your name is: {this.props.person.name}</div>
<div>Your age is: {this.props.person.age}</div>
<div><button onClick={this.changeName.bind(this)}>Change Name</button></div>
</div>
)
}
}
const mapStateToProps = (state) => ({
person: state.person
});
const mapDispatchToProps = {
updatePerson: PersonActionCreators.updatePerson
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
The above code assumes you have a root reducer in the following format:
import { combineReducers } from 'redux';
import personReducer from './reducers/person';
const appReducer = combineReducers({
person: personReducer
})
const rootReducer = (state, action) => appReducer(state, action);
export default rootReducer;
You will need to create the store and connect your root reducer to it. Details on that can be found here.
The combineReducers function simply helps to construct the root reducer:
The combineReducers helper function turns an object whose values are
different reducing functions into a single reducing function you can
pass to createStore.
This is more boilerplate, but it is the established and most popular way of handling application state in React. It may seem like a lot to wrap your head around at first, but once you become familiar with reducers, action creators, and the connect function, it becomes very straightforward.
Redux uses a uni-directional data-flow, which means data streams downwards from the top-level components to child components. Stateful components are kept to a minimum; but where state is required, the connect function provides it. When a component needs to modify state, it does so through an action creator. The reducers listen to actions and update state accordingly.
See Dan Abramov's free egghead courses on this topic for an excellent introduction to Redux:
Getting started with
Redux
Building React Applications with Idiomatic
Redux
It's simple. The right way is using state for properties you want to display.
In this case, your code should be
setState({
name: this.person.name,
age: this.person.age
});
Why? Well, it's best practice, and using this.state is encouraged in docs. Attaching properties on the component (this), is generally usual for methods.
Also, consider the component methods shouldComponentUpdate(nextProps, nextState), componentWillUpdate(nextProps, nextState), componentDidUpdate(prevProps, prevState).
Obviously, if you need their arguments, you will not be able to retrieve the old/new properties you change, if them're not on state or props.

Resources