Redux state is being updated without dispatching any action - reactjs

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)
}

Related

React native component rerender when using context api

I'm currently learning React Native. I have an aplication that is used to track expenses and I'm setting some dummy data as initial state. The app works fine, I can add new expenses, edit and delete them. Also, I have a component that is showing recent expenses for the last 7 days only.
Today I noticed that my recent list was empty, because all of the expenses were older than the last 7 days, so I changed the dates of a few items so I would have some data on initial load.
After saving the changes, I noticed that the app refreshed, but the state didn't, I still had an empty list. Only when I forced the reload of the app, the state was updated.
So my question is, is this intended? Should the component rerender after I manually change the initial state or not? I couldn't find any similar issues and also I couldn't find anything in the docs.
Here's my context code:
import { createContext, useReducer } from 'react';
const DUMMY_EXPENSES = [
{
id: 'e1',
description: 'A pair of shoes',
amount: 59.99,
date: new Date('2021-12-19'),
},
{
id: 'e2',
description: 'A pair of trousers',
amount: 89.29,
date: new Date('2022-01-02'),
},
{
id: 'e3',
description: 'Bananas',
amount: 19.99,
date: new Date('2023-02-06'),
},
{
id: 'e4',
description: 'Book',
amount: 69.69,
date: new Date('2023-02-05'),
},
];
export const ExpensesContext = createContext({
expenses: [],
addExpense: ({ description, amount, date }) => {},
deleteExpense: (id) => {},
updateExpense: (id, { description, amount, date }) => {},
});
function expensesReducer(state, action) {
switch (action.type) {
case 'ADD':
const id = new Date().toString() + Math.random().toString();
return [{ ...action.payload, id: id }, ...state];
case 'UPDATE':
const expenseIndex = state.findIndex((e) => e.id === action.payload.id);
const expenses = [...state];
expenses[expenseIndex] = { id: action.payload.id, ...action.payload.data };
return expenses;
case 'DELETE':
const expenseArray = [...state];
return expenseArray.filter((e) => e.id !== action.payload);
default:
return state;
}
}
export default function ExpensesContextProvider({ children }) {
const [expenses, dispatch] = useReducer(expensesReducer, DUMMY_EXPENSES);
function addExpense(expenseData) {
dispatch({ type: 'ADD', payload: expenseData });
}
function deleteExpense(id) {
dispatch({ type: 'DELETE', payload: id });
}
function updateExpense(id, expenseData) {
dispatch({ type: 'UPDATE', payload: { id: id, data: expenseData } });
}
const value = {
expenses: expenses,
addExpense: addExpense,
updateExpense: updateExpense,
deleteExpense: deleteExpense,
};
return <ExpensesContext.Provider value={value}>{children}</ExpensesContext.Provider>;
}

PUT/Update/Delete item in nested Appsync array. React, useReducer, Apollo

I have an object in my schema which has an array like so:
type Foo #aws_cognito_user_pool {
id: ID!
name: String
comments: [Comment]
createdAt: AWSDateTime
}
the input and Comment Type look like so:
input FooInput #aws_cognito_user_pool {
name: String
comments: [CommentInput]
}
type Comment #aws_cognito_user_pool {
id: String
body: String
parent: String
createdAt: String
}
input CommentInput {
id: String
body: String
parent: String
createdAt: String
}
In my app I am using useReducer to manage state and apollo.
My Foo reducer looks like so
const INITIAL_FOO_STATE = () => {
name: "",
comments: [],
}
export const FooReducer = (state, action) => {
switch(action.field) {
case "name":
case "comments":
return {
...state, [action.field]: action.payload,
}
default:
return
}
}
in my component i have the add, update and delete functions like so:
const addComment = (body, parent) => {
CreateComment(
body,
parent,
)
.then((comment) => {
setComments([comment, ...comments]);
setActiveComment(null);
});
};
const updateComment = (body, commentId) => {
UpdateComment(body)
.then(() => {
const updatedComments = comments.map((item) => {
if (item.id === commentId) {
return {
...item,
body: body,
};
}
return item;
})
setComments(updatedComments);
setActiveComments(null);
})
};
const deleteComment = (commentId) => {
if (window.confirm("Are you sure you want to remove this comment?")) {
DeleteComment()
.then(() => {
const updatedComments = comments.filter((comment) => comment.id !== commentId);
setComments(updatedComments)
})
}
};
This works just fine on the frontend, but now want to dispatch these actions to my Foo.comments array on the backend and looking for some help as to the best way without the need of another table.
I was thinking of placing the dispatch's within their respective .then()
For anyone interested, the solution was simple.
I had to modify the comment type in schema to:
type Comment #aws_cognito_user_pool {
id: ID!
sourceId: ID!
body: String
parent: String
createdAt: String
}
input CommentInput {
body: String
parent: String
createdAt: String
}
then add the mutations and resolvers for create, update and delete of the Comment.
In the Foo comment field, a query resolver was needed.
This also meant a new table was needed for comments.
The table needed to have the sourceId as primary key and ID as sort key, with a global index setup on the sourceId
I swapped out the CRUD functions in my component with the appropriate Apollo mutations and done.

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)

Functional component problems React

I transformed a class component into a functional component but it looks like it does not work in a way it suppose to work and I can not find what is wrong. When I create a new object there is no name for the object and when I try to mark the object as a complete it removes all created objects at ones. I created a codesandbox here. Unfortunately, I am not too much familiar with functional component. Any help would be appreciated.
Here is my codesandbox sample:
https://codesandbox.io/s/crazy-sid-09myu?file=/src/App.js
Your Todos:
const [todos, setTodos] = useState([
{ id: uuid(), name: "Task 1", complete: true },
{ id: uuid(), name: "Task 2", complete: false }
]);
onAddHandler:
const addTodo = () =>
setTodos([...todos, { id: uuid(), name: "New Task", complete: false }]);
onSetCompleteHandler:
const setCompleteHandler = id =>
setTodos(
todos.map(todo => {
if (todo.id === id) {
return {
...todo,
complete: todo.complete ? 0 : 1
};
}
return todo;
})
);
I have created your new todos. Check out this link
Todos App
I have updated your code, please check the URL https://codesandbox.io/s/determined-morning-n8lgx?file=/src/App.js
const onComp = id => {
for (let i = 0; i < todos.length; i++) {
if (todos[i].id === id) {
let t = { ...todos[i] };
t.complete = !t.complete;
todos[i] = t;
}
}
setTodos([...todos]); // Here todos reference need to be changed
};
And also
const onSubmit = event => {
event.preventDefault();
setTodos([
...todos,
{
id: generateNewId(),
name: newTodoName,
complete: false
}
]);
setNewTodoName("");
};
While using hooks we need to be careful about state variable updates. While manipulating arrays and objects use the spread operator to create new references which invokes child components and re-render current component.

Hooks and immutable state

I am apologising in advance if this questions has already been answered before (and also for the long post, but I've tried to be as specific as I could be). But, the answers I found does not completely satisfy me.
I want to use the new amazing React Hooks for my project. And for what I've been doing so far, it has been straight forward.
Now I have ran into a more complex example, and I feel unsure on how I best should tackle this one.
Let's say, I have more of a complex object (at least it's not flat) in my state.
{
persons: [
{
id: 1,
name: 'Joe Doe',
age: 35,
country: 'Spain',
interests: [
{ id: 1, en: 'Football', es: 'FĂștbol' },
{ id: 2, en: 'Travelling', es: 'Viajar' }
]
},
{
id: 2,
name: 'Foo Bar',
age: 28,
country: 'Spain',
interests: [
{ id: 3, en: 'Computers', es: 'Computadoras' },
{ id: 4, en: 'Cooking', es: 'Cocinar' }
]
}
],
amount: 2,
foo: 'bar'
}
What is the best way to:
Add an item (an object) to my colleagues array
Add an item to a specific "interests" array?
Manipulate the value of a property within an object in the array?
Change a value outside the persons array, for example foo?
using the useState hook?
The following code examples will try to illustrate each question. They're not tested...
Let us consider I have a container that begins with this.
It also includes the functions that are split up in the rest of the post.
const [colleagues, setColleagues] = useState({});
// using the useEffect as "componentDidMount
useEffect(() => {
// receiving data from ajax request
// and set the state as the example object provided earlier
setColleagues(response.data);
}, []);
1) So for the first question. Is this valid?
Or do I need to to make sure that each and every object in my persons array is destructured?
const onAddNewColleague = () => {
// new colleague, this data would be dynamic
// but for the sake of this example I'll just hard code it
const newColleague = {
name: 'Foo Baz Bar',
age: 30,
country: 'Spain',
interests: [
{ en: 'Cats', es: 'Gatos' }
]
};
// creates a copy of the state
// targets the "persons" prop and adds a new array
// where the new colleague is appended
const newState = {
...colleagues,
persons: colleagues.concat(newColleague)
};
// updates the state
setColleagues(newState);
};
2) This feels wrong as I end up updating the entire persons array instead of just the interest array for a specific person.
const onAddNewInterest = (personId) => {
// a new interest
const newInterest = {
en: 'Languages', es: 'Idiomas'
};
// find the person according to personId
// and update the interests
const updatedPersons = colleagues.persons.map(person => {
if(person.id === personId) {
return {
...person,
interests: person.interests.concat(newInterest);
};
}
return person;
});
// create a copy of the state
const newState = {
...colleagues,
persons: [...updatedPersons]
};
setColleagues(newState);
};
3) As the second example, this one feels wrong too as I am updated the entire persons array when in fact I might just want to change the age of one specific person
const onChangeValue = (personId, key, value) => {
// find the person
const updatedPersons = colleagues.persons.map(person => {
if(person.id === personId) {
// key, could be age?
return {
...person,
[key]: value
};
}
return person;
});
// create a copy of the state
const newState = {
...colleagues,
persons: [...updatedPersons]
};
setColleagues(newState);
};
4) Is this valid, or do I need to destruct every part of my colleagues object separately?
const onChangeOtherValue = (key, value) => {
// for example changing the value foo
const newState = {
...colleagues,
[key]: value
};
setColleagues(newState);
};
I do have a feeling that only the concept of the first function is valid, while the rest of them are not.
Can this be done easily, or should I just use an immutable-helper?
Thanks in advance!
Updated examples to get syntax, right. Thanks Valerii.
To clarify
What I'm really after here is best practise to handle use cases like this one. I want to make sure my state is updated in the most correct and efficient way. So feel free to rip my examples a part or write new ones - I'm all ears. It is not necessary to simply modify mine to fit this post (unless they actually turn out to be good).
1) OK
2)
const updatedPersons = colleagues.persons.map(person => {
if(person.id === personId) {
return {
...person,
interests: person.interests.concat({ en: 'Test', es: 'Test' })
};
}
return person;
});
const newState = {
...colleagues,
persons: updatedPersons
};
3)
const updatedPersons = colleagues.persons.map(person => {
if(person.id === personId) {
return {
...person,
[key]: value
};
}
return person;
});
// create a copy of the state
const newState = {
...colleagues,
persons: updatedPersons
};
4) OK
for the first one, i would do this way,
const newState = {
...colleagues,
persons: [...persons, newColleague]
};

Resources