React Redux Combine Reducers - reactjs

In Redux, if I am using say a data structure as follows:
{
products: [
{id:1, name:'tv', description:'A color Sony TV', cost:1000},
{id:2, name:'remote control', description:'A black compact Remote Control', cost: 999}
],
shoppingCart: [],
checkoutCart: []
}
And I use the Redux combineReducers
combineReducers({products, shoppingCart, checkoutCart})
If I wanted to copy the shopping Cart data into the checkoutCart say on a click event, where would I do so / or should I do so ?

When you use combineReducers you're assuming the reducers are indepedent and they cannot see each other's state. Each reducer will only receive a "slice" of the state.
In order to achieve what you want, I would consider joining the shoppingCart and checkoutCart reducers / state into a single one and using switch (action.type) to know what to do.
Now you can simply create an action on your new reducer that returns something like
{ checkoutCart: clone(state.shoppingCart) }

If you want to clone your shoppingCart to the checkoutCart. You could in your onClick function pass this.props.shoppingCart as a parameter to your action call.
onClick(e) {
this.props.checkOutActions.checkOut(this.props.shoppingCart);
}

Related

Should every React component have it's own slice, when using createEntityAdapter?

I'm using Redux-tookit's createSlice and createEntityAdapter with normalized data.
This is a typical blog app with (posts, comments, users) - Entities
Usually, before using createEntityAdapter I would:
Fetch, normalize and store data in postsSlice
So my postSlice state would look something like this:
blogPosts: {entities: {posts: {}, users:{}, comments: {}}, ids:[]}
Get id's from postsSlice's state to Posts component
Pass comment/user id's from Posts down to children - Comment User components, where they would get data using passed id's with selectors connected to parent's postSlice state
const postsAdapter = createEntityAdapter();
const postsSlice = createSlice({
name: "posts",
initialState: postsAdapter.getInitialState(),
reducers: {
setPosts: (state, { payload }) =>
postsAdapter.setAll(state, payload.entities.posts),
},
});
The problem is:
When using createEntityAdapter
Since we're using createEntityAdapter.getInitialState() we get the same initialState {entities: {} ids: []} pattern in every slice.
And this doesn't allow to have initialState like I had before:
blogPosts: {entities: {posts: {}, users:{}, comments: {}}, ids:[]}
Should every component (Posts, User, Comment) have it's own slice/reducer and fetch it's own piece of data with thunk from the same endpoint?
So that: (according the createEntityAdapter.getInitialState() pattern)
postSlice state would just contain post Entity - entities: {posts: {}, ids:[]}
commentSlice state - comments Entity - entities: {comments: {}, ids:[]}
etc...
No. There has never been a 1:1 association between components and Redux state structure. Instead, you should organize your state in terms of your data types and update logic, and components should access and re-shape that data for their own needs as necessary.
Note that there are multiple ways to approach structuring that data in the store even if it's being normalized. For example, you could have each data type as its own top-level slice, or have a shared entities reducer with each of those types nested inside.

Clarification on Stores in Reactjs-Redux

I am currently learning how to use Redux in my ReactJS application and all the tutorials I've seen lead me to believe that there can only be one store in Redux. In a real-world applications, you may have multiple streams of data coming from a REST api or some other source. So, for example, I may be looking at a list of customers in one component and a list of projects in another and list of users in yet another. My question is: Does Redux allow you to create a store for each entity with it's own reducer and actions? For example, a customer store, a project store and user store? If not how would one organize this in a single store without everything getting convoluted? Thanks.
* EDIT *
So my state would look something like:
let initalState={
customers:{
data:[]
...
},
projects:{
data:[]
...
}
users:{
data:[]
...
}
}
I think that combinereducers is what you are looking for.
As your app grows more complex, you'll want to split your reducing function into separate functions, each managing independent parts of the state.
The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function you can pass to createStore.
Imagine you want to manage the customers, projects and users.
You will get one reducer per feature :
const customersInitialState = {data:[], ...}
export default function customersReducer(state = customersInitialState , action) {
switch (action.type) {
case 'ADD':
return {...state, data: [...state.data, action.payload]}
default:
return state
}
}
Then you will combine all theses reducers into a single one
export default combineReducers({
customers: customersReducer,
projects: projectsReducer,
users: usersReducer
})
const store = createStore(reducer)
Finally the state of your store will be like this :
{
customers: {data: [], ...},
projects: {data: [], ...},
users: {data: [], ...},
}

Redux state / subcomponent render triggers in react?

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

Redux, when to split reducers?

I have a Redux store with a reducer consisting of 9 case statements. Six of these cases deal with populating an array in the state white three deal with sorting said array, thus there is no natural way of splitting the reducer, is it ok to leave the reducer as one?
The number of case statements isn't the right metric for deciding whether you should split your reducer. Rather, it's how deeply nested is the state slice that your reducer action handler is working on. If its too deep, the code could explode.
Lets say you have this state shape:
{
cart: {
products: [ {name:"shirt"}, {name:"pants"} ],
checkout: false,
}
}
In a single/global reducer setup, you might have this action handler:
//reducer.js
case ADD_CART_PRODUCT:
const {newProduct} = payload
const {cart: {products}} = state
return {...state, cart: {...state.cart, products: [...products, newProduct] } }
Not bad, but you can split off a cartReducer for action handlers that affects the cart state slice, shrinking your code to this:
//cartReducer.js
case ADD_CART_PRODUCT:
const {newProduct} = payload
const {products} = state //this is actually state.cart from the global perspective
return {...state, products: [...products, newProduct] } }
Yes, it is okay to leave the reducer as one. Lets say if I have a reducer for User, I would consider splitting the reducer if it got too big to manage or if it had nothing to do with User and that was my first one. You only have 9 switch cases and that should be fine.

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