useState change values in reducer REACT JS - reactjs

I stored an object in the reducer, I retrieve using useSelector <definitely :) > it and I use as the initial value for useState as bellow
const Product = useSelector(state => state.reducer.selectedMeal);
const [SelectedMeals, setSelectedMeals] = useState({...Product});
and I have this function to change the quantity which is one of the attributes
const Increase = () => {
let copy = {...SelectedMeals};
copy.featuresList.items.qty += 1
setSelectedMeals(copy);
}
ISSUE:
the value in Product.featuresList.items.qty //changed as well

State Mutation
You are mutating state by doing this because the featuresList inside of copy is the same as the one in the SelectedMeals state and the one is the Product in the reducer. You are dealing with a shallow copy.
Minimize Component State
Your Product variable will subscribe to the latest changes in your redux store.
You only need local component state for data that you don't want to push to the redux state, such as form inputs or other UI data.
I would advise that your useState hooks (you can have multiple) should store the minimum amount of data that you need to describe the local changes. You probably don't need to copy the whole Product.
Here's an example where the only local state is a number of the current quantity.
const [qty, setQty] = useState(Product.featuresList.item.qty);
const increase = () => {
setQty( prev => prev + 1 );
}
Nested Updates
Updating a state property that's three-levels deep without mutation is really annoying, but you can do it:
const increase = () => {
setSelectedMeals((product) => ({
...product,
featuresList: {
...product.featuresList,
items: {
...product.featuresList.items,
qty: product.featuresList.items.qty + 1
}
}
}));
};

Related

how can i use setState in state object within functional component?

if i'm using state object within functional component like this:
const state = {
stage: useState(1),
players: useState([]),
result: useState(""),
};
then how would i be able to define the setState function and use it ? thanks in advance
I'd prefer having a single state object, with 3 properties, using a single useState call.
let [game,updateGame] = useState({stage: 1, players: [], result: ''});
I'd hesitate using the state as a variable name, as it may be confusing, when another developer looks at the code for the 1st time.
You can update it as follows:
function goToNextLevel() {
updateGame(game=> {
if(game.stage == 12) {
return {...game, result: 'win'};
} else {
return {...game, stage: game.stage+1};
}
});
}
Those are three completely separate and unique states, and they must be set individually.
useState returns an array with two items. The first item is the states value, and the second item is a function used to set that state.
So with this state object, you would get the state value with:
state.stage[0] // get "stage" value
And set the state with:
state.stage[1](newStage) // set "stage" value
But that's not how react applications are typically structured because trying to remember if you should use [0] or [1] is pretty ugly.
Instead, this is typically laid out like this:
const [stage, setStage] = useState(1),
const [players, setPlayers] = useState([])
const [result, setResult] = useState("")
And now reading state is as easy as stage and setting it as easy as setStage(newStage).
We can update state value in the following way
import hooks
import React, { useState, useCallback } from 'react'
State Value
const [ expand, setExpand ] = useState(false)
Update Method
const handleExpand = useCallback(() => {
setExpand(prevState => !prevState)
})

How to add another key value pair to array of object coming from the api

I am trying to add another property to the array of object coming from the api.
const productList = useSelector((state) => state.productReducer.productList);
if (productList !== undefined) {
for (let b = 0; b < productList.length; b++) {
productList[b].power = 0;
}
}
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
setLoading(true);
await dispatch(fetchProductList());
setLoading(false);
}
fetchData();
});
return unsubscribe;
}, [navigation]);
As you can see I am getting data by using using useDispatch, useSelector hooks.
But What I am doing now is looping the product list and adding power property to each individual product.
This thing is that this process is recalled every rendered.
If I changed the productList[4].power = 4 in jsx.
This loop is called again and the data is gone back to 0.
if (productList !== undefined) {
for (let b = 0; b < productList.length; b++) {
productList[b].power = 0;
}
}
Where should I put this code so that It will not re-rendered.
For my case I trying putting this loop in useEffect but the initial data is empty array so it won't work
It would help if you provided the whole file. It's difficult to see exactly what the JSX is referencing and changing however....
I can see that you're dispatching an action to get some data which is then stored in your redux store and brought into the component with the useSelector hook.
Now first things first... It is possible that data mutations can occur when using useSelector hook, meaning if you're not handling immutability then changes to this state object from a component could be modifying the data in your store.
Regardless of this, your current implementation shows that regardless of what is in the store, after you get the products from the store, you are setting every power to 0 having that loop beneath your selector hook. So whatever you do it will always show as 0.
Perhaps in your reducer when saving the products I would to something like below. Where data is your product list api response...
case 'FETCH_PRODUCT_LIST_SUCCESS':
const newState = _.clone(state);
newState.productList = data.map(product =>
if (product.power === undefined || product.power === null) {
product.power = 0;
}
return product;
);
return newState;
For the useSelector hook, I would highly recommend creating functions that take state as a parameter which returns a copy of your data using a library like 'lodash'. In the example below you can see that I'm using the .clone function on lodash which ensures that the data passed to your component will not carry the same reference. Meaning your components will not be able to modify the data in the store.
import _ from 'lodash-es';
export const getProductList = (state) => _.clone(state.productReducer.productList);
Then use in your selector hook like so:
const productList = useSelector(state => getProductList(state));
This will help to keep your code clean and easy to understand.
For the rest of your task, there are many ways that you can approach this but I'll give you two examples.
Create an action for updating a product's power which will be
dispatched by the component when the power input changes. This will
persist the change in the redux store. In your reducer you would add
a case statement for this new action and modify the data as you
wish. You may even want to persist this change in a database
somewhere and instead make an api call to save a product's power and
then modify your store's product list with the newly persisted
power.
If you only want to persist these changes locally in the
component use can make use of the useState hook. You could initialise a useState hook with the products from your product list selector and write a handler to update this local state.
Option 1 would look like this example below. Where index and power would be passed in your updateProductPower action's payload.
case 'UPDATE_PRODUCT_POWER':
const newState = _.clone(state);
newState.productList[index].power = power;
return newState;
Option 2 would look like this:
const productList = useSelector((state) => _.clone(state.productReducer.productList));
const [products, setProducts] = useState(productList);
// Updates your local state if the store changes. But will overwrite changes in your local state.
useEffect(() => {
setProducts(productList);
}, [productList]);
// Dispatches an action to persist changes on API when the local products state changed.
useEffect(() => {
dispatch(persistProductList(products);
}, [products]);
const handlePowerChange = (index, power) => {
const result = _.clone(products);
result[index].power = power;
setProducts(result);
}
You can use a mixture of both but you just need to remember how data flows in a redux/flux architecture.
Sorry not a perfect answer but should help you understand the direction you need to go. I have to crack on with work now.
You can try the split operator:
if (productList !== undefined) {
for (let b = 0; b < productList.length; b++) {
productList[b] = {...productList[b], power: 0};
}
}
Or you can assign via array brackets:
if (productList !== undefined) {
for (let b = 0; b < productList.length; b++) {
productList[b]["power"] = 0;
}
}

Adding Redux State Values

I'm trying to combine two separate prices into one total price. I have the two individual price states stored and correctly updating using independent reducers, but I need a way to combine these two dynamic values into a third, total price state.
import React, {useState} from "react";
import { useSelector, useDispatch } from "react-redux";
const planPrice = useSelector(state => state.planPrice);
const repairPrice = useSelector(state => state.repairPrice);
//Use the state hook to store the Total Price
const [totalPrice, setTotalPrice] = useState(0);
const [items, setItems] = useState([]);
function addItem (newItem) {
setItems((prevItems) => {
return [...prevItems, newItem];
});
//Update the Plan Price
if ({plan}.plan === "Premium") {
dispatch(addItemPremium());
} else if ({plan}.plan === "Basic") {
dispatch(addItemBasic());
}
//Update the Repair Price
if (newItem === "Small Chip") {
dispatch(addSmallChip());
} else if (newItem === "Big Chip") {
dispatch(addBigChip());
}
// //Update the Total Price
setTotalPrice({planPrice} + {repairPrice});
}
Is this even possible using Redux, or am I going about this the wrong way?
If you have derived state, you should consider using selectors. These are functions that take state as an argument and return a derived state value.
For example:
function totalPrice(state) {
return state.price1 + state.price2;
}
If you need to use this in mapStateToProps, you can do so as follows:
const mapStateToProps = state => ({
price: totalPrice(state)
})
If you're using hooks, you can use the useSelector hook:
const price = useSelector(totalPrice);
There are libraries (e.g., reselect) that will help you create composable selectors with caching for efficiency.
Note that the reason you should use selectors versus storing redundant state, as you may have intuited, is that redundant state can get out of sync and then you're hosed. But if you have a function that computes the total price, it'll always be in sync with the individual state parts.

Prevent infinite renders when updating state variable inside useEffect hook with data fetched using useQuery of graphql

Graphql provides useQuery hook to fetch data. It will get called whenever the component re-renders.
//mocking useQuery hook of graphql, which updates the data variable
const data = useQuery(false);
I am using useEffect hook to control how many times should "useQuery" be called.
What I want to do is whenever I receive the data from useQuery, I want to perform some operation on the data and set it to another state variable "stateOfValue" which is a nested object data. So this has to be done inside the useEffect hook.
Hence I need to add my stateOfValue and "data" (this has my API data) variable as a dependencies to the useEffect hook.
const [stateOfValue, setStateOfValue] = useState({
name: "jack",
options: []
});
const someOperation = (currentState) => {
return {
...currentState,
options: [1, 2, 3]
};
}
useEffect(() => {
if (data) {
let newValue = someOperation(stateOfValue);
setStateOfValue(newValue);
}
}, [data, stateOfValue]);
Basically I am adding all the variables which are being used inside my useEffect as a dependency because that is the right way to do according to Dan Abramov.
Now, according to react, state updates must be done without mutations to I am creating a new object every time I need to update the state. But with setting a new state variable object, my component gets re-rendered, causing an infinite renders.
How to go about implementing it in such a manner that I pass in all the variables to my dependency array of useEffect, and having it execute useEffect only once.
Please note: it works if I don't add stateOfValue variable to dependencies, but that would be lying to react.
Here is the reproduced link.
I think you misunderstood
what you want to be in dependencies array is [data, setStateOfValue] not [data, stateOfValue]. because you use setStateOfValue not stateOfValue inside useEffect
The proper one is:
const [stateOfValue, setStateOfValue] = useState({
name: "jack",
options: []
});
const someOperation = useCallback((prevValue) => {
return {
...prevValue,
options: [1, 2, 3]
};
},[])
useEffect(() => {
if (data) {
setStateOfValue(prevValue => {
let newValue = someOperation(prevValue);
return newValue
});
}
}, [data, setStateOfValue,someOperation]);
If you want to set state in an effect you can do the following:
const data = useQuery(query);
const [stateOfValue, setStateOfValue] = useState({});
const someOperation = useCallback(
() =>
setStateOfValue((current) => ({ ...current, data })),
[data]
);
useEffect(() => someOperation(), [someOperation]);
Every time data changes the function SomeOperation is re created and causes the effect to run. At some point data is loaded or there is an error and data is not re created again causing someOperation not to be created again and the effect not to run again.
First I'd question if you need to store stateOfValue as state. If not (eg it won't be edited by anything else) you could potentially use the useMemo hook instead
const myComputedValue = useMemo(() => someOperation(data), [data]);
Now myComputedValue will be the result of someOperation, but it will only re-run when data changes
If it's necessary to store it as state you might be able to use the onCompleted option in useQuery
const data = useQuery(query, {
onCompleted: response => {
let newValue = someOperation();
setStateOfValue(newValue);
}
)

How to update the correct state value in a parent component that gets its value from a child component using hooks in react?

I am working on a react project and have an issue with the using current correct state value. I've recreated the issue in a small dummy project that contains a Parent component and a Child Component. The Child is passed down props from the Parent component, one state variable and one function that updates said state utilizing react hooks (useState). Within the original function residing in the Parent component is a call to a second function that performs an action based on the current state. I have the console logs and result showing that the passed down function sends back up the correct value to update, but the following function does not use the updated value, instead seems to always be one render behind.
I've attempted using async/await as well as the useEffect hook. I've been able to get the "expected" result using some variation of useEffect in this small dummy project however it didn't translate into the actual project duo to their being more function calls that also depend on a state value, although I may just be misunderstanding/ doing something wrong.
export const Parent = () => {
const [count, setCount] = useState(0);
const [fruit, setFruit] = useState(null);
const counter = (index) => {
console.log("index passed in: ", index);
setCount(index);
secondMethod()
}
const secondMethod = () => {
console.log('state of count in second method', count);
const arr = ['apple', 'cherry', 'orange', 'kiwi', 'berry'];
setFruit(arr[count]);
}
return (
<div>
<p>Hello from Parent, current count: {count}</p>
<Child counter={counter} count={count}/>
<p>result of second method: {fruit}</p>
</div>
);
}
export const Child = ({ counter, count }) => {
return (
<div>
<p>Child comp</p>
<button onClick={() => counter(count + 1)}>
Click on send counter
</button>
</div>
);
}
The console log value on index is correct as well as the { count } output. The result of the console log from the secondMethod and therefore the state of the setFruit is incorrect and uses the state from one render behind? So count will be displayed as 1 but the secondMethod will still have count as value 0 so displays "apple" instead of "cherry". I appreciate any and all help/suggestions, thanks!
React state updates action are asynchronous and are batched for performance gains. There's no guarantee that console.log('state of count in second method', count); will reflect the updated state.
Solution 1: use the index value in secondMethod passed in as a parameter
Solution 2: update the fruit after count was updated, with the useEffect hook. Replace secondMethod with:
useEffect(() => {
console.log('state of count in useEffect', count);
const arr = ['apple', 'cherry', 'orange', 'kiwi', 'berry'];
setFruit(arr[count]);
}, [count, setFruit]);
Solution 3: If your states depend heavily on each other, then maybe they should be part of the same single state. That way you can use the function version of setState, that always receives the correct previous value.
// join separate states together
const [fruitCount, setFruitCount] = useState({ count: 0, fruit: null });
const counter = index => {
setFruitCount({ count: index });
secondMethod();
};
const secondMethod = () => {
const arr = ["apple", "cherry", "orange", "kiwi", "berry"];
// use setState function, count is taken from the previous state value
setFruitCount(prevState => ({ ...prevState, fruit: arr[prevState.count] }));
};

Resources