useState, useMemo keeps reseting value on browser refresh - javascript

I was trying to get the total value of marks on a table of student exam scores. On save changes I get the correct value. But on browser refresh the value to 0. I used useEffect() and useMemo() hooks but kept getting the same result. But I used normal variable, (not useState() hook), the values stays even after refresh.
Here is the code.
const englishSubmitted = JSON.parse(
localStorage.getItem("englishSubmitted") as string
);
const [englishResult, setEnglishResult] = useState<
{ [key: string]: string }[]
>([]);
useEffect(() => {
if (englishSubmitted) {
setEnglishResult(
JSON.parse(localStorage.getItem("englishAnswers") as string)
);
}
},[])
`// this does not work`
useEffect(() => {
const value = englishResult.reduce((acc, current) => {
acc += parseInt(current.marks);
return acc;
}, 0);
setEnglishMarks(value);
}, []);
`//this does not work either`
useMemo(() => {
const value = englishResult.reduce((acc, current) => {
acc += parseInt(current.marks);
return acc;
}, 0);
setEnglishMarks(value);
}, []);
Putting a value in the dependency array does not work either.
But the following code works. the value variable retains its value even after refresh
const value = englishResult.reduce((acc, current) => {
acc += parseInt(current.marks);
return acc;
}, 0);

But the following code works. the value variable retains its value even after refresh
const value = englishResult.reduce((acc, current) => {
acc += parseInt(current.marks);
return acc;
}, 0);
That's the code i would recommend anyway for a computed value like this one. Copying it into a different state just makes the component do extra renders and complicates the code.
If the array is particularly large, such that it's causing you a performance issue, you can skip the calculation if nothing has changed by doing useMemo like this:
const value = useMemo(() => {
return englishResult.reduce((acc, current) => {
acc += parseInt(current.marks);
return acc;
}, 0);
}, [englishResult])
Note that you should not set state in useMemo. Just return the value.

You can use localStorage for that:
For example: https://www.npmjs.com/package/easy-react-hooks

Related

Redux Toolkit state object is not changing "Uncaught TypeError: Cannot assign to read only property 'property-name' of object '#<Object>'"

I want to change the data of a state object but Redux Toolkit is not changing and gives an error like Uncaught TypeError: Cannot assign to read only property 'status' of object '#<Object>'.
These lines from component:
const [width, setWidth] = useState(8)
const [height, setHeight] = useState(9)
const [table, setTable] = useState({
rows:
[...Array(height)].map(()=>
(
{
cells:
[...Array(width)].map(()=>
(
{status: true}
)
)
}
)
)
})
useEffect(()=>{
dispatch(changePlayableFields(table)) // <- it's not changing the state
},[table])
function changeCell(i:number,k:number){
const localTable = {...table}
localTable.rows[i].cells[k].status = !localTable.rows[i].cells[k].status // <-Uncaught TypeError: Cannot assign to read only property 'status' of object '#<Object>'
setTable(localTable)
}
changeCell function is working very well and I see the truth results on the page. But when adding useEffect codes to move new datas to keep them in memory with redux, then I get the errors.
And these are from Redux Slice:
import { createSlice } from "#reduxjs/toolkit"
const levelSlice = createSlice({
name: "level",
initialState: {
gridSizeAndPlayableFields: {
width: 8,
height: 9,
playableFields: {
rows:
[...Array(9)].map(()=>
(
{
cells:
[...Array(8)].map(()=>
(
{status: true}
)
)
}
)
)
}
},
},
reducers: {
changePlayableFields: (state, action) => {
state.gridSizeAndPlayableFields.playableFields = action.payload // <- it's not changing the data
},
}
})
export const {changeGridSize, changePlayableFields} = levelSlice.actions
export default levelSlice.reducer
It's little about my previous question, maybe you'd like to check it. Here is my previous question link: Redux Slice is returning an error after adding a function into a property of initial states
I hope anyone can help. Thanks...
1. Reducer does not update issue:
It seems you are sending rows as payload of the dispatch. So you should update gridSizeAndPlayableFields.playableFields.rows in the reducer:
changePlayableFields: (state, action) => {
// console.log(action.payload);
state.gridSizeAndPlayableFields.playableFields.rows = action.payload.rows;
// console.log(current(state.gridSizeAndPlayableFields.playableFields));
},
2. object update issue in changeCell method:
In a normal function you cannot mutate the object like in changeCell function. You can only do this in the redux toolkit slice thanks to Immer.
You can map the rows and cells arrays to update the corresponding indeces' status values. You can write the changeCell method in the following way:
function changeCell(i, k) {
if (!table) return;
const localTable = {};
const localTableRows = [...table.rows];
// map rows array
const updatedRows = localTableRows.map((item, index) => {
// if index equals i, map its cells as well
//... and find kth cell and change its status
if (index === i) {
return {
...item,
cells: item.cells.map((c, idx) => {
if (idx === k) {
return {
...c,
status: !c.status,
};
}
// if idx not equals to k return old cell item
return c;
}),
};
}
// if index is not equal i return old row item
return item;
});
localTable.rows = updatedRows;
setTable(localTable);
}
I suppose i and k index values for the zero based array.
You can use Object.reduce method to calculate updated table object in your changeCell function; but personally I try to avoid from this because in general Object.reduce is less performant.

How to get the value of useRef current in React Native

I am developing a React Native application and am facing the following error:
I have defined a useRef which stores the doc ID from a firebase collection. But when I call that variable after it has been defined, the .current value returns a blank string.
db.collection('users').onSnapshot((snapshot) => {
snapshot.docs.map((doc) => {
if (doc.data().email === auth.currentUser?.email) {
bidId.current = doc.id
console.log(bidId.current)
}
})
})
The above code returns the expected value. However, when I call the variable outside this db.collection loop, I get the following value:
But calling the bidId.current returns a blank string.
Please can someone help me with this. Thanks!
Actually this is what happens:
db.collection('users').onSnapshot((snapshot) => {
snapshot.docs.map((doc) => {
if (doc.data().email === auth.currentUser?.email) {
bidId.current = doc.id
// This line gets executed after some time!
console.log(bidId.current)
}
})
})
// This gets executed first! (The value has not been stored yet!)
console.log(bidId.current);
Using the "useState" hook instead of "useRef" will solve the issue. Consider the following code:
const [BidId, setBidId] = useState<string | null>(null);
// This will store the value...
useEffect(() => {
db.collection('users').onSnapshot((snapshot) => {
snapshot.docs.map((doc) => {
if (doc.data().email === auth.currentUser?.email) {
setBidId(doc.id);
}
})
})
}, []);
// Here you can access the value
useEffect(() => {
if(BidId !== null)
console.log(BidId);
}, [BidId]);
// You can also return the component like the following:
return (<View>The Bid ID is: {BidId !== null ? BidId : "Loading..."}</View>);
Your useEffect basically says that whenever pageRef changes, call this function. If done outside, it will call do your tasks on every render instead of doing the whenever pageRef values is changed. Also, in initial renders, it may give undefined values.
You can only return a function in useEffect which basically says that before running the same next time, run this function before.
Try (currentUser without the '?' query character):
if (doc.data().email === auth.currentUser.email) {
bidId.current = doc.id
console.log(bidId.current)
}

How do I set the value of a property via a function?

I'm trying to run a function inside a reducer function to calculate a cart total, but the value in the state object is the function and not the result of the function. After render, the result is displayed, but I cannot pass the object to other components (I'm using a context). How do I do it? Here is the code (assume that the values work, because they do).
I've tried setting it to the const to no avail. I've tried an anonymous function that returns the function's total, and it still doesn't work. I've also tried just calling the function.
const reducer = (cart, action) => {
switch(action.type) {
case("ADD_ITEM"):
return {
...cart,
products: {
...cart.products,
[action.payload.product.id]: {...action.payload.product}
},
total: () => (cartTotal)
}
break
case("REMOVE_ITEM"):
delete cart.products[action.payload]
return {
...cart,
products: {
...cart.products
},
total: () => (cartTotal)
}
break
case("CLEAR_CART"):
return {
cart: {
...initialState
}
}
break
}
}
Here is the object:
cart: {
products: [{}],
total: 0
}
Here is the function to return the total:
const cartTotal = () => {
const total = Object.values(cart.products).reduce((prev, curr) => {
const currPrice = (curr.data.on_sale && curr.data.sale_price) ? curr.data.sale_price : curr.data.price
return prev + currPrice
}, 0)
return total.toFixed(2)
}
Right now, I'm passing the method that allows you to calculate the total, but it seems like it is unnecessary, as I'm watching the cart state and updating the value of total each time items are added/removed. How do I set the value of a property inside of the reducer function as the return of another helper function? Thanks!
I don't know why are you storing derived state, well, in state? This should be computed via a selector when reading your state out (and/or passed to a custom Context provider).
If you must store the total in state then you need to call the cartTotal function to be able to store its return value. Unfortunately this will only compute the cart total on the unupdated cart since you are currently in the function that returns the new cart state.
You can factor out the cart update so you have an updated cart products object, and with a small revision of cartTotal it can consume this updated cart products object and compute a total.
Example:
const cartTotal = (products) => {
const total = Object.values(products).reduce((prev, curr) => {
const currPrice = (curr.data.on_sale && curr.data.sale_price) ? curr.data.sale_price : curr.data.price;
return prev + currPrice;
}, 0);
return total.toFixed(2);
};
Cases
case "ADD_ITEM": {
const { payload } = action;
const products = {
...cart.products,
[payload.product.id]: { ...payload.product },
}
return {
...cart,
products,
total: cartTotal(products),
}
break;
}
case "REMOVE_ITEM": {
const products = { ...cart.products };
delete products[action.payload];
return {
...cart,
products,
total: cartTotal(products),
}
break;
}
You need to call cartTotal to calculate the value.
total: cartTotal()

Typescript Reduce returning wrong Type

I'm trying to use reduce to sum my SQL response in two categories, income and outcome, but I'm getting the "currentValue" (transaction) type as my return instead of the accumulator (BalanceDTO), I'm returning only the accumulator, how I'm getting the transaction type as my return type?
I really don't know what I'm doing wrong, I am completely stuck for the last two hours.
import { getRepository } from 'typeorm';
import Transaction from '../models/Transaction';
interface BalanceDTO {
income: number;
outcome: number;
}
class GetBalanceService {
public async execute(): Promise<BalanceDTO> {
const transactionRepository = getRepository(Transaction);
const transactions = await transactionRepository.find();
const balance = transactions.reduce(
(accumulator: BalanceDTO, transaction): BalanceDTO => {
accumulator[transaction.type] += transaction.value;
return accumulator; //// HERE -- How it is returning the transactions type? ////
},
);
return balance;
}
}
export default GetBalanceService;
You missing define initial value and type of initial value. Type of initial value will define return type of the reduce function.
I suggest define initial value as a BalanceDTO and start values is zero
const balance = transactions.reduce((accumulator, transaction) => {
accumulator[transaction.type] += transaction.value;
return accumulator;
}, {
income: 0,
outcome: 0,
} as BalanceDTO); // initialValue as a BalanceDTO
Now, balance is a BalanceDTO
You need to pass an initial value for accumulator as the second argument of recude function:
const balance = transactions.reduce(
(accumulator: BalanceDTO, transaction): BalanceDTO => {
accumulator[transaction.type] += transaction.value;
return accumulator; //// HERE -- How it is returning the transactions type? ////
},
{
income: 0,
outcome: 0
}
);
Try this one.
const balance = transactions.reduce(
(accumulator: BalanceDTO, transaction): BalanceDTO => {
accumulator[transaction.type] += transaction.value;
return accumulator;
},{income: 0, outcome: 0} //you'd better set the initial value
);
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

Use spread operator to copy array state and add all objects that match

I am trying to sort all objects that match the regex into an array.
This does not seem to work with the spread operator and useState, is there any way I can do that?
The result I am getting now is the samples thing only gives me the last object that matches it and nothing else.
The desired effect I want is all the samples that match get pushed into the samples state.
const [accessories, setAccessories] = useState([]);
const [paints, setPaints] = useState([]);
const [samples, setSamples] = useState([]);
// Load order into state
useEffect(() => {
loadUser();
getOrderById(match.params.orderId);
}, []);
// Load order into state
useEffect(() => {
if (!loading) {
console.log(order.line_items);
for (let i = 0; i < order.line_items.length; i++) {
if (order.line_items[i].sku.match(/^(TAC|T.BU.AC)/)) {
console.log('SKU: ', order.line_items[i].sku);
//#ts-ignore
setAccessories([...accessories, order.line_items[i]]);
console.log(accessories);
}
if (order.line_items[i].sku.startsWith('TBA') || order.line_items[i].sku.match(/^TCR(?!0000)/)
|| order.line_items[i].sku.match(/^TCR0000/)) {
//#ts-ignore
setPaints([...paints, order.line_items[i]]);
}
if (order.line_items[i].sku.match(/^TCR\d+P?\d+SAMP/)) {
console.log(samples);
console.log(order.line_items[i]);
//#ts-ignore
setSamples([...samples, ...[order.line_items[i]]]);
}
}
}
}, [loading]);
Well there are few mistakes you're doing here.
Mistake 1:
Calling the same setStates way too many times inside a single useEffect block using a for loop, this might greatly affect React's performance. Again, this is clearly a violation of Rules of Hooks, Only Call Hooks at the Top Level
Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions.
Mistake 2:
Though this is not as serious as the previous ones, it's still a mistake. Not using better solutions, Use inbuilt JavaScript methods like filter instead of writing your own for loop
useEffect(() => {
let _accessories;
let _paints;
let _samples;
if (!loading) {
_accessories = order.line_items.filter(({ sku }) => sku.match(/^(TAC|T.BU.AC)/))
_paints = order.line_items.filter(({ sku }) => sku.startsWith('TBA') || sku.match(/^TCR(?!0000)|^TCR0000/))
_samples = order.line_items.filter(({ sku }) => sku.match(/^TCR\d+P?\d+SAMP/))
// Never use setState inside a for loop
// of useEffects
// Also avoid calling same setState multiple times
// use callback setState if you want to access
// previous state, but it ain't a compulsory like
// for class components
setAccessories(s => [ ...s, ..._accessories ])
setPaints(s => [ ...s, ..._paints ])
setSamples(s => [ ...s, ..._samples ])
}
// return in useEffect has different role
// than normal functions
}, [loading])
Spread the results of calling .filter into the calls:
useEffect(() => {
if (loading) {
return;
}
const items = order.line_items;
setAccessories([
...accessories,
items.filter(({ sku }) => sku.match(/^(TAC|T.BU.AC)/))
]);
setPaints([
...paints,
items.filter(({ sku }) => sku.startsWith('TBA') || sku.match(/^TCR(?!0000)|^TCR0000/))
]);
setSamples([
...samples,
items.filter(item => item.sku.match(/^TCR\d+P?\d+SAMP/))
]);
}, [loading]);

Categories