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.
Related
I have this reducer.
const userInitialState = {
users: [],
};
const users = (state = userInitialState, action) => {
if (action.type === "FETCH_USERS") {
return {
...state,
users: action.payload,
};
}
return state;
};
export default combineReducers({
users,
});
initially the users property is edmpty array,when the new results from the api call comes
for example response like
https://jsonplaceholder.typicode.com/users
is this the correct way for immutable way in redux store for my array inside ?
A proper immutable update is best described as "a nested shallow clone". You don't want to copy every value in a nested data structure - just the ones that need to be updated.
But yes, that looks correct.
A couple additional observations:
You should read through the post The Complete Guide to Immutability in React and Redux and the Redux docs page on Immutable Update Patterns to better understand how to do immutable updates correctly
But, you should really be using our official Redux Toolkit package, which uses Immer to let you write "mutating" update logic that is turned into safe and correct immutable updates.
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: [], ...},
}
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.
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);
}
Imagine i had three main parts in my react application whose state is managed entirely by redux (no local state). Login, Register and View Profile:
My understanding is that the initial state would look like so:
const initialState = {
login: {},
register: {},
profile: {},
appUI: {
menuToggled: false
}
};
export function mainReducer(state = initialState, action) {
...
Then in my container component i would pull the relevant part of state using react-redux connect():
function select(state) {
return {
state: state.login
};
}
export default connect(select)(Login);
So when user types in username,password ..etc in the presentational component of login, the global state would be updated (using actions) and would end up looking like so:
{
login: {
username: "foo",
password: "bar"
},
register: {},
profile: {},
app: {
menuToggled: false
}
};
Is this a valid approach? By valid i mean the way in which i am organising the state of my application, so if there were more "section" to the application as its growing (think crud) i would have greater number fields in the redux. I am new to react and redux and want to avoid any anti-patterns.
This is definitely not an anti-pattern. The entire purpose of mapStateToProps - the first parameter in connect - is to extract the part of the application state, that the component needs.
The one thing that concerns me though is that your container component depends on something like login credentials. Also, I do not know how you have structured your reducer, but I would recommend using the combineReducers function from redux.