React usePrevious does not seem to be having previous value - reactjs

In my React functional components, I am trying to use custom usePrevious to get/compare the previous value of my context object. However, it seems to be having the latest value always and not the previous value. Below is my code. Please let me know if I am doing something wrong here.
function MyHeaderComponent(props) {
const [myPref, setMyPref] = React.useContext(MyContext);
const prevPreferences = usePrevious(myPref.preferences);
useEffect(() => {
console.log("prevPreferences : " + prevPreferences); // Prints current context object, instead of the previous one
// I want to call myInfo only if prevPreferences is not the same as myPref.preferences (i.e. current)
}, [myPref]);
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const myInfo = async () => {
setMyPref(myPref);
}
return (
<>
<div>
Some JSX
</div>
</>
)
}
export default withRouter(withStyles()(MyHeaderComponent));
function MyPreferences(props) {
const [myPref, setMyPref] = React.useContext(MyContext);
// somewhere in code
setMyPref(myPref);
}

That implementation of useLatest (which should not be within the component, by the way) does not copy the value into the ref box, it just assigns a reference.
I'd wager myPref.preferences gets internally modified (instead of being reassigned a new object), which is why you always see the same value.

I think the problem is you are using usePrevious hook inside another functional component. Could you try to create a file for the usePrevious hook.
usePreviousState.js
function usePrevious(value, initialValue) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
if (ref.current === undefined && initialValue !== undefined) {
return initialValue;
}
return ref.current;
}

Related

How to inform useCallback that useState was updated?

I have problem with me code because in useCallback function always is shown state outdated, pre-last one.
In useEffect(()) I do console.log, and I can see the change of state, but in second function it shown only when it is click two times. It is not the way I can do something.
import produce from Immer;
const [obiektPompa, setObiektPompa] = useState([]);
const [modulPV, setModulPV] = useState([]);
function setDodatkowe(e, index) {
e.preventDefault();
const {cena, nazwa} = formData.current;
const newProduct = {nazwa: nazwa.value,
cena: parseFloat(cena.value)
}
setObiektPompa((prevState) => ([...prevState, newProduct ]))
console.log("zmiana Pomp", obiektPompa)
AddItemPrice(index)
}
useEffect(() => {
console.log("obiekt Pompa z useEffect", obiektPompa) //Console.log state
}, [obiektPompa])
const AddItemPrice = useCallback ((index ) => {
setModulyPV(
produce( modulyPV, draft => {
const objIndex = draft.findIndex((obj => obj.index === index));
const value = obiektPompa[obiektPompa.length -1].cena
console.log("index", draft[index].cena)
console.log("Vaalue pompa", value)
})
)
}, [modulyPV, setModulyPV, setDodatkowe, setObiektPompa, obiektPompa])
Needless to say that I put useRef hook the value from setDodatkowe is from submit property of button.
Problem is with obiektPompa which is useState, and as you see in useEffect show me everything, but in useCallback do not.
In AddItemPrice I tried to add dependence formData.current.onsubmit but it do not give result.

Cannot update a component while rendering a different Component - ReactJS

I know lots of developers had similar kinds of issues in the past like this. I went through most of them, but couldn't crack the issue.
I am trying to update the cart Context counter value. Following is the code(store/userCartContext.js file)
import React, { createContext, useState } from "react";
const UserCartContext = createContext({
userCartCTX: [],
userCartAddCTX: () => {},
userCartLength: 0
});
export function UserCartContextProvider(props) {
const [userCartStore, setUserCartStore] = useState([]);
const addCartProduct = (value) => {
setUserCartStore((prevState) => {
return [...prevState, value];
});
};
const userCartCounterUpdate = (id, value) => {
console.log("hello dolly");
// setTimeout(() => {
setUserCartStore((prevState) => {
return prevState.map((item) => {
if (item.id === id) {
return { ...item, productCount: value };
}
return item;
});
});
// }, 50);
};
const context = {
userCartCTX: userCartStore,
userCartAddCTX: addCartProduct,
userCartLength: userCartStore.length,
userCartCounterUpdateCTX: userCartCounterUpdate
};
return (
<UserCartContext.Provider value={context}>
{props.children}
</UserCartContext.Provider>
);
}
export default UserCartContext;
Here I have commented out the setTimeout function. If I use setTimeout, it works perfectly. But I am not sure whether it's the correct way.
In cartItemEach.js file I use the following code to update the context
const counterChangeHandler = (value) => {
let counterVal = value;
userCartBlockCTX.userCartCounterUpdateCTX(props.details.id, counterVal);
};
CodeSandBox Link: https://codesandbox.io/s/react-learnable-one-1z5td
Issue happens when I update the counter inside the CART popup. If you update the counter only once, there won't be any error. But when you change the counter more than once this error pops up inside the console. Even though this error arises, it's not affecting the overall code. The updated counter value gets stored inside the state in Context.
TIL that you cannot call a setState function from within a function passed into another setState function. Within a function passed into a setState function, you should just focus on changing that state. You can use useEffect to cause that state change to trigger another state change.
Here is one way to rewrite the Counter class to avoid the warning you're getting:
const decrementHandler = () => {
setNumber((prevState) => {
if (prevState === 0) {
return 0;
}
return prevState - 1;
});
};
const incrementHandler = () => {
setNumber((prevState) => {
return prevState + 1;
});
};
useEffect(() => {
props.onCounterChange(props.currentCounterVal);
}, [props.currentCounterVal]);
// or [props.onCounterChange, props.currentCounterVal] if onCounterChange can change
It's unclear to me whether the useEffect needs to be inside the Counter class though; you could potentially move the useEffect outside to the parent, given that both the current value and callback are provided by the parent. But that's up to you and exactly what you're trying to accomplish.

React local element not updating?

I have a component which has a local variable
let endOfDocument = false;
And I have a infinite scroll function in my useEffect
useEffect(() => {
const { current } = selectScroll;
current.addEventListener('scroll', () => {
if (current.scrollTop + current.clientHeight >= current.scrollHeight) {
getMoreExercises();
}
});
return () => {
//cleanup
current.removeEventListener('scroll', () => {});
};
}, []);
In my getMoreExercises function I check if we reached the last document in firebase
function getMoreExercises() {
if (!endOfDocument) {
let ref = null;
if (selectRef.current.value !== 'All') {
ref = db
.collection('exercises')
.where('targetMuscle', '==', selectRef.current.value);
} else {
ref = db.collection('exercises');
}
ref
.orderBy('average', 'desc')
.startAfter(start)
.limit(5)
.get()
.then((snapshots) => {
start = snapshots.docs[snapshots.docs.length - 1];
if (!start) endOfDocument = true; //Here
snapshots.forEach((exercise) => {
setExerciseList((prevArray) => [...prevArray, exercise.data()]);
});
});
}
}
And when I change the options to another category I handle it with a onChange method
function handleCategory() {
endOfDocument = false;
getExercises();
}
I do this so when we change categories the list will be reset and it will no longer be the end of the document. However the endOfDocument variable does not update and getMoreExercise function will always have the endOfDocument value of true once it is set to true. I cannot change it later. Any help would be greatly appreciated.
As #DevLoverUmar mentioned, that would updated properly,
but since the endOfDocument is basically never used to "render" anything, but just a state that is used in an effect, I would put it into a useRef instead to reduce unnecessary rerenders.
Assuming you are using setExerciseList as a react useState hook variable. You should use useState for endOfDocument as well as suggested by Brian Thompson in a comment.
import React,{useState} from 'react';
const [endOfDocument,setEndOfDocument] = useState(false);
function handleCategory() {
setEndOfDocument(false);
getExercises();
}

How to get the closest previous different prop in ReactJS

I have a functional component with props:
function MyComponent({propA, propB, propC}) {
...
}
This component is rendered few times with different props values. I need to get the closest previous prop value which is not equal to current value. For example:
First render. propA is 1. previous value must be undefined
Second render. propA is 2. previous value must be 1
Third render. propA is 2. previous value must be 1 (not 2, because
current value is 2)
There is a similar "usePrevious" hook:
export const usePrevious = <T>(value: T): T | undefined => {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
But it returns any previous value even if it has not changed. And it looks like I should modify this hook, but I don't know how
code example: https://codesandbox.io/s/cranky-snow-mkile?file=/src/index.js (props values are logged in console)
Try this:
const usePrevious = newValue => {
const refPrev = useRef()
const refCurr = useRef()
if (newValue !== refCurr.current) {
refPrev.current = refCurr.current;
refCurr.current = newValue;
}
return refPrev.current;
}

Should I use useMemo in hooks?

I created useBanner hooks
const useBanner = (array, yardage) => {
const [bannArr, setBannArr] = useState(array.slice(0, yardage));
const [bannListIndex, setBannIndex] = useState(1);
return {
....
};
};
Am I doing the right thing and the props throw in useState.
It’s permissible to use useBanner.
const Banner= ({
array,
yardage
}) => {
const { bannForth, bannBeck, bannArr } = useBanner(array, yardage);
return (
...
);
};
when props will change here.
Will change the state in useBanner.
or is it considered anti-patterns I have to write all this in useMemo
const useBanner = (array, yardage) => {
const [bannArr, setBannArr] = useState([]);
const [bannListIndex, setBannIndex] = useState(1);
useMemo(() => {
setBannArr(array.slice(0, yardage));
setBannIndex(1);
}, [array, yardage]);
return {
....
};
};
Yes, custom hooks are possible in React. Here is separate document discussing custom hooks.
But exactly you sample may require additional code depending on what is your final goal.
If you want initialize state only once, when component Banner is first created, you can just do as in your first sample
const Banner= ({
array,
yardage
}) => {
const { bannForth, bannBeck, bannArr } = useBanner(array, yardage);
return (
...
);
};
This will work perfectly. But if props array and yardage will change, this will not be reflected in component. So props will be used only once as initial values and then will not be used in useBanner even if changed (And it doesn't matter whether you'll use useBanner or useState directly). This answer highlight this.
If you want to update inital values on each props change, you can go with useEffect like below
const Banner= ({
array,
yardage
}) => {
const { bannForth, bannBeck, bannArr, setBannArr } = useBanner(array, yardage);
useEffect (() => {
// setBannArr should also be returned from useBanner. Or bannArr should be changed with any other suitable function returned from useBanner.
setBannArr(array.slice(0, yardage));
}, [array, yardage, setBannArr])
return (
...
);
};
In this case Banner component can control state itself and when parent component change props, state in Banner component will be reset to new props.
Here is small sample to showcase second option.

Resources