React local element not updating? - reactjs

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

Related

State value not updating in react

I'm working on my first React application and I'm not understanding why the State doesn't have the updated value.
Here is my code:
const SlideOutPanel = forwardRef((props: any, ref: any) => {
const initCss: string = 'is-slide-out-panel';
const [compClass, setCompClass] = useState(initCss);
useImperativeHandle(ref, () => ({
open() {
open();
},
close() {
close();
},
}));
function refresh(): any {
let classVal: string = compClass;
if (props.direction === 'left-to-right') {
classVal = `${classVal} left-to-right`;
} else if (props.direction === 'right-to-left') {
classVal = `${classVal} right-to-left`;
}
if (Types().boolVal(props.userOverlay)) {
classVal = `${classVal} use-overlay`;
}
if (Types().boolVal(props.pushMain)) {
classVal = `${classVal} push-effect`;
}
if (props.theme === 'dark') {
classVal = `${classVal} theme-dark`;
}
setCompClass(classVal);
let classValdd: string = compClass;
}
function open(): void {
let classVal: string = compClass;
}
useEffect(() => {
refresh();
}, []);
return (
<section id={id} className={compClass}>
<div className="content">{props.children}</div>
</section>
);
});
I call refresh() when the components first load, which basically sets the className based on the passed props. At the end of the function, I set state "setCompClass" the value of "classVal" which works as I verified in Chrome Debugger. But on the same function I have the following line "let classValdd: string = compClass;" just to check what the value of "compClass" is and its always "is-slide-out-panel".
At first I thought it has to do with a delay. So when I call open() to do the same check, the value is still "is-slide-out-panel". So I'm a bit confused. Am I not able to read the state value "compClass"? Or am I misunderstanding its usage?
Setting the state in React acts like an async function.
Meaning that when you set it, it most likely won't finish updating until the next line of code runs.
So doing this will not work -
setCompClass(classVal);
let classValdd: string = compClass;
You will likely still end up with the previous value of the state.
I'm not exactly sure what specifically you're trying to do here with the classValdd variable at the end of the function block, but with function components in React, if we want to act upon a change in a state piece, we can use the built-in useEffect hook.
It should look like this -
useEffect(() => {
// Stuff we want to do whenever compClass gets updated.
}, [compClass]);
As you can see, useEffect receives 2 parameters.
The first is a callback function, the second is a dependency array.
The callback function will run whenever there is a change in the value of any of the members in that array.

React usePrevious does not seem to be having previous value

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

Should I use useEffect in this situation?

I don't know if I am allowed to ask questions like these, but I have a dilemma where I don't know should I use useEffect in the situation I have here:
const handleUrlQuery = () => {
if (params.debouncedValue.length > 0) {
queryName = "name";
queryValue = params.debouncedValue;
return {
queryName,
queryValue,
};
} else if (params.debouncedValue === "") {
queryName = "page";
queryValue = params.page;
return {
queryName,
queryValue,
};
}
};
handleUrlQuery();
const url = `${process.env.REACT_APP_API_URL}?${queryName}=${queryValue}`;
const { data, error } = useFetch(url);
This function is used for changing the query part of the url, now it is supposed to change the queryName and queryValue based on the search value or in this case debounced search value. Now I am confused because I have a feeling that I need to use useEffect, but I am not sure, anyone has any advice on this?
If you really want to optimize this code, which unless its in a super heavy component, I don't see too much of a need you could use useMemo.
const url = useMemo(() => {
if (params.debouncedValue.length > 0) {
queryName = "name";
queryValue = params.debouncedValue;
} else if (params.debouncedValue === "") {
queryName = "page";
queryValue = params.page;
}
return `${process.env.REACT_APP_API_URL}?${queryName}=${queryValue}`;
}, [params.debouncedValue, params.page]);
// don't believe you have to add process.env.REACT_APP_API_URL as a dependency
const { data, error } = useFetch(url);
When you don't call the function handleUrlQuery inside a useEffect, it will be called on every re-render, even if params.debouncedValue didn't change.
Therefore, you need a useEffect if you have other state variables changing, and you only want to call handleUrlQuery when specifically params.debouncedValue changes.
Dummy Codesandbox example

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 setState not updating state in one of my functions

I'm working an a react app with a few forms and I am trying to implement an edit form for input items. The function first opens the list item in a pre-populated form.
The editItem function currently looks like this:
editItem(event) {
event.preventDefault();
const target = event.target.parentNode.parentNode;
const { key } = target.dataset;
const { className } = target;
const currState = { ...this.state[className] };
const currItem = currState.list[key];
for (let i in currItem) {
if (i !== "list" && i !== "hidden") {
currState[i] = currItem[i]
}
}
this.setState({ [className]: currState });
this.hideUnhide({target: {name: className}});
}
I have confirmed with console logs that currState is correctly set with the values that I am looking for, and that I am not having an async issue. I am using this same format to set state in other functions in my app and all of the others are working properly. If I directly mutate state in the same place, I get the behavior I'm looking for (form fields populate), but nothing happens when I use setState.
Link to my github repo: here. The function in question is in App.js.
As Brian Thompson points out in his comment, it turns out that the hideUnhide function call directly after my setState uses setState as well and writes over the first setState call with the previous state:
hideUnhide(event) {
const { name } = event.target;
const currState = { ...this.state[name] };
if (currState.hidden === true) {
currState.hidden = false;
}
this.setState({ [name]: currState });
}
The way to prevent that was to use hideUnhide as a callback to the setState in editItem:
this.setState({ [className]: currState }, () =>
this.hideUnhide({ target: { name: className } })
);
and now everything functions as intended.

Resources