React useRef is not setting even in useEffect - javascript

I am attempting to show an image in a random position according to dynamic dimensions using a ref. However, when getting the useRef's current value, I recieve undefined likely because the useRef's target div hasn't loaded yet.
To overcome this, I am using a useEffect to wait for the target div to load.
However, when I run the program the image does not move because the useRef's current value remains at 0. The
import './MouseAim.css'
import '../Game.css'
import React, {useEffect, useRef, useState} from 'react'
import TargetImg from '../../../assets/Target.png'
import _ from "lodash";
function MouseAim() {
const [start, setStart] = useState(true)
const [target, setTarget] = useState(true)
const elementDimensions = useRef()
//wait for elementDimensions to be set
useEffect(() => {
console.log('elementDimensions', elementDimensions.current?.clientHeight)
} , [elementDimensions])
return (
<div>
<div className="main-container">
{
start ?
<div ref={elementDimensions} className="aim-container">
{
target ?
<input
className="target"
type="image"
style={{position: "relative", left:elementDimensions.current?.clientHeight+"px" , top:_.random(0, elementDimensions.current?.clientHeight)+"px"}}
onClick={() => console.log("hello")}
src={TargetImg}
alt="target"
/>
:null
}
</div>
:
<div className="start-container">
<input className="start-button" type="button" value="Start Game" onClick={() => setStart(true)}/>
</div>
}
</div>
</div>
)
}
export default MouseAim

Just set your target initial state to 'false', then set it to true when 'ref' is ready
Try this one:
const [start, setStart] = useState(true)
const [target, setTarget] = useState(false)
const elementDimensions = useRef()
//wait for elementDimensions to be set
useEffect(() => {
console.log('elementDimensions', elementDimensions.current?.clientHeight)
setTarget(true)
} , [elementDimensions])

Just like you mentioned, the useRef is set very initially when the div has not been mounted.
The div mounts when start becomes true. So that's where the trigger needs to happen.
Try to pass start as the dependency variables to the useEffect.
useEffect(() => {
console.log('elementDimensions', elementDimensions.current?.clientHeight)
} , [start, elementDimensions])
PS. If it doesn't work, pass target as the dependency variable as well.
useEffect(() => {
console.log('elementDimensions', elementDimensions.current?.clientHeight)
} , [start, target, elementDimensions])

I think separating your code into subcomponents will make this much simpler, as MouseAim has too much responsibility. In general, giving your components one clearly-defined purpose is a good way to simplify your codebase.
const AimContainer = () => {
const containerRef = useRef()
const [containerSize, setContainerSize] = useState();
useEffect(() => {
// set container size state here
}, [])
return <div ref={containerRef}>...</div>
}
const MouseAim = () => {
const [hasStarted, setHasStarted] = useState(false);
return <div>
{ hasStarted ?
<AimContainer />
:
<button onClick={() => setHasStarted(true)}>Start</button>
}
</div>
}
AimContainer will render before running useEffect, so you may also want to avoid rendering the target until containerSize is set.

Related

ReactJS compontent state not update correctly

Consider I got a component called Test
import {useEffect, useState} from "react";
const Test = (props) => {
const [Amount, setAmount] = useState(1);
useEffect(()=>{
if(props.defaultAmount){
setAmount(props.defaultAmount)
}
props.getResult(Amount);
},[props, Amount])
return (
<>
<span>Amount is: {Amount}</span>
<input value={Amount} onChange={(e)=>setAmount(e.target.value)}/>
</>
)
}
export default Test;
I use this in two different components (actually my pages), one with defaultAmount another without.
Page 1:
<Test getResult={getAmountResult} defaultAmount={25}/>
But this not update result and it back to default one!
Page 2:
<Test getResult={getAmountResult} />
it works fine!
Working Demo
Is there any solution to avoid this?
try to change your code like this
import {useEffect, useState} from "react";
const Test = (props) => {
const [Amount, setAmount] = useState(1);
useEffect(() => {
props.getResult(Amount);
}, [Amount])
useEffect(()=>{
if(props.defaultAmount){
setAmount(props.defaultAmount)
}
},[props.defaultAmount])
return (
<>
<span>Amount is: {Amount}</span>
<input value={Amount} onChange={(e)=>setAmount(e.target.value)}/>
</>
)
}
export default Test;
in your current implementation you always overwrite the amount state with the default
Your useEffect function is the culprit. You're setting the Amount back to defaultAmount everytime Amount changes, thus overriding the user input.
Try updating the condition within useEffect before you set the value, to make sure you don't override the user input, something like:
useEffect(()=>{
if(props.defaultAmount && Amount === 1){ // Checking if the amount is still the initial value
setAmount(props.defaultAmount)
}
props.getResult(Amount);
},[props, Amount])
When input changes, setAmount called, it will update amount and trigger useEffect hook which will set amount to default value. Try this
import { useEffect, useState } from "react";
const Test = (props) => {
const [amount, setAmount] = useState(props.defaultAmount);
useEffect(() => {
if (amount) {
props.getResult(amount);
}
}, [amount, props]);
return (
<>
<span>Amount is: {amount}</span>
<input value={amount} onChange={(e) => setAmount(e.target.value)} />
</>
);
};
export default Test;

How to solve setState is always one step behind on React JS [duplicate]

This question already has answers here:
Update variable and use it immediately React
(2 answers)
Closed 11 months ago.
I am a beginner. I am learning react js. I am having an problem. setState is always one step behind.
Here is a sample:
Here, when I typed i then the console is showing nothing. Next, when I typed the m it shows i and as it is one step behind.
I have created two functions named handleChange and handleKeyword. The functions are behaving the same. I searched on the internet and got useEffect() suggestion to solve the problem but that has not solved my problem or I can't properly implement it.
Here is my codes:
Home.jsx
import React, { useState, useEffect } from 'react';
import Search from '../../components/searchBar/Search';
import './home.scss';
const Home = () => {
const [search, setSearch] = useState('');
const [keyword, setKeyword] = useState('');
const handleChange = event => {
setSearch(event.target.value);
console.log('Search: ', search);
};
const handleKeyword = () => {
setKeyword(search);
console.log('Keyword:', keyword);
};
return (
<div className="container pb-5">
<Search
handleChange={handleChange}
handleKeyword={handleKeyword}
keyword={keyword}
/>
</div>
);
};
export default Home;
Search.jsx
import React from 'react';
import './search.scss'
const Search = props => {
return (
<div className="d-flex input-group justify-content-center">
<input
type="text"
className="form-control searchBox"
placeholder="Search for copyright free images & videos..."
value={props.value}
onChange={event => props.handleChange(event)}
/>
<button className="btn btn-primary" onClick={() => props.handleKeyword()}>
Search
</button>
</div>
);
};
export default Search;
How can I solve the problem?
In Home.jsx, you can move the console statments inside useEffect with states search and keyword as dependencies to get the updated values. This issue is because react is declarative in nature so it decides when to setState runs. It can even be batched together for performance optimisations. So useEffect can be used in such cases to listen to change in states.
import React, { useState, useEffect } from 'react';
import Search from '../../components/searchBar/Search';
import './home.scss';
const Home = () => {
const [search, setSearch] = useState('');
const [keyword, setKeyword] = useState('');
useEffect(() => {
console.log('Search: ', search);
console.log('Keyword:', keyword);
}, [search, keyword])
const handleChange = event => {
setSearch(event.target.value);
};
const handleKeyword = () => {
setKeyword(search);
};
return (
<div className="container pb-5">
<Search
handleChange={handleChange}
handleKeyword={handleKeyword}
keyword={keyword}
/>
</div>
);
};
export default Home;
The problem is setState just promise you that value will be updated It does not affect your code, just move console.logs outside handleClicks
So, when you set a new state and you will see a new value only after rerender component.
const handleKeyword = () => {
setKeyword(search);
console.log("Keyword:", keyword);
};
console.log("Keyword:2", keyword);
console.log("Keyword:", keyword); will be called in the first render with the old value
console.log("Keyword:2", keyword); will be called in the second render with a new value.
setState is async so changes to the state are not applied immediately.
see here https://reactjs.org/docs/react-component.html#setstate

React render component only for few seconds

In my existing react component, I need to render another react component for a specific time period.
As soon as the parent component mounts/or data loads, the new-component (or child component) should be visible after 1-2 seconds and then after another few seconds, the new-component should be hidden. This needs to be done only if there is no data available.
This is what currently I've tried to achieve:
import React, { useState, useEffect } from "react";
function App() {
const [showComponent, setShowComponent] = useState(false);
const sampleData = [];
useEffect(() => {
if (sampleData.length === 0) {
setTimeout(() => {
setShowComponent(true);
}, 1000);
}
}, [sampleData]);
useEffect(() => {
setTimeout(() => {
setShowComponent(false);
}, 4000);
}, []);
const componentTwo = () => {
return <h2>found error</h2>;
};
return <>First component mounted{showComponent && componentTwo()}</>;
}
export default App;
The current implementation is not working as expected. The new-component renders in a blink fashion.
Here is the working snippet attached:
Any help to resolve this is appreciated!
Every time App renders, you create a brand new sampleData array. It may be an empty array each time, but it's a different empty array. Since it's different, the useEffect needs to rerun every time, which means that after every render, you set a timeout to go off in 1 second and show the component.
If this is just a mock array that will never change, then move it outside of App so it's only created once:
const sampleData = [];
function App() {
// ...
}
Or, you can turn it into a state value:
function App() {
const [showComponent, setShowComponent] = useState(false);
const [sampleData, setSampleData] = useState([]);
// ...
}
I have modified the code to work, hope this how you are expecting it to work.
import React, { useState, useEffect } from "react";
const sampleData = [];
// this has to be out side or passed as a prop
/*
reason: when the component render (caued when calling setShowComponent)
a new reference is created for "sampleData", this cause the useEffect run every time the component re-renders,
resulting "<h2>found error</h2>" to flicker.
*/
function App() {
const [showComponent, setShowComponent] = useState(false);
useEffect(() => {
if (sampleData.length === 0) {
const toRef = setTimeout(() => {
setShowComponent(true);
clearTimeout(toRef);
// it is good practice to clear the timeout (but I am not sure why)
}, 1000);
}
}, [sampleData]);
useEffect(() => {
if (showComponent) {
const toRef = setTimeout(() => {
setShowComponent(false);
clearTimeout(toRef);
}, 4000);
}
}, [showComponent]);
const componentTwo = () => {
return <h2>found error</h2>;
};
return <>First component mounted{showComponent && componentTwo()}</>;
}
export default App;
You can try this for conditional rendering.
import { useEffect, useState } from "react";
import "./styles.css";
const LoadingComponent = () => <div>Loading...</div>;
export default function App() {
const [isLoading, setLoading] = useState(true);
const [isError, setIsError] = useState(false);
const onLoadEffect = () => {
setTimeout(() => {
setLoading(false);
}, 2000);
setTimeout(() => {
setIsError(true);
}, 10000);
};
useEffect(onLoadEffect, []);
if (isLoading) {
return <LoadingComponent />;
}
return (
<div className="App">
{isError ? (
<div style={{ color: "red" }}>Something went wrong</div>
) : (
<div>Data that you want to display</div>
)}
</div>
);
}
I needed to do imperatively control rendering an animation component and make it disappear a few seconds later. I ended up writing a very simple custom hook for this. Here's a link to a sandbox.
NOTE: this is not a full solution for the OP's exact use case. It simply abstracts a few key parts of the general problem:
Imperatively control a conditional render
Make the conditional "expire" after duration number of milliseconds.

making set method of useState hook to wait for the variable update

I have a use case where I am using the useState hook to increment value of the variable. Once the value of the variable is incremented then only I need to call a update function.
const [page, setPage] = useState(1);
const fetchMoreData = () => {
setPage(page+1);
updateNews();
};
So in essence I wanted it to be something like await setPage(page+1);. So that once the page is updated then only I fetch the news from the update URL page.
Due to this currently I am getting
index.js:1 Warning: Encountered two children with the same key, `https://english.jagran.com/trending/did-mars-ever-look-like-earth-heres-what-top-nasa-scientist-has-to-say-10033695`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
at div
at div
at div
at div
at InfiniteScroll (http://localhost:3000/static/js/vendors~main.chunk.js:32922:24)
at News (http://localhost:3000/static/js/main.chunk.js:775:89)
at Route (http://localhost:3000/static/js/vendors~main.chunk.js:34951:29)
at Switch (http://localhost:3000/static/js/vendors~main.chunk.js:35153:29)
at div
at Router (http://localhost:3000/static/js/vendors~main.chunk.js:34582:30)
at BrowserRouter (http://localhost:3000/static/js/vendors~main.chunk.js:34203:35)
at App
This is my component News.js currently
const News = (props)=>{
const [articles, setArticles] = useState([]);
const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1);
const [totalResults, setTotalResults] = useState(0);
const capitalizeFirstLetter = (string)=> {
return string.charAt(0).toUpperCase() + string.slice(1);
}
const updateNews = async ()=>{
props.setProgress(10);
let goToPage = page;
const url = `https://newsapi.org/v2/top-headlines?country=${props.country}&category=${props.category}&apiKey=${props.apiKey}&page=${goToPage}&pageSize=${props.pageSize}`;
props.setProgress(30);
let data = await fetch(url);
props.setProgress(50);
let parsedData = await data.json();
props.setProgress(70);
if(parsedData)
{
setArticles(articles.concat(parsedData.articles));
setLoading(false);
setPage(page);
setTotalResults(parsedData.totalResults);
}
props.setProgress(100);
}
useEffect(() => {
updateNews();
// eslint-disable-next-line
}, [])
const fetchMoreData = () => {
setPage(page+1);
updateNews();
};
return (
<>
<h3 className="text-center" style={{marginTop:'4%'}}>NewsMonkey - Top {`${capitalizeFirstLetter(props.category)}`} Headlines</h3>
{loading && <Spinner/>}
<InfiniteScroll
dataLength={articles.length}
next={fetchMoreData}
hasMore={articles.length < totalResults}
loader={<Spinner/>}
>
<div className="container">
<div className="row">
{articles.map((element)=>{
return (
<div className="col-md-4" key={element.url}>
<NewsItem title={element && element.title?element.title.slice(0, 45): ""} description={element && element.description?element.description.slice(0, 50):""}
imageUrl={element.urlToImage}
newsUrl ={element.url}
author={element.author}
date={element.publishedAt}
source={element.source.name}/>
</div>
)})}
</div>
</div>
</InfiniteScroll>
</>
)
}
export default News
I tried printing the value of goToPage in the update function and as I could see it was 1 every time.
Is there any way to resolve the error or wait for setPage.
Note : I tried the solution to the question which I was getting as suggestion to this question, but that did not work for me.
If you want to increment page property, you probably should use setPage with callback function, like this:
setPage(page => page + 1);
You can achieve desired effect by using useEffect with page in dependency array:
useEffect(() => {
updateNews();
}, [page])

Browser freezing when using React Hook

When I enter into a router that refers to this Component, my browser just crashes. I've made some console tests and notices that when the response.data.message is up, it continually re-renders the page. Can someone help me?
import React from 'react'
import "./UsernameStory.css";
import Axios from "axios";
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
const fetchUsername = () => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {
}).then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
})
}
return (
<div>
{fetchUsername()}
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
On every render, fetchUsername() is called and results in updating statue and stories, which results in another rerender, and thus leads to an infinite loop of rerendering (since every render triggers a state update).
A better practice for handling functions with side-effects like fetching data is to put the fetchUsername in useEffect.
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
useEffect(() => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {})
.then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
});
}, []); // An empty denpendency array allows you to fetch the data once on mount
return (
<div>
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
You can't call function fetchUsername() inside return statement. It
will go in infinite loop.
You can't directly set the state of two variable as setStatue(response.data.statue) & setStories(response.data.message) respectively
use the useEffect hooks and set the status inside that hooks with conditional rendering to avoid looping.
call the fetchUsername() just before the return statement.
I looked at your code carefully. As you can see in this code, the fetchUsername() function is executed when the component is first rendered.
When you call setState() in the fetchUsername function, your component's state variable is updated. The problem is that when the state variable is updated, the component is rendered again. Then the fetchUsername function will be called again, right?
Try the useEffect Hook.
The example code is attached.
eg code
How about trying to change the way you call the fetchUsername() function with the useEffect hook instead?
import React, { useEffect } from 'react'
import "./UsernameStory.css";
import Axios from "axios";
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
useEffect(() => {
const fetchUsername = () => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {
}).then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
})
}
fetchUsername();
// clean up here
return () => {
// something you wanna do when this component being unmounted
// console.log('unmounted')
}
}, [])
return (
<div>
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
But, sometimes your code just doesn't need a cleanup in some scenarios, you could read more about this here: Using the Effect Hook - React Documentation.

Categories

Resources