In am using React and trying to trigger a function only once, when the page initially loads. Currently, the below code triggers the console message twice at page load.
import { useState, useEffect, useRef } from "react";
export default function TestRef(){
const [inputValue, setInputValue] = useState("");
const count = useRef(null);
const myFunc = () => {
console.log('Function Triggered');
}
useEffect(() => {
if(!count.current){
count.current = 1;
myFunc();
}
return () => { count.current = null; }
}, []);
return (
<>
<p>Page content</p>
</>
);
}
I have read up on how React 18 intentionally double-renders elements, which is my reason for using useRef and for returning the cleanup function in the useEffect hook, but it still doesn't seem to work.
hi please make sure you didn't invoke TestRef componenet twice in your page!
for debug and find rerenders you can use react profiler extention on chrome then remove extra rerender by using momo and useMemo and useCallback
Finally got it to work by doing this. This appears to only run myFunc() once, on the initial rendering of the component.
import { useState, useEffect, useRef } from "react";
export default function TestRef(){
const [inputValue, setInputValue] = useState("");
const count = useRef(null);
const myFunc = () => {
console.log('Function Triggered');
}
useEffect(() => {
if(count.current == null){
myFunc();
}
return () => { count.current = 1; }
}, []);
return (
<>
<p>Page content</p>
</>
);
}
Related
I am new to react (that I use with typeScript) and I am facing an issue with the use of the useMemo hook.
Here is my fetching service:
export default class FetchingService {
datas: Data[] = [];
constructor() {
this.fetch();
}
async fetch(): Promise<Data[]> {
const d = // await an async array from an api, using Array.flat()
this.datas = d;
console.log(this.datas);
return d;
}
}
In a component, I try to watch for change of the datas attribute of my service:
import fetchingService from '../services/fetchingService.ts';
const Home: React.FC = () => {
const ds: Data[];
const [datas, setDatas] = useState(ds);
const fetchDatas = useMemo(() => {
console.log('Render datas', fetchingService.datas?.length)
setDatas(fetchingService.datas);
return fetchingService.datas;
}, [fetchingService.datas]);
return (
<ul>{datas.map(d => {
return (
<li key={d.id}>{d.id}</li>
);
</ul>
);
}
The problem I am facing is that the useMemo hook is not recompouted when the datas attribute changes within my fetchService. I am pretty sure that my FetchingService.fetch() function works because the console.log within the fetch function always display the fetched datas.
The observed behavior is that sometimes datas are well rendered (when fetch ends before rendering ...), but sometimes it isn't.
The expected one is that datas are rendered every time and only on refresh, exept when datas are modified
I also tried to put the length of the data array as a dependency in useMemo, but in both cases it doesn't work and I have a warning in my IDE, telling me it is an unnecessary dependency.
I don't really understand if it is a typescript or a specific react behavior issue. I think the reference of the datas attribute should change at the end of the fetch (or at least its length attribute ...), but tell me if I am wrong.
I do appreciate every help !
in fetchingService, when datas change, probably the dependency cannot be accepted. You can use a custom hook in stead of it.
You can use this source about useMemo: useMemo with an array dependency?
import { useState, useLayoutEffect, useCallback } from "react";
export const useFetchingService = () => {
const [fetchedData, setFetchedData] = useState([]);
const fetch = useCallback(async () => {
const d = await new Promise((res, rej) => {
setTimeout(() => {
res([1, 2, 3]);
}, 5000);
}); // await an async array from an api, using Array.flat()
setFetchedData(d);
}, []);
useLayoutEffect(() => {
fetch();
}, []);
return [fetchedData];
};
useLayoutEffect runs before rendering
using:
const [fetchData] = useFetchingService();
const fetchDatas = useMemo(async () => {
console.log("Render datas", fetchData.length);
setDatas(fetchData);
return fetchData;
}, [fetchData]);
You can also use this directly without 'datas' state.
I hope that this will be solution for you.
So I put together a codesandbox project that uses a context to store the value:
App.tsx
import React, { useState, useEffect, createContext } from "react";
import Home from "./Home";
export const DataContext = createContext({});
export default function App(props) {
const [data, setData] = useState([]);
useEffect(() => {
const get = async () => {
const d = await fetch("https://dummyjson.com/products");
const json = await d.json();
const products = json.products;
console.log(data.slice(0, 3));
setData(products);
return products;
};
get();
}, []);
return (
<div>
Some stuff here
<DataContext.Provider value={{ data, setData }}>
<Home />
</DataContext.Provider>
</div>
);
}
Home.tsx
import React, { FC, useMemo, useState, useEffect, useContext } from "react";
import { DataContext } from "./App";
import { Data, ContextDataType } from "./types";
const Home: FC = () => {
const { data, setData }: ContextDataType = useContext(DataContext);
return (
<>
<ul>
{data.map((d) => {
return (
<li key={d.id}>
{d.title}
<img
src={d.images[0]}
width="100"
height="100"
alt={d.description}
/>
</li>
);
})}
</ul>
</>
);
};
export default Home;
This was my first time using both codesandbox and typescript so I apologize for any mistakes
I am facing an abnormal output on the browser from React while using useEffect hook.
I would request you to please have a look at the code. You can copy and paste the code on any online IDE that supports React to visualize the behavior on the browser.
I want the counter to increment after every 1 second. But with the code it stucks after 10.
import { useState, useEffect } from "react";
function App() {
const initialState = 0;
const [count, setCount] = useState(initialState);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 1000);
// return () => {
// clearInterval(interval);
// };
}, [count]);
return (
<div className="App">
<h1>{count}</h1>
</div>
);
}
export default App;
I want to know the reason for that. Why is it happening?
But when I do cleanup with useEffect to do componentWillUnmoint() it behaves normal and renders the counter every second properly. I have intentionally comment cleanup part of code useEffect.
You are adding an interval on every render, soon enough, your thread will be overloaded with intervals.
I guess you wanted to run a single interval, its done by removing the closure on count by passing a function to state setter ("functional update"):
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return (
<div className="App">
<h1>{count}</h1>
</div>
);
}
export default App;
I need to refresh a component using a button and a HTTP fetch request, so I've a hook that calls the function but I need to fire it from an onClick handler in a button:
const {useEffect, useState, useRef} = React;
const useRefreshArtifacts = (setArtifactsStore) => {
const refresh = () => {
console.log("do some stuff with state");
const [match, setMatch] = useState({});
};
return {refresh};
};
function ArtifactApp(props) {
const {refresh} = useRefreshArtifacts("");
return (<div><button onClick={refresh}>Hello</button></div>);
}
const AppContainer = () => {
return (<div><ArtifactApp /></div>);
}
ReactDOM.render(<AppContainer />, document.getElementById("root"));
https://jsfiddle.net/jre2cwbf/
Which gives the usual Invalid hook call. Hooks can only be called inside of the body of a function component but I can't figure out how to externalise the hook call, I've tried a useCallback but not got any close.
Any help appreciated.
Modify your code in this way:
const {useEffect, useState, useRef} = React;
const useRefreshArtifacts = (setArtifactsStore) => {
const [match, setMatch] = useState("Hello");
const refresh = () => {
console.log("do some stuff with state");
setMatch("refresh")
};
return {match, refresh};
};
function ArtifactApp(props) {
const {match, refresh} = useRefreshArtifacts("");
return (<div><button onClick={refresh}>{match}</button></div>);
}
const AppContainer = () => {
return (<div><ArtifactApp /></div>);
}
ReactDOM.render(<AppContainer />, document.getElementById("root"));
As you can see I have:
move useState outside refresh function;
set match on refresh function;
returned match from custom hook (useRefreshArtifacts) to use it on component.
Here your fiddle modified.
i can't get the useState value that inside of useCallback. when i run the code below,
import React, { useCallback, useEffect, useRef, useState } from 'react';
const DBLab = () => {
const [hashHistory, setHashHistory] = useState("initial_value");
const [inputValue, setInputValue] = useState("");
const didAuthMountRef = useRef(false);
const set = useCallback(() => {
const decodeHash = decodeURI(window.location.hash);
console.log();
console.log("decodeHash: " + decodeHash);
console.log("hashHistory: "+ hashHistory);
setHashHistory(decodeHash);
},[hashHistory]);
useEffect(() => {
const startFunc = async() => {
set();
window.addEventListener('hashchange', set);
didAuthMountRef.current = true;
}
if(!didAuthMountRef.current) {
startFunc();
}
}, [set]);
return (
<div>
<h1>dblab</h1>
<h1>{hashHistory}</h1>
<input type='text' value={inputValue} onChange={(e)=>setInputValue(e.target.value)}/>
</div>
)
}
export default DBLab;
in web console, i get
decodeHash: #/dblab#first
hashHistory: initial_value
which is right. but when i change url to http://localhost:3000/#/dblab#next, i get
decodeHash: #/dblab#next
hashHistory: initial_value
this is wrong. because hashHistory has not changed. but it is not useState problem. because hashHistory that i see with <h1>{hashHistory}</h1> in screen is #/dblab#next which is the right hash.
how can i get the right hashHistory inside of useCallback?.
ps. i must have to use useCallback.
useEffect(() => {
const startFunc = () => {
if(!didAuthMountRef.current) {
set();
didAuthMountRef.current = true;
}
window.addEventListener('hashchange', set);
}
startFunc();
return ()=> {
window.removeEventListener('hashchange', set);
}
}, [set]);
Logic problem
I want to pass in a boolean value in a useState hook that opens and closes a modal on click between two functions. However, I keep getting this error message: Cannot destructure property 'setOpenModal' of 'props' as it is undefined.
Main.js
import React, { useState, useEffect } from "react";
import * as Materials from "../components/Materials"; // <- Material.js
const Main = () => {
const [openModal, setOpenModal] = useState(false); //<- Opens (True) and Closes (False) Modal
const { MaterialContainer } = Materials.MaterialTable(); // <-Calling Function Under MaterialTable
return (
<MaterialContainer
openModal={openModal}
setOpenModal={setOpenModal}
/>
// This is how I am passing in Open/Close useState.
}
Material.js
export const MaterialTable = (props) => {
const { openModal, setOpenModal } = props; // <- Pointed in Error Message.
const openMaterialModal = (item) => {
console.log("Button Clicked");
setOpenModal(true); // <- Where I am passing in a true statement.
};
const MaterialContainer = () => (
<>
<Table>Stuff</Table>
</>
);
return {
MaterialContainer
}
}
Thanks in advance.
The MaterialTable component is entirely malformed from a React perspective, though valid JavaScript. It's just a normal function that defines a couple of constants and then returns nothing. (Well, in the original question it returned nothing. Now it returns an object.)
And when you call that function you indeed don't pass anything to it:
const { MaterialContainer } = Materials.MaterialTable();
So the props will be undefined.
Make MaterialTable itself a React component:
export const MaterialTable = (props) => {
// destructure the props passed to the component
const { openModal, setOpenModal } = props;
// a function I assume you plan to use in the JSX below later?
const openMaterialModal = (item) => {
console.log("Button Clicked");
setOpenModal(true);
};
// the rendering of the component
return (
<>
<Table>Stuff</Table>
</>
);
}
Then just import and use that component, without trying to destructure anything from it or invoke it manually:
import React, { useState, useEffect } from "react";
// import the component
import { MaterialTable } from "../components/Materials";
const Main = () => {
// use the state hook
const [openModal, setOpenModal] = useState(false);
// render the component, passing props
return (
<MaterialTable
openModal={openModal}
setOpenModal={setOpenModal}
/>
);
}