Imperatively trigger an asynchronous request with React hooks - reactjs

I'm having trouble deciding how to trigger an API call imperatively, for example, on a button click.
I'm unsure what is the proper approach with hooks, because there seems to be more than one method, but I don't understand which is the "best" approach and the eventual implications.
I've found the following examples that are simple enough and do what I want:
Using useEffect() with a trigger value
function SomeFunctionComponent() {
const [fakeData, setFakeData] = useState(0);
const [trigger, setTrigger] = useState(false);
async function fetchData() {
if (!trigger) return;
const newData = await someAPI.fetch();
setTrigger(false);
setFakeData(newData);
}
useEffect(() => {
fetchData();
}, [trigger]);
return (
<React.Fragment>
<p>{fakeData}</p>
<button onClick={() => setTrigger(!trigger)}>Refresh</button>
</React.Fragment>
);
}
Example
Just calling the API and then setState()
function SomeFunctionComponent() {
const [fakeData, setFakeData] = useState(0);
async function fetchData() {
const newData = await someAPI.fetch();
setFakeData(newData);
}
return (
<React.Fragment>
<p>{fakeData}</p>
<button onClick={fetchData}>Refresh</button>
</React.Fragment>
);
}
Example
There are also other approaches that leverage useCallback() but as far as I understood they are useful to avoid re-rendering child components when passing callbacks down and are equivalent to the second example.
I think that the useEffect approach is useful only when something has to run on component mount and programmatically, but having what essentially is a dummy value to trigger a side-effect looks verbose.
Just calling the function looks pragmatic and simple enough but I'm not sure if a function component is allowed to perform side-effects during render.
Which approach is the most idiomatic and correct to have imperative calls using hooks in React ?

The first thing I do when I try to figure out the best way to write something is to look at how I would like to use it. In your case this code:
<React.Fragment>
<p>{fakeData}</p>
<button onClick={fetchData}>Refresh</button>
</React.Fragment>
seems the most straightforward and simple. Something like <button onClick={() => setTrigger(!trigger)}>Refresh</button> hides your intention with details of the implementation.
As to your question remark that "I'm not sure if a function component is allowed to perform side-effects during render." , the function component isn't doing side-effects during render, since when you click on the button a render does not occur. Only when you call setFakeData does a render actually happen. There is no practical difference between implementation 1 and implementation 2 in this regard since in both only when you call setFakeData does a render occur.
When you start generalizing this further you'll probably want to change this implementation all together to something even more generic, something like:
function useApi(action,initial){
const [data,setData] = useState({
value:initial,
loading:false
});
async function doLoad(...args){
setData({
value:data.value,
loading:true
});
const res = await action(...args);
setData({
value:res,
loading:false
})
}
return [data.value,doLoad,data.loading]
}
function SomeFunctionComponent() {
const [data,doLoad,loading] = useApi(someAPI.fetch,0)
return <React.Fragment>
<p>{data}</p>
<button onClick={doLoad}>Refresh</button>
</React.Fragment>
}

The accepted answer does actually break the rules of hooks. As the click is Asynchronous, which means other renders might occur during the fetch call which would create SideEffects and possibly the dreaded Invalid Hook Call Warning.
We can fix it by checking if the component is mounted before calling setState() functions. Below is my solution, which is fairly easy to use.
Hook function
function useApi(actionAsync, initialResult) {
const [loading, setLoading] = React.useState(false);
const [result, setResult] = React.useState(initialResult);
const [fetchFlag, setFetchFlag] = React.useState(0);
React.useEffect(() => {
if (fetchFlag == 0) {
// Run only after triggerFetch is called
return;
}
let mounted = true;
setLoading(true);
actionAsync().then(res => {
if (mounted) {
// Only modify state if component is still mounted
setLoading(false);
setResult(res);
}
})
// Signal that compnoent has been 'cleaned up'
return () => mounted = false;
}, [fetchFlag])
function triggerFetch() {
// Set fetchFlag to indirectly trigger the useEffect above
setFetchFlag(Math.random());
}
return [result, triggerFetch, loading];
}
Usage in React Hooks
function MyComponent() {
async function fetchUsers() {
const data = await fetch("myapi").then((r) => r.json());
return data;
}
const [fetchResult, fetchTrigger, fetchLoading] = useApi(fetchUsers, null);
return (
<div>
<button onClick={fetchTrigger}>Refresh Users</button>
<p>{fetchLoading ? "Is Loading" : "Done"}</p>
<pre>{JSON.stringify(fetchResult)}</pre>
</div>
);
}

Related

Unexpected behaviour of setInterval function (interval keeps on decreasing) [duplicate]

Are there ways to simulate componentDidMount in React functional components via hooks?
For the stable version of hooks (React Version 16.8.0+)
For componentDidMount
useEffect(() => {
// Your code here
}, []);
For componentDidUpdate
useEffect(() => {
// Your code here
}, [yourDependency]);
For componentWillUnmount
useEffect(() => {
// componentWillUnmount
return () => {
// Your code here
}
}, [yourDependency]);
So in this situation, you need to pass your dependency into this array. Let's assume you have a state like this
const [count, setCount] = useState(0);
And whenever count increases you want to re-render your function component. Then your useEffect should look like this
useEffect(() => {
// <div>{count}</div>
}, [count]);
This way whenever your count updates your component will re-render. Hopefully this will help a bit.
There is no exact equivalent for componentDidMount in react hooks.
In my experience, react hooks requires a different mindset when developing it and generally speaking you should not compare it to the class methods like componentDidMount.
With that said, there are ways in which you can use hooks to produce a similar effect to componentDidMount.
Solution 1:
useEffect(() => {
console.log("I have been mounted")
}, [])
Solution 2:
const num = 5
useEffect(() => {
console.log("I will only run if my deps change: ", num)
}, [num])
Solution 3 (With function):
useEffect(() => {
const someFunc = () => {
console.log("Function being run after/on mount")
}
someFunc()
}, [])
Solution 4 (useCallback):
const msg = "some message"
const myFunc = useCallback(() => {
console.log(msg)
}, [msg])
useEffect(() => {
myFunc()
}, [myFunc])
Solution 5 (Getting creative):
export default function useDidMountHook(callback) {
const didMount = useRef(null)
useEffect(() => {
if (callback && !didMount.current) {
didMount.current = true
callback()
}
})
}
It is worth noting that solution 5 should only really be used if none of the other solutions work for your use case. If you do decide you need solution 5 then I recommend using this pre-made hook use-did-mount.
Source (With more detail): Using componentDidMount in react hooks
There's no componentDidMount on functional components, but React Hooks provide a way you can emulate the behavior by using the useEffect hook.
Pass an empty array as the second argument to useEffect() to run only the callback on mount only.
Please read the documentation on useEffect.
function ComponentDidMount() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log('componentDidMount');
}, []);
return (
<div>
<p>componentDidMount: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
ReactDOM.render(
<div>
<ComponentDidMount />
</div>,
document.querySelector("#app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
useEffect() hook allows us to achieve the functionality of componentDidMount, componentDidUpdate componentWillUnMount functionalities.
Different syntaxes of useEffect() allows to achieve each of the above methods.
i) componentDidMount
useEffect(() => {
//code here
}, []);
ii) componentDidUpdate
useEffect(() => {
//code here
}, [x,y,z]);
//where x,y,z are state variables on whose update, this method should get triggered
iii) componentDidUnmount
useEffect(() => {
//code here
return function() {
//code to be run during unmount phase
}
}, []);
You can check the official react site for more info. Official React Page on Hooks
Although accepted answer works, it is not recommended. When you have more than one state and you use it with useEffect, it will give you warning about adding it to dependency array or not using it at all.
It sometimes causes the problem which might give you unpredictable output. So I suggest that you take a little effort to rewrite your function as class. There are very little changes, and you can have some components as class and some as function. You're not obligated to use only one convention.
Take this for example
function App() {
const [appointments, setAppointments] = useState([]);
const [aptId, setAptId] = useState(1);
useEffect(() => {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = aptId;
console.log(aptId);
setAptId(aptId + 1);
return item;
})
setAppointments(apts);
});
}, []);
return(...);
}
and
class App extends Component {
constructor() {
super();
this.state = {
appointments: [],
aptId: 1,
}
}
componentDidMount() {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = this.state.aptId;
this.setState({aptId: this.state.aptId + 1});
console.log(this.state.aptId);
return item;
});
this.setState({appointments: apts});
});
}
render(...);
}
This is only for example. so lets not talk about best practices or potential issues with the code. Both of this has same logic but the later only works as expected. You might get componentDidMount functionality with useEffect running for this time, but as your app grows, there are chances that you MAY face some issues. So, rather than rewriting at that phase, it's better to do this at early stage.
Besides, OOP is not that bad, if Procedure-Oriented Programming was enough, we would never have had Object-Oriented Programming. It's painful sometimes, but better (technically. personal issues aside).
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Please visit this official docs. Very easy to understand the latest way.
https://reactjs.org/docs/hooks-effect.html
Info about async functions inside the hook:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside:
useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or state
useLayoutEffect hook is the best alternative to ComponentDidMount in React Hooks.
useLayoutEffect hook executes before Rendering UI and useEffect hook executes after rendering UI. Use it depend on your needs.
Sample Code:
import { useLayoutEffect, useEffect } from "react";
export default function App() {
useEffect(() => {
console.log("useEffect Statements");
}, []);
useLayoutEffect(() => {
console.log("useLayoutEffect Statements");
}, []);
return (
<div>
<h1>Hello Guys</h1>
</div>
);
}
Yes, there is a way to SIMULATE a componentDidMount in a React functional component
DISCLAIMER: The real problem here is that you need to change from "component life cycle mindset" to a "mindset of useEffect"
A React component is still a javascript function, so, if you want something to be executed BEFORE some other thing you must simply need to execute it first from top to bottom, if you think about it a function it's still a funtion like for example:
const myFunction = () => console.log('a')
const mySecondFunction = () => console.log('b)
mySecondFunction()
myFunction()
/* Result:
'b'
'a'
*/
That is really simple isn't it?
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be mapped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
And in this specific case it's true. But what happens if I do something like that:
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be maped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
This "cleverFunction" we are defining it's not the same in every re-render of the component.
This lead to some nasty bugs and, in some cases to unnecessary re-renders of components or infinite re-render loops.
The real problem with that is that a React functional component is a function that "executes itself" several times depending on your state thanks to the useEffect hook (among others).
In short useEffect it's a hook designed specifically to synchronize your data with whatever you are seeing on the screen. If your data changes, your useEffect hook needs to be aware of that, always. That includes your methods, for that it's the array dependencies.
Leaving that undefined leaves you open to hard-to-find bugs.
Because of that it's important to know how this work, and what you can do to get what you want in the "react" way.
const initialState = {
count: 0,
step: 1,
done: false
};
function reducer(state, action) {
const { count, step } = state;
if (action.type === 'doSomething') {
if(state.done === true) return state;
return { ...state, count: state.count + state.step, state.done:true };
} else if (action.type === 'step') {
return { ...state, step: action.step };
} else {
throw new Error();
}
}
const MyComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
dispatch({ type: 'doSomething' });
}, [dispatch]);
return (
<div>
<h1>Hi!</h1>
</div>
)}
useReducer's dispatch method it's static so it means it will be the same method no matter the amount of times your component is re-rendered. So if you want to execute something just once and you want it rigth after the component is mounted, you can do something like the above example. This is a declarative way of do it right.
Source: The Complete Guide to useEffect - By Dan Abramov
That being said if you like to experiment with things and want to know how to do it "the imperative wat" you can use a useRef() with a counter or a boolean to check if that ref stores a defined reference or not, this is an imperative approach and it's recommended to avoid it if you're not familiar with what happen with react behind curtains.
That is because useRef() is a hook that saves the argument passed to it regardless of the amount of renders (I am keeping it simple because it's not the focus of the problem here, you can read this amazing article about useRef ). So it's the best approach to known when the first render of the component happened.
I leave an example showing 3 different ways of synchronise an "outside" effect (like an external function) with the "inner" component state.
You can run this snippet right here to see the logs and understand when these 3 functions are executed.
const { useRef, useState, useEffect, useCallback } = React
// External functions outside react component (like a data fetch)
function renderOnce(count) {
console.log(`renderOnce: I executed ${count} times because my default state is: undefined by default!`);
}
function renderOnFirstReRender(count) {
console.log(`renderOnUpdate: I executed just ${count} times!`);
}
function renderOnEveryUpdate(count) {
console.log(`renderOnEveryUpdate: I executed ${count ? count + 1 : 1} times!`);
}
const MyComponent = () => {
const [count, setCount] = useState(undefined);
const mounted = useRef(0);
// useCallback is used just to avoid warnings in console.log
const renderOnEveryUpdateCallBack = useCallback(count => {
renderOnEveryUpdate(count);
}, []);
if (mounted.current === 0) {
renderOnce(count);
}
if (mounted.current === 1) renderOnFirstReRender(count);
useEffect(() => {
mounted.current = mounted.current + 1;
renderOnEveryUpdateCallBack(count);
}, [count, renderOnEveryUpdateCallBack]);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(prevState => (prevState ? prevState + 1 : 1))}>TouchMe</button>
</div>
);
};
class App extends React.Component {
render() {
return (
<div>
<h1>hI!</h1>
</div>
);
}
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<MyComponent/>
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
If you execute it you will see something like this:
You want to use useEffect(), which, depending on how you use the function, can act just like componentDidMount().
Eg. you could use a custom loaded state property which is initially set to false, and switch it to true on render, and only fire the effect when this value changes.
Documentation
the exact equivalent hook for componentDidMount() is
useEffect(()=>{},[]);
hope this helpful :)

How to properly implement "subscription"-like fetches with React useEffect

I have a question about the "proper" (or most idiomatic) way to implement network fetch behavior in React based on a single changing property.
A simplified example of the functionality I'm building is below: I am looking to build a multi-page form that "auto-saves" a draft of form inputs as the user navigates back/forth between pages.
TL;DR - I thought useEffect hooks would be the right way to save a draft to the backend every time a url slug prop changes, but I'm running into issues, and wondering about suggestions for the "right" tool for this type of behavior.
Here is my attempt so far. My code is technically working how I want it to, but violates React's recommended hook dependency pattern (and breaks the exhaustive-deps ESLint rule).
import React from 'react';
const ALL_SLUGS = [
'apple',
'banana',
'coconut',
];
function randomUrlSlug() {
return ALL_SLUGS[Math.floor((Math.random() * ALL_SLUGS.length))];
}
// just resovles the same object passed in
const dummySaveDraftToBackend = (input) => {
return new Promise((resolve, _reject) => {
setTimeout(() => {
resolve(input);
}, 1000);
});
};
export function App() {
const [urlSlug, setUrlSlug] = React.useState(randomUrlSlug());
return (
<MyComponent urlSlug={urlSlug} setUrlSlug={setUrlSlug} />
);
}
export function MyComponent({ urlSlug, setUrlSlug }) {
const [loading, setLoading] = React.useState(false);
const [complexState, setComplexState] = React.useState({ foo: 'bar', baz: 'wow', responseCount: 0 });
// useCallback memoization is technically unnecessary as written here,
// but if i follow the linter's advice (listing handleSave as a dependency of the useEffect below), it also suggests memoizing here.
// However, complexState is also technically a dependency of this callback memo, which causes the fetch to trigger every time state changes.
//
// Similarly, moving all of this inside the effect hook, makes the hook dependent on `complexState`, which means the call to the backend happens every time a user changes input data.
const handleSave = React.useCallback(() => {
console.log('*** : start fetch');
setLoading(true);
dummySaveDraftToBackend(complexState).then((resp) => {
console.log('fetch response: ', resp);
// to keep this example simple, here we are just updating
// a dummy "responseCount", but in the actual implementation,
// I'm using a state reducer, and want to make some updates to form state based on error handling, backend validation, etc.
setComplexState((s) => ({
...resp,
responseCount: s.responseCount + 1,
}));
setLoading(false);
});
}, [complexState]);
// I know this triggers on mount and am aware of strategies to prevent that.
// Just leaving that behavior as-is for the simplified example.
React.useEffect(() => {
if (urlSlug) {
handleSave();
}
}, [urlSlug]); // <- React wants me to also include my memoized handleSave function here, whose reference changes every time state changes. If I include it, the fetch fires every time state changes.
return (
<div className="App">
<h2>the current slug is:</h2>
<h3>{urlSlug}</h3>
<div>the current state is:</div>
<pre>{JSON.stringify(complexState, null, 2)}</pre>
<div>
<h2>edit foo</h2>
<input value={complexState.foo} onChange={(e) => setComplexState((s) => ({ ...s, foo: e.target.value }))} disabled={loading} />
</div>
<div>
<h2>edit baz</h2>
<input value={complexState.baz} onChange={(e) => setComplexState((s) => ({ ...s, baz: e.target.value }))} disabled={loading} />
</div>
<div>
<button
type="button"
onClick={() => setUrlSlug(randomUrlSlug())}
disabled={loading}
>
click to change to a random URL slug
</button>
</div>
</div>
);
}
As written, this does what I want it to do, but I had to omit my handleSave function as a dependency of my useEffect to get it to work. If I list handleSave as a dependency, the hook then relies on complexState, which changes (and thus fires the effect) every time the user modifies input.
I'm concerned about violating React's guidance for not including dependencies. As-is, I would also need to manually prevent the effect from running on mount. But because of the warning, I'm wondering if I should not use a useEffect pattern for this, and if there's a better way.
I believe I could also manually read/write state to a ref to accomplish this, but haven't explored that in much depth yet. I have also explored using event listeners on browser popstate events, which is leading me down another rabbit hole of bugginess.
I know that useEffect hooks are typically intended to be used for side effects based on event behavior (e.g. trigger a fetch on a button click). In my use case however, I can't rely solely on user interactions with elements on the page, since I also want to trigger autosave behavior when the user navigates with their browser back/forward controls (I'm using react-router; current version of react-router has hooks for this behavior, but I'm unfortunately locked in to an old version for the project I'm working on).
Through this process, I realized my understanding might be a bit off on proper usage of hook dependencies, and would love some clarity on what the pitfalls of this current implementation could be. Specifically:
In my snippet above, could somebody clarify to me why ignoring the ESLint rule could be "bad"? Specifically, why might ignoring a dependency on some complex state can be problematic, especially since I dont want to trigger an effect when that state changes?
Is there a better pattern I could use here - instead of relying on a useEffect hook - that is more idiomatic? I basically want to implement a subscriber pattern, i.e. "do something every time a prop changes, and ONLY when that prop changes"
If all the "state" that is updated after saving it to backend is only a call count, declare this as a separate chunk of state. This eliminates creating a render loop on complexState.
Use a React ref to cache the current state value and reference the ref in the useEffect callback. This is to separate the concerns of updating the local form state from the action of saving it in the backend on a different schedule.
Ideally each useState hook's "state" should be closely related properties/values. The complexState appears to be your form data that is being saved in the backend while the responseCount is completely unrelated to the actual form data, but rather it is related to how many times the data has been synchronized.
Example:
export function MyComponent({ urlSlug, setUrlSlug }) {
const [loading, setLoading] = React.useState(false);
const [complexState, setComplexState] = React.useState({ foo: 'bar', baz: 'wow' });
const [responseCount, setResponseCount] = React.useState(0);
const complexStateRef = React.useRef();
React.useEffect(() => {
complexStateRef.current = complexState;
}, [complexState]);
React.useEffect(() => {
const handleSave = async (complexState) => {
console.log('*** : start fetch');
setLoading(true);
try {
const resp = await dummySaveDraftToBackend(complexState);
console.log('fetch response: ', resp);
setResponseCount(count => count + 1);
} catch(error) {
// handle any rejected Promises, errors, etc...
} finally {
setLoading(false);
}
};
if (urlSlug) {
handleSave(complexStateRef.current);
}
}, [urlSlug]);
return (
...
);
}
This feels like a move in the wrong direction (towards more complexity), but introducing an additional state to determine if the urlSlug has changed seems to work.
export function MyComponent({ urlSlug, setUrlSlug }) {
const [slug, setSlug] = React.useState(urlSlug);
const [loading, setLoading] = React.useState(false);
const [complexState, setComplexState] = React.useState({ foo: 'bar', baz: 'wow', responseCount: 0 });
const handleSave = React.useCallback(() => {
if (urlSlug === slug) return // only when slug changes and not on mount
console.log('*** : start fetch');
setLoading(true);
dummyFetch(complexState).then((resp) => {
console.log('fetch response: ', resp);
setComplexState((s) => ({
...resp,
responseCount: s.responseCount + 1,
}));
setLoading(false);
});
}, [complexState, urlSlug, slug]);
React.useEffect(() => {
if (urlSlug) {
handleSave();
setSlug(urlSlug)
}
}, [urlSlug, handleSave]);
Or move handleSave inside the useEffect (with additional slug check)
Updated with better semantics
export function MyComponent({ urlSlug, setUrlSlug }) {
const [autoSave, setAutoSave] = React.useState(false); // false for not on mount
React.useEffect(() => {
setAutoSave(true)
}, [urlSlug])
const [loading, setLoading] = React.useState(false);
const [complexState, setComplexState] = React.useState({ foo: 'bar', baz: 'wow', responseCount: 0 });
React.useEffect(() => {
const handleSave = () => {
if(!autoSave) return
console.log('*** : start fetch');
setLoading(true);
dummyFetch(complexState).then((resp) => {
console.log('fetch response: ', resp);
setComplexState((s) => ({
...resp,
responseCount: s.responseCount + 1,
}));
setLoading(false);
});
}
if (urlSlug) {
handleSave();
setAutoSave(false)
}
}, [autoSave, complexState]);

How to prevent set sate in unmounted component in React?

I received a warning: "Can't perform a React state update on an unmounted component", so I try to determine when my component is unmounted, like below:
function ListStock() {
let mounted = true;
const [data, setData] = useState([]);
const [search, setSearch] = useState();
useEffect(() => {
async function fetchData() {
const {start_date, end_date} = search;
const result = await getDataStock(start_date, end_date);
if (result && mounted) {
setData(result.data); // only set a state when mounted = true
}
}
fetchData();
return () => {
mounted = false; // set false on clean up
}
}, [search])
const handleSearch = () => {
...
setSearch({
start_date: moment().subtract(1, 'month').format('YYYY-MM-DD'),
end_date: moment().format('YYYY-MM-DD')
});
}
return (
<div>
<input type="text" id="keyword">
<input type="button" onlick={handleSearch} value="Search">
{data}
</div>
)
}
By this way, it can resolve that warning message, however it shows another one:
"Assignments to the 'mounted' variable from inside React Hook
useEffect will be lost after each render. To preserve the value over
time, store it in a useRef Hook and keep the mutable value in the
'.current' property. Otherwise, you can move this variable directly
inside useEffect"
When I store the 'mounted' variable in a useRef hook, I cannot search anymore, since the 'mounted' is always set to "false".
My questions are:
Why a clean up code runs when User click a search button? I though it runs only when a component is unmounted?
What is the right way to implement a searching job with a remote api?
Is it fine if I config ESLint to ignore all this kind of warning messages?
Thanks all.
The problem is that you are doing the whole mounted/unmounted thing wrong. Here is a proper implementation:
const mounted = useRef(false);
useEffect(() => {
mounted.current = true;
return () => {
mounted.current = false;
};
}, []); // Notice lack of dependencies
Before I go on, I should probably refer you to the awesome react-use library, which already comes with a useMountedState hook
Now back to your questions
Why a clean up code runs when User click a search button? I though it
runs only when a component is unmounted?
I didn't realize this was a thing until I read the docs:
When exactly does React clean up an effect? React performs the cleanup
when the component unmounts. However, as we learned earlier, effects
run for every render and not just once. This is why React also cleans
up effects from the previous render before running the effects next
time...
So there you have it: The cleanup function is run after every render which happens after state changes, thus when search changes, a re-render is required.
What is the right way to implement a searching job with a remote api?
The way you are doing it is fine, but if you are going to be checking for unmounted state every time, you might as well use the library I mentioned.
Is it fine if I config ESLint to ignore all this kind of warning
messages?
Nah. Just fix it. It is very easy
Instead of using a variable, you need to store mounted = true; in a useRef hook. UseRef can hold values and it won't re-render the page when the value changes.
function ListStock() {
const mounted = useRef(true);
const [data, setData] = useState([]);
const [search, setSearch] = useState();
useEffect(() => {
async function fetchData() {
const {start_date, end_date} = search;
const result = await getDataStock(start_date, end_date);
if (result && mounted,current) {
setData(result.data); // only set a state when mounted = true
}
}
fetchData();
return () => {
mounted.current = false; // set false on clean up
}
}, [search])
const handleSearch = () => {
...
setSearch({
start_date: moment().subtract(1, 'month').format('YYYY-MM-DD'),
end_date: moment().format('YYYY-MM-DD')
});
}
return (
<div>
<input type="text" id="keyword">
<input type="button" onlick={handleSearch} value="Search">
{data}
</div>
)
}
Hopefully, questions 1 and 2 will be solved by the above code. 3rd question, I would say it's better to keep it as it shows what's going wrong.
in this case, put mounted inside useEffect is better,
once search changed, previous request should be cancel,
const [data, setData] = useState([]);
const [search, setSearch] = useState();
useEffect(() => {
let cancel = true;
async function fetchData() {
const {start_date, end_date} = search;
const result = await getDataStock(start_date, end_date);
if (result && !cancel) {
setData(result.data); // only set a state when not canceled
}
}
fetchData();
return () => {
cancel = true; // to cancel setState
}
}, [search])

Logical understanding react hooks, difference between useState and useEffect (or state and lifecycle methods)

I cannot understand the difference between useState and useEffect. Specifically, the difference between state and lifecycle methods. For instance, I have watched tutorials and seen this example for useEffect:
const UseEffectBasics = () => {
const [value, setVal] = useState(0);
const add = () => {
setVal((x) => { return x + 1 })
}
useEffect(() => {
if (value > 0) {
document.title = `Title: ${value}`
}
},[value])
return <>
<h1>{value}</h1>
<button className="btn" onClick={add}>add</button>
</>;
};
When we click the button, the title of the document shows us increasing numbers by one. When I removed the useEffect method and did this instead:
const UseEffectBasics = () => {
const [value, setVal] = useState(0);
document.title = `Title: ${value}`
const add = () => {
setVal((x) => { return x + 1 })
}
return <>
<h1>{value}</h1>
<button className="btn" onClick={add}>add</button>
</>;
};
It worked same as the previous code.
So, how does useEffect actually work? What is the purpose of this method?
P.S. Do not send me links of documentation or basic tutorials, please. I have done my research. I know what I am missing is very simple, but I do not know what is it or where to focus to solve it.
Using useEffect to track stateful variable changes is more efficient - it avoids unnecessary calls by only executing the code in the callback when the value changes, rather than on every render.
In the case of document.title, it doesn't really matter, since that's an inexpensive operation. But if it was runExpensiveFunction, then this approach:
const UseEffectBasics = () => {
const [value, setVal] = useState(0);
runExpensiveOperation(value);
would be problematic, since the expensive operation would run every time the component re-renders. Putting the code inside a useEffect with a [value] dependency array ensures it only runs when needed - when the value changes.
This is especially important when API calls that result from state changes are involved, which is pretty common. You don't want to call the API every time the component re-renders - you only want to call it when you need to request new data, so putting the API call in a useEffect is a better approach than putting the API call in the main component function body.

how to handle useEffect with render props in react

I want to use a render props function with hooks but I am not entirely sure if this is possible.
I have a FetcherComponent that takes a renderprop but I want to use a setState from
export const HierarchyGraph: React.FunctionComponent = () => {
const [rootNode, setRootNode] = useState<HierarchyNode<GraphicalNode> | null>(null);
return (
<Fetcher
url="/hierarchy"
initialData={{}}
render={({ data }: { data: TreeData }) => {
// this will cause infinite recursion
setRootNode(getHierarchy(data));
Should I not use render props in this situation?
You can use a render prop but you have to branch inside the render function of HierarchyGraph to detect whether or not you have to make the call. Otherwise the request is triggered multiple times. Here is a quick example:
const HierarchyGraph = () => {
const [rootNode, setRootNode] = useState(null);
if (!rootNode) {
return (
<Fetcher
url="/hierarchy"
initialData={{}}
render={({ data }) => {
setRootNode(getHierarchy(data));
}}
/>
);
}
return <div>render the data related to rootNode</div>;
};
An alternative solution is to inline the call inside the render function and perform the operation on each render. It depends on the use case but if the operation is cheap it might be simpler. The last alternative is to leverage useEffect rather than the Fetcher component. Its usage would be more suited than the render prop pattern since you can explicitly trigger the call to the API only once.
it causes infinite recursion because of setRootNode causing re-render of HierarchyGraph and this again triggers setRootNode. You need to find a way to stop this state updates when it's not necessary e.g:
export const HierarchyGraph = () => {
const [data, setData] = useState({});
return (
<Fetcher
url="/hierarchy"
initialData={data}
render={({ data: newData }) => {
if(data !== newData) {
setData(newData);
}
}}
/>
);
}

Resources