I am trying to understand the useRef hook in React.
I have created a simple time in react . The code for which is available below.
import { useRef, useState, useEffect } from 'react';
function Parent() {
const [count,setCount]=useState(5);
const ref=useRef(0);
//let hold=0;
const countdown=()=>{
ref.current=setInterval(()=>{
// console.log('ref.current-->',ref.current);
setCount((c)=>c-1)
},1000)
}
useEffect(()=>{
if(count<1)
clearInterval(ref.current)
},[count])
return(
<>
<h3>Timer : {count}</h3>
<br/>
<button onClick={countdown}>countdown</button>
</>
)
}
export default Parent;
Here I have created a ref using the hook and I am monitoring the count state . When it hits 0 , I am calling the 'clearInteval' function to clear up the timer.
This code is working fine .
But when I try to do the same using a normal variable rather than the one created by the hook , the interval is not getting cleared .
Please find below the code for the same.
import { useRef, useState, useEffect } from 'react';
function Parent() {
const [count,setCount]=useState(5);
const ref=useRef(0);
let hold=0;
const countdown=()=>{
hold=setInterval(()=>{
// console.log('ref.current-->',ref.current);
setCount((c)=>c-1)
},1000)
}
useEffect(()=>{
if(count<1)
clearInterval(hold)
},[count])
return(
<>
<h3>Timer : {count}</h3>
<br/>
<button onClick={countdown}>countdown</button>
</>
)
}
export default Parent;
What am I not understanding here ?
This code should have worked in normal javaScript.
const myRef = useRef() will provide a ref object such that myRef.current's value will persist across renders. When you use let myVar = something, myVar will be re-created on each render, so you will lose and replace its value each time.
Your Parent component is getting re-rendered each time your state changes, so you benefit from keeping your interval reference as a ref from useRef.
Related
i have a react component thats keep re-rendering idk why but i think the reason is the data fetching
data code :
export function KPI_Stock_Utilisation() {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
setKpi_stock_utilisation((existingData) => {
return response.data;
});
});
}, []);
console.log('data get')
return kpi_stock_utilisation;
}
this log displayed many times , and the log in the component too
component code :
import React from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import { useEffect } from "react";
export default function WarehouseUtilisChart(props) {
let kpi_stock_utilisations =KPI_Stock_Utilisation();
let Stock_utilisation = (kpi_stock_utilisations.length / 402) * 100;
console.log('component render')
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
im new with react i tried useEffect inside the componenets but its not working
Calling the react custom hook KPI_Stock_Utilisation several times will for sure render more than once.
in your case I suggest you use useEffect in the same component as I will show you.
import React,{useEffect,useRef} from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import axios from 'axios';
export default function WarehouseUtilisChart(props) {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
const stock_utilisation= useRef(0);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
stock_utilisation.current = (response.data.length / 402) * 100;
setKpi_stock_utilisation(response.data);
});
//this will guarantee that the api will be called only once
}, []);
//you should see this twice, one with the value 0, and another one, the calculated data
console.log('component render',stock_utilisation.current)
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
To note, if you call this component from more than one location, for sure it will render several times - keep that in mind.
On the other hand, all your variables should always start with a lower case and try to name your variables like this: instead of kpi_stock_utilisation change it to kpiStockUtilisation for a better coding practice
You got into infinite loop.
Its hard to explain why it doesn't work as expected, but I can try.
First of all, useEffect with empty array of dependencies works like componentDidMount and fires only after (!) first render.
So you have some value returned from your let kpi_stock_utilisations =KPI_Stock_Utilisation(); then it rendered, after this your useEffect fires a request and set state, setting of state trigger re-render and new value to return, this new value trigger your parent component to return let kpi_stock_utilisations =KPI_Stock_Utilisation(); might run again.
If you are trying to create a custom hook for fetching some info, follow rules of hooks
I hope it helped you
Im new in react.
I'm Created two file App.js and UseEffect.js
I'm Learn about lifecycle in react with function.
So When I See in console, that's render multiple time.
You can see my picture below.
My Console In Browser
This Is My Code
UseEffect.js
import React, {useState, useEffect} from "react";
function MyFunction(){
console.log('-> Function Init')
const [count, setCount] = useState(0)
const handleCount = () => {
setCount(prevState => {
return prevState+1
})
}
//LifeCycle
useEffect(() => {
console.log('my first effect')
})
console.log(`-> Start Render (${count})`)
return(
<div>
<h1>Function Component</h1>
<p>
<button onClick={handleCount}>Count</button>
{count}
</p>
</div>
)}
export default MyFunction
App.Js
import './App.css';
import UseEffect from './components/UseEffect'
function App() {
return (
<div className="App">
<UseEffect />
</div>
);
}
export default App;
How do it's work?, I Want it. it's just render one times.
Your useEffect call is missing a dependency array. When you want it to run only at the initial render, you need to pass it an empty array as its dependencies.
useEffect(() => {
console.log('my first effect')
}, [])
For further details, see this question.
Why it renders twice:
It's an intentional feature of the StrictMode. This only happens in development, and helps find accidental side effects put into the render phase. We only do this for components with Hooks because those are more likely to accidentally have side effects in the wrong place.
-gaearon
TLDR: It's a feature not a bug.
I tried to increment the counter in the test but when i press the button the value doesnt change. I used the fireEvent from React testing library and React test utils but the value still in 10.I use react 18.
CounterApp:
import {useState} from "react";
import PropTypes from "prop-types";
const CounterApp = ({value=10})=>{
const [counter,setCounter] = useState(value);
const handleAdd= ()=>{
setCounter(counter+1);
}
const handleSubstract = ()=>{
if(counter>0){
setCounter(counter-1);
}
}
const handleReset = ()=>{
setCounter(0);
}
return(
<>
<h1>CounterApp</h1>
<h2>{counter}</h2>
<button onClick={handleAdd}>+1</button>
<button onClick={handleSubstract}>-1</button>
<button onClick={handleReset}>Reset</button>
</>
);
}
CounterApp.propTypes={
value: PropTypes.number.isRequired
}
export default CounterApp;
And the test archive:
import { create} from "react-test-renderer";
import CounterApp from "../CounterApp";
import '#testing-library/jest-dom';
import ReactTestUtils from 'react-dom/test-utils';
import {fireEvent} from "#testing-library/react";
describe("Test in counterApp",()=>{
test("Should be increment the count",()=>{
const component = create(<CounterApp value={10}/>);
const values= component.root;
const button=values.findAllByType("button").at(0).props;
const counter = values.findByType("h2").props.children;
ReactTestUtils.Simulate.click(button);
expect(counter).toBe("11");
})
})
You should format your component. Otherwise it's hard to read and you'll get issues because of that.
I couldn't understand if it works fine on a manual test, so not sure if the issue is on the testing or the component itself.
When using the setter in useState you have a callback, so instead of using the getter, you should do:
const handleAdd = () => {
setCounter(prev => prev + 1);
}
For the testing you should use an id to better identify the button, not the type.
You made a mistake to update state variable using the previous state value.
ReactJS setState()
All the React components can have a state associated with them. The state of a component can change either due to a response to an action performed by the user or an event triggered by the system. Whenever the state changes, React re-renders the component to the browser. Before updating the value of the state, we need to build an initial state setup. Once we are done with it, we use the setState() method to change the state object. It ensures that the component has been updated and calls for re-rendering of the component.
setState({ stateName : updatedStateValue })
// OR
setState((prevState) => ({
stateName: prevState.stateName + 1
}))
So you should use like the following.
const handleAdd= ()=>{
setCounter(prev => prev+1);
}
const handleSubstract = ()=>{
if(counter>0){
setCounter(prev => prev-1);
}
}
Although the text gets updated on the page, the console.log still logs out the initial value. Why is this? I know that setting the state is asynchronous but why does it still log out the old value even after waiting 1 second?
import { useState, useEffect, useRef } from "react";
function App() {
const [requestState, setRequestState] = useState("initial");
useEffect(() => {
setRequestState("changed");
setTimeout(() => {
console.log(requestState);
}, 1000);
}, []);
return (
<div className="App">
{requestState}
</div>
);
}
export default App;
useEffect will run when the component renders,To call a function conditionally, specify the list of dependencies.
And the rule of thumb is to always add those dependencies that you are using inside the useEffect()
import { useState, useEffect, useRef } from "react";
function App() {
const [requestState, setRequestState] = useState("initial");
setRequestState("changed");
useEffect(() => {
setTimeout(() => {
console.log(requestState);
}, 1000);
}, [requestState]);
return (
<div className="App">
{requestState}
</div>
);
}
export default App;
The useEffect() hook "captures" state and props when it is executed. That is why it has a stale value. The value is from when the function in useEffect() was run.
This is a beautiful article by Dan Abramov: https://overreacted.io/a-complete-guide-to-useeffect/. It has an explanation about almost the exact same problem as yours. Read it completely to have a great insight into useEffect()
Your useEffect depends on the requestState varible, so pass it inside the empty list like so:
useEffect(() => {some code},[used variables])
I want to have a global object that is available to my app where I can retrieve the value anywhere and also set a new value anywhere. Currently I have only used Context for values that are related to state i.e something needs to render again when the value changes. For example:
import React from 'react';
const TokenContext = React.createContext({
token: null,
setToken: () => {}
});
export default TokenContext;
import React, { useState } from 'react';
import './App.css';
import Title from './Title';
import TokenContext from './TokenContext';
function App() {
const [token, setToken] = useState(null);
return(
<TokenContext.Provider value={{ token, setToken }}>
<Title />
</TokenContext.Provider>
);
}
export default App;
How would I approach this if I just want to store a JS object in context (not a state) and also change the value anywhere?
The global context concept in React world was born to resolve problem with passing down props via multiple component layer. And when working with React, we want to re-render whenever "data source" changes. One way data binding in React makes this flow easier to code, debug and maintain as well.
So what is your specific purpose of store a global object and for nothing happen when that object got changes? If nothing re-render whenever it changes, so what is the main use of it?
Prevent re-render in React has multiple ways like useEffect or old shouldComponentUpdate method. I think they can help if your main idea is just prevent re-render in some very specific cases.
Use it as state management libraries like Redux.
You have a global object (store) and you query the value through context, but you also need to add forceUpdate() because mutating the object won't trigger a render as its not part of React API:
const globalObject = { counter: 0 };
const Context = React.createContext(globalObject);
const Consumer = () => {
const [, render] = useReducer(p => !p, false);
const store = useContext(Context);
const onClick = () => {
store.counter = store.counter + 1;
render();
};
return (
<>
<button onClick={onClick}>Render</button>
<div>{globalObject.counter}</div>
</>
);
};
const App = () => {
return (
<Context.Provider value={globalObject}>
<Consumer />
</Context.Provider>
);
};