Redux reducer - a single state object or multiple states? - reactjs

Consider these two patterns for reducer's state:
A single state object:
// personReducer.js
const initialState = {
personInfo: {
firstName: "",
lastName: "",
foo: "",
bar: "",
baz: "",
foo1: "",
bar1: "",
bar2: "",
foo2: "",
foo3: "",
}
// some not-to-mention states
};
Multiple states (without the person level):
// personReducer.js
const initialState = {
firstName: "", // no person level
lastName: "",
foo: "",
bar: "",
baz: "",
foo1: "",
bar1: "",
bar2: "",
foo2: "",
foo3: ""
};
Which one is better? What confuses me is:
// Person.jsx - pattern 1
const mapStateToProps = ({ personInfo }) => {
personInfo
}
// Person.jsx - pattern 2
const mapStateToProps = ({ firstName, lastName }) => {
firstName,
lastName
}
// Human.jsx - pattern 1
const mapStateToProps = ({ personInfo }) => {
personInfo
}
// Human.jsx - pattern 2
const mapStateToProps = ({ foo, bar }) => {
foo,
bar
}
Let's say we have 2 components Person and Human in our app, both of them will connect to personReducer to retrieve personal information.
For pattern 1:
In Person component, I dispatch an action to update firstName inside personInfo, which later will force Human to re-render as well, does it? Something like this in our reducer:
case UPDATE_PERSON_INFO: {
return {
...state,
personInfo: {
...state.personInfo,
firstName: payload.firstName
}
}
}
For pattern 2:
In Person component, I dispatch an action to update firstName, which later will not force Human to re-render, because Human is not mapping firstName to its props, but foo, bar. Am I right? Something
like:
case UPDATE_PERSON_INFO: {
return {
...state,
firstName: payload.firstName
}
}

It will re-render in both cases because in both patterns you update the reference to the new immutable state object.
If you want to prevent unnecessary renderings of components you have to use memoized selectors in mapStateToProps. Here is the documentation link and GitHub
These, selectors should be specific for your components.

Related

Redux/Toolkits, useSelector Doesn't Work, Why?

I want to save my data in localstorage to evade the loss of it when reloading the page but i also need it in my gloable state to show a preview of it once it's added and never be lost when reloading the page,This is my slice format:
import { createSlice } from "#reduxjs/toolkit";
export const resumeSlicer = createSlice({
name: "resume",
initialState: {
Education: [
{
key: NaN,
Title: "",
Date: "",
Establishment: "",
Place: "",
},
],
},
reducers: {
SaveEducation: (state, action) => {
let Education = JSON.parse(localStorage.getItem("Education"));
if (!Education) {
Education.push(action.payload);
localStorage.setItem("Education", JSON.stringify(Education));
state.Education = Education;
} else {
Education.push(action.payload);
let i = 0;
Education.map((e) => {
e.key = i;
i++;
return e.key;
});
localStorage.setItem("Education", JSON.stringify(Education));
state.Education = Education;
}
},
getEducation: (state, action) => {
const items = JSON.parse(localStorage.getItem("Education"));
const empty_array = [
{
key: NaN,
Title: "",
Date: "",
Establishment: "",
Place: "",
},
];
state.Education.splice(0, state.Education.length);
state.Education = items;
},
},
});
And this is how i fetched:
const EdList = useSelector((state) => state.Education);
When i console.log it the result is "undefined"
Image Preview
https://i.stack.imgur.com/hD8bx.png
I'm hazarding a guess that the issue is a missing reference into the state. The chunk of state will typically nest under the name you give the slice, "resume" in this case. This occurs when you combine the slice reducers when creating the state object for the store.
Try:
const EdList = useSelector((state) => state.resume.Education);
If it turns out this isn't the case then we'll need to see how you create/configure the store and how you combine your reducers.

React: setState - which is more correct?

I have a react class component that has a state object and I want to update the object with setState. I can get the state to update correctly two different ways and would like to know if one is more correct than the other.
this.state = {
people: {
name: "",
typeofComponent: "class",
}
};
onChange = e => {
// option 1
this.setState((prevState) => ({
people: {
...prevState.people, name: e.target.value
}
}));
// option 2
this.setState({ people: { name: e.target.value }});
}
Your second option would work if the state has only 1 field (name in your case). If you set that way it will overwrite the whole people. That's why we need to use spread operator inorder to make sure the other states are not lost
let a = {
people: {
name: "",
typeofComponent: "class",
}
}
let newName = 'newname'
let withSpread= {people:{...a.people,name:newName}}
let withoutSpread = {people:{name:newName}}
console.log(withSpread)
console.log(withoutSpread)

setState to nested object delete value

I have the following state:
const [state, setState] = React.useState({
title: "",
exchangeTypes: [],
errors: {
title: "",
exchangeTypes: "",
}
})
I am using a form validation in order to populate the state.errors object if a condition is not respected.
function formValidation(e){
const { name, value } = e.target;
let errors = state.errors;
switch (true) {
case (name==='title' && value.length < 4):
setState(prevState => ({
errors: { // object that we want to update
...prevState.errors, // keep all other key-value pairs
[name]: 'Title cannot be empty' // update the value of specific key
}
}))
break;
default:
break;
}
}
When I do so, the object DOES update BUT it deletes the value that I have not updated.
Before I call formValidation
My console.log(state) is:
{
"title": "",
"exchangeTypes: [],
"errors": {
title: "",
exchangeTypes: "",
}
}
After I call formValidation
My console.log(state) is:
{
"errors": {
title: "Title cannot be empty",
exchangeTypes: ""
}
}
SO my other state values have disappeared. There is only the errors object.
I followed this guide: How to update nested state properties in React
What I want:
{
"title": "",
"exchangeTypes: [],
"errors": {
title: "Title cannot be empty",
exchangeTypes: "",
}
}
What I get:
{
"errors": {
title: "Title cannot be empty",
exchangeTypes: "",
}
}
unlike the setState in class component, setState via useState doesn't automatically merge when you use an object as the value. You have to do it manually
setState((prevState) => ({
...prevState, // <- here
errors: {
// object that we want to update
...prevState.errors, // keep all other key-value pairs
[name]: 'Title cannot be empty', // update the value of specific key
},
}));
Though you can certainly use the useState hook the way you're doing, the more common convention is to track the individual parts of your component's state separately. This is because useState replaces state rather than merging it, as you've discovered.
From the React docs:
You don’t have to use many state variables. State variables can hold objects and arrays just fine, so you can still group related data together. However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
So in practice, your code might look like the following:
const MyComponent = () => {
const [title, setTitle] = useState('');
const [exchangeTypes, setExchangeTypes] = useState([]);
const [errors, setErrors] = useState({
title: "",
exchangeTypes: "",
});
function formValidation(e) {
const { name, value } = e.target;
switch (true) {
case (name === 'title' && value.length < 4):
setErrors({
...errors,
[name]: 'Title cannot be empty'
});
break;
default:
break;
}
}
return (
...
);
};

action structure to get state from redux using reducers

I'm using redux actions to set some contacts list. I want to get those contacts using a redux action. but all i get is the contact set in the action. Can you tell me how to get the actual state contact.
action.ts
export const setCurrentContact = (contact: IContactObject) => ({
type: 'SET_CURRENT_CONTACT',
contact,
})
export const getCurrentContact = () => ({
type: 'GET_CURRENT_CONTACT',
contact: { ID: "", Name: "", Email: "", Mobile: "", Landline: "", Website: "", Address: "" },//when using dispatch i get this contact i.e blank details
})
reducer.ts
const initialContact=[{ ID: "507", Name: "xander", Email: "xander.cage#gmail.com", Mobile: "9999999999", Landline: "4026241799", Website: "www.xandercage.com", Address: "california" }]
export const currentContact = (state = initialContact, action) => {
switch (action.type) {
case 'SET_CURRENT_CONTACT':
{
return [
{
ID: action.contact.ID,
Name: action.contact.Name,
Email: action.contact.Email,
Mobile: action.contact.Mobile,
Landline: action.contact.Landline,
Website: action.contact.Website,
Address: action.contact.Address,
}
]
}
case 'GET_CURRENT_CONTACT':
{
console.log(state[0]);
return state
}
default:
return state
}
}
somefile.ts
interface IDispatchFromProps
{
setCurrentContact: any,
getCurrentContact: any,
}
function mapDispatchToProps(dispatch): IDispatchFromProps
{
return{
setCurrentContact: (contact:IContactObject)=>{dispatch(setCurrentContact(contact))},
getCurrentContact: ()=>{dispatch(getCurrentContact())},//this should give me the initial data
}
}
expected output:
getCurrentContact() gives intial data set in reducer.ts
actual output:
data set in contact key of action.ts
The problem is that when you dispatch 'SET_CURRENT_CONTACT' action type, you crush the existing state.
instead of :
return [
{
ID: action.contact.ID,
...rest of your payload
}
]
do this :
return [
...state,
{
ID: action.contact.ID,
...rest of your payload
}
]
Hope it helps !

Redux state is being updated without dispatching any action

I should start off by saying this is not a duplicate of this question, which happened to have the same title.
I'm simply getting a customers object of arrays from props inside a componentDidMount method like this;
componentDidMount() {
const { customers } = this.props
const expiringCustomers = getExpiringCustomers(customers)
console.log('Expiring customers ', expiringCustomers)
}
Inside another file, I have that getExpiringCustomers function which takes the customers passed and is suppose to return a newly modified list of customers like this;
function numbersOnly(value) {
if(_.isString(value)) {
value = Number(value.replace(/[^\d]/g, ''))
}
return value
}
function normalizeNumber(collection, field) {
return collection.map(obj => {
obj[field] = numbersOnly(obj[field])
return obj
})
}
export function getExpiringCustomers(customers) {
const expiringCustomers = customers.filter(customer => {
const daysLeft = Number(new Date(customer.endDate)) - _.now()
if(daysLeft <= (dateInMonth * 3)) {
return customer
}
})
return normalizeNumber(expiringCustomers, 'rent')
}
I'm connecting my react component with redux state like this;
const mapStateToProps = state => ({
customers: state.customers.filter(customer => customer && !customer.deleted)
})
export default connect(mapStateToProps)(Accounting)
Problem
After the functions run and log results, customers' state is changed in redux store.
This is very confusing as customers_edit action has to pass through some procedures but none of them are called/logged.
Snapshot of the affected object:
Ps. The data is just boilerplate.
//- Focus on rent property
const customers = [
...,
{
id: 'o91wukyfsq36qidkld02a0voo93rna5w',
cardId: 'GD-1101010111',
id_type: 'Driving License',
firstName: 'Maalim',
lastName: 'Guruguja',
names: 'Maalim Guruguja',
property: '5iaprurefg3v3uhad688mypo9kqf6xk3',
rent: '250,000',
email: 'tonimarikapi#yahoo.com',
phone: '239-288-3838-38',
noticePeriod: '3',
status: '2 months remain',
startDate: '2018-07-09',
endDate: '2018-08-17',
createdAt: 1530623480772,
updatedAt: 1531213159147
},
...
]
//- After the functions run, log and edit customers array
const customers = [
...,
{
id: 'o91wukyfsq36qidkld02a0voo93rna5w',
cardId: 'GD-1101010111',
id_type: 'Driving License',
firstName: 'Maalim',
lastName: 'Guruguja',
names: 'Maalim Guruguja',
property: '5iaprurefg3v3uhad688mypo9kqf6xk3',
rent: 250000,
email: 'tonimarikapi#yahoo.com',
phone: '239-288-3838-38',
noticePeriod: '3',
status: '2 months remain',
startDate: '2018-07-09',
endDate: '2018-08-17',
createdAt: 1530623480772,
updatedAt: 1531213159147
},
...
]
From the linked question (possible duplicate one) the guy who answered stated that it's some mutation issue that may cause this. I'm not sure if that applies on props that are suppose to be read-only.
How can I stop these functions from updating my redux store, please help.
You mutate the objects in normalizeNumber, since all the array methods you use don't clone the array's objects.
Change normalizeNumber callback to return a new object with the updated field:
function normalizeNumber(collection, field) {
return collection.map(obj => ({
...obj,
[field]: numbersOnly(obj[field])
}))
}
It looks like you're modifying the customers array unintentionally.
Try:
componentDidMount() {
const { customers } = { ...this.props };
const expiringCustomers = getExpiringCustomers(customers)
console.log('Expiring customers ', expiringCustomers)
}

Resources