React state not updating in time with useEffect - javascript

Update: Problem found - the onChange event is fired on component render, this overwrites what happens in the useEffect... not sure how to address this conflict, might have to dig into the installed component.
Update 2: "Solution" was to put a setTimeout in the onChange event for the slider, and a slightly longer setTimeout on the useEffect so that they resolved in the correct order.
I'm using a slider component in react from here - it is working properly.
I am trying to update the minimum and maximum slider values + the actual value of the sliders whenever a chartData set is updated using useEffect();
Despite setting some states within useEffect, these states aren't being rendered by the component.
The chartData is being retrieved asynchronously from the backend, which may contribute to this problem.
Here is my code:
//Histogram block size
const blockSize = 5000;
//Initial boundaries for histogram slider
const initialMin = 15000;
const initialMax = (labels.length-1)*blockSize;
const [max, setMax] = useState(initialMax);
//States for histogram slider
const [minValue, setMinValue] = useState(initialMin);
const [maxValue, setMaxValue] = useState(initialMax);
//Update histogram sliders when new data is input
useEffect(()=>{
setMax(initialMax)
setMinValue(initialMin)
setMaxValue(initialMax)
}, [chartData])
//Handle histogram sliding
const handleInput = (e) => {
setMinValue(e.minValue);
setMaxValue(e.maxValue);
};
return (<div className = 'Chart' style={{position: 'relative', width: "62vh"}}>
<Bar options={options} data={data}/>
<MultiRangeSlider
min={0}
max={max}
step={blockSize}
minValue={minValue}
maxValue={maxValue}
onChange={(e) => {
handleInput(e);
}}
/>
Chart data is coming from:
//chartData state passed to Chart component
const [chartData, setChartData] = useState(initialChartData);
const buttonClickFunction = ()=>axios.get('/api/v1/data', {'params': ['python', 'javascript' , 'c++']})
.then((res)=>{
setChartData(res.data);
});
As part of the rendered component:
...
<Chart chartData={chartData}></Chart>
...

Related

React firebase get data from .on('value')

I am getting data from firebase in react, but I am not able to pass on that data as the variables are defined internally. Following is what I am trying to do.
function getCommentNums(item){
const formRef = database.ref(
`/comments/${form_id}/${instanceId}/${item.id}`
);
console.log('formref = ', formRef)
formRef.on('value', async(snap)=>{
const commentsArr = (await snap.val()) ?? [];
console.log('commentArr=', commentsArr.length)
setCommentLen(commentsArr.length)
})
return someNum
}
then in main return statement getcommentnums is called inside accordion
{questions.map((item, index) => (
<Accordion
key={index}
id={
"question-" +
(noOfQuestionsPerPage * (page - 1) + 1 + index)
}
question={item}
questionNo={noOfQuestionsPerPage * (page - 1) + 1 + index}
//match vs item.id
commentNums = {getCommentNums(item)}
onBlur={handleClickSave}
onClickComments={onClickComments}
onChangeAnswer={onChangeAnswer}
answers={answers}
onClickLastValue={onClickLastValue}
disabled={form.is_submitted}
/>
))}
I am trying someNum to be commentsArr.length, which is supposed to be some integer. This function is going to be called in some child component to display value of commentNums. Multiple child components are going to be on one page and each would be calling above fn to get there respective commentNums.
I have tried using set state, but that just causes infinite loop.
Can someone show me how to send commentArr.length value forward?
While you call setCommentLen(commentsArr.length) to update the commentLen state variable, your rendering code still tries to render the return value of getCommentNums, which won't work.
The proper way to implement this is to:
Modify your loader function to no longer return any value, and only update the state.
function loadCommentCount(item){
const formRef = database.ref(`/comments/${form_id}/${instanceId}/${item.id}`);
formRef.on('value', async(snap)=>{
const commentsArr = (await snap.val()) ?? [];
setCommentLen(commentsArr.length)
})
}
Call this loader function outside of the rendering code, for example when the component is created, typically in a useState handler.
useState(() => {
questions.map((item, index) => (
loadCommentCount(item);
})
}, [questions])
Then render the value from the state.
commentNums = {commentCount}
Also see:
React Not Updating Render After SetState
How to return a value from Firebase to a react component?
Firebase response is too slow
My firebase realtime push key is changed when I save it in array in react native
React-native prevent functions from executing asynchronously?

How to pass props to React component using React Router after state change?

I'm building a web app and it contains a dropdown list with various scores. Each score looks like this:
<NavDropdown.Item
key = {i}
id = {score["scoreId"]}
onClick = {(e) => {
setScore(score)
window.location.href = '/home/score'
// do something to go to the score page
}}
>
{score["scoreName"]}
</NavDropdown.Item>
When clicking on a score, I first use setScore() to set the score state to the currently selected score, then redirect to /home/score
<Routes>
<Route path="/score" element={<ScoreHome score={score}/>} />
</Routes>
Problem:
I need to pass the newly set score prop to the <ScoreHome /> component, but useState() is asynchronous so props.score would be undefined on the new page.
useState doesn't have callback functions and I tried to use useEffect for something like this:
useEffect(() => {
window.location.href = '/home/score'
},[score])
But this creates an infinite loop for me (the browser keeps jumping to or refreshing /home/score)
What's the best way to handle this? Thanks!
With React-Router, you can pass a state with your navigation call, like so:
...
const navigate = useNavigate();
...
<NavDropdown.Item
key = {i}
id = {score["scoreId"]}
onClick = {(e) => {
navigate('/home/score', { state: {score} });
}}
>
{score["scoreName"]}
</NavDropdown.Item>
Then in ScoreHome.js
const {state} = useLocation();
// state.score <= your selected score
See docs for more
With the help from #Moath, I was able to navigate to ScoreHome.js with the updated score in useEffect() without causing infinite loops. Here's what I did:
const navigate = useNavigate();
useEffect(() => {
if (score["scoreId"] !== undefined) {
navigate('/home/score',{state:{score, 'overview': false}})
}
},[score])
And in ScoreHome.js:
const location = useLocation()
// got score here
var score = location.state.score

React changing background images using getElementById

There is soemthing weird happening with me in react
for some reason changing the background image of a div like that works:
document.getElementById("player1-card1").style.backgroundImage =
`url(${require(../images/3.png)})`
But like that wont work
const y = 3
const x = `../images/${y}.png`
document.getElementById("player1-card1").style.backgroundImage =
`url(${require(x)})`
x is the same value as ../images/3.png so why it is not working I am confused
You shouldn't be using document.getElementById in React. If you want to set the style in react, import the image and then just pass it into the style prop.
import img from "../images/3.png"
<div style={{backgroundImage: img}}
I don't remember how websockets api looks like, but your code should utilize state like that:
const Component = () => {
const [image, setImage] = useState();
useEffect(() => {
socket.onMessage = (data) => {
setImage(data)
}
})
return <div style={{ backgroundImage: image }} />
}

Problem with reset state function and only than fetching data (React hooks)

I am showing a list of users(profiles), and fetch it from some users DB.
I am in the search page which include sub pages for diffrenet filters - like which users are currently online.
Each time i am moving inside the search sub pages, i have to reset only once the main filtering variable in order ot get the correct result.
The problem is the fetch request happpend before the setState variable changed.
I saw other people asked how to fetch after, while i need it to first reset the variables of setState and the to go and fetch according to the correct values.
code:
const [isPopUpShowState,setIsPopUpShowState] = useState(false);
const [profilesloading,setProfilesLoading] = useState(<Spinner/>);
const [profilesLength,setProfilesLength] = useState(0);
const [profilesPerPage] = useState(4);
const [searchStartPoint,setSearchStartPoint] = useState(0);
const [lastUserConnIndex,setLastUserConnIndex] = useState(1);
useEffect( ()=> {
restoreStatesToDefault(); // reset states+list --> the variables doesnt changed before the the fetch
getProfilesMatchingPage(); // init profiles
},[history.location.pathname]);
const restoreStatesToDefault = () => {
list = {};
setSearchStartPoint(0);
setLastUserConnIndex(1);
setProfilesLength(0);
}
const getSearchProfilesParmsInObj = () => {
const parmsObj = {};
if(currUser.loginObj){
parmsObj['isMale'] = !currUser.loginObj.data.isMale;
parmsObj['profilesPerPage'] = profilesPerPage;
parmsObj['searchStartPoint'] = searchStartPoint;
parmsObj['lastUserConnIndex'] = lastUserConnIndex;
parmsObj['allProfiles'] = list;
}
return parmsObj;
}
const getProfilesMatchingPage = () => {
switch(history.location.pathname){
case '/search/online':
dispatch(getProfilesOnline(getSearchProfilesParmsInObj(),setProfilesLoading,setLastUserConnIndex,setProfilesLength));
break;
case '/search/pics':
dispatch(getProfilesOnlyWithPics(getSearchProfilesParmsInObj(),setProfilesLoading,setLastUserConnIndex,setSearchStartPoint,setProfilesLength));
break;
case '/search/recently':
dispatch(getProfilesRecentlyVisited(getSearchProfilesParmsInObj(),setProfilesLoading,setLastUserConnIndex,setSearchStartPoint,setProfilesLength));
break;
case '/search/news':
dispatch(getProfilesNewUsersRegistered(getSearchProfilesParmsInObj(),setProfilesLoading,setLastUserConnIndex,setSearchStartPoint,setProfilesLength));
}
}
The problem is that both functions are called within the same lifecycle of the function, so the states haven't updated yet (They are within the same closure). After your useEffect finishes, then the next render is called with the updated state values, but they are not dependencies of your useEffect so they don't trigger it to fire again (which is a good thing in this case).
Basically what you want is two useEffect -> one is triggered on path change, and that one should update state that is a dependency of another useEffect that triggers the fetch.
A simple example would be:
const [shouldFetch, setShouldFetch] = useState(false) // Set this to true if you want to fetch on initial render
useEffect( ()=> {
restoreStatesToDefault(); // reset states+list --> the variables doesnt changed before the the fetch
setShouldFetch(true);
},[history.location.pathname]);
useEffect(() => {
if (shouldFetch) {
setShouldFetch(false);
getProfilesMatchingPage(); // init profiles
}
}, [shouldFetch])

How to access state values from a different class in React?

I'm trying to create a search bar that filters out a set of data. The search function I made uses several states to filter results. When the search bar and results page are in the same class, the search function works but what I'm trying to do now is separate the search bar and display the search results on a separate page. Here's the state being set in the SearchBar class.
handleChange = (event) => {
this.setState({
names: event.target.value
})
}
The problem is I have no idea how to get the data stored in the SearchBar class to be displayed on the results page. Here's how I'm filtering the results on the results page.
const filteredData = data.filter(entry => (entry.name === (this.state.names))
This data is being filtered in the Search class but this.state.names is being stored in the SearchBar class. This SearchBar class is being displayed on my header where users can search for whatever they want and after they press search, the results page appears. So how can I take the data stored in the SearchBar class and use it in a different class?
UPDATE: I tried passing in the state to the Search class in the render function but that causes the entire page to just freeze.
render() {
return (
<Search names = {this.state.names} />
)
}
Not sure if I understood correctly but:
You can make a new component to store your data.
Then use this function (or similar) in onChange on that component
const filterData = (e) => {
const valueToCheck = e.target.value
let newArr = []
for(entry of data) {
// do the logic
//push the data you want into an array
newArr.push(entry)
}
setState(newArr)
}
SearchBar should call onSearchResults([...]) callback and then PageResult may accept those results, you need a component that orchestrate all.
const App = () =>{
const [results, setResults] = useState([]);
return (<>
<SearchBar onSearchChange={setResults}/>
{ results.length && <PageResult results={results}/> }
</>)
}
SearchBar will call props.onSearchChange(results) with the filtered data. App component will react to that change and send those results to PageResult component

Categories