I have the following problem. I have a component which needs to call an API when mounted. I do the call in a useCallback like this:
const sendCode = useCallback(() => {
console.log('InsideSendCode');
}, []);
And then I call this function inside of a useEffect like this:
useEffect(() => {
sendCode();
}, [sendCode]);
The thing is that, even by using the useCallback, the message is displayed in the console twice and I've seen that this would be the only option.
I know about the StrictMode, but I don't want to disable it.
If everyone would have an opinion, would be great.
Thanks.
That is standard behavior in Strict mode:
When Strict Mode is on, React mounts components twice (in development only!) to stress-test your Effects.
Initially react was only calling render twice to detect if you have some side effects, but afterwards they also added calling effects twice during initial mount, to make sure you have implemented cleanup functions well. This only applies to strict mode AFAIK.
I suggest you read the link above. You have few options:
if the API call you are making is GET request which simply gets some information, let it be called twice, there is no much harm.
You could use a ref to keep track if it is first mount or not, and then correspondingly make request in useEffect only if this is first mount. Although this approach is not listed in official recommendations, I suppose you can use it as a last resort; it was documented at some point. Dan Abramov also mentioned you could use it as last measure, just they don't encourage it.
disable Strict mode
Related
In the following code from reactjs.org:
useEffect(() => {
function tick() {
// Read latest props at any time
console.log(latestProps.current);
}
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []); // This effect never re-runs
as my brain compiles it, the effect created is a "wait one second then do something", but the setInterval being async itself, it returns immediately then useEffect return its closure callback. React being aware of states changes only and not of actions (it doesn't know what was launched in the useEffect, isn't it? How could he know!), I suppose that it'd fire the closure callback directly on return and then prevent the tick() function to be fired even ounce... but it's not the case. How comes ? How React knows what to wait before firing the closure callback returned by useEffect?
While I don't know exactly what happens in the background, for implementation sake you need only to know that the return callback of a useEffect is only called when the effect is re-ran, or more specifically, after "closing" the previous effect-run and before the new effect running. Depending on the effect's dependencies, it can be on every render, or (as in the example you posted) only when the component is unmounted.
It's useful to think that functional components are just functions, so unless the function (the component) is called again (a re-render or other lifecycle change), the effect is "stopped", there's no magical parallel process. I would risk saying that react checks the hooked effects on a component in pre and post render. Depending on dependencies and the effect's details, it knows whether it should call the return callback or not call the effect at all.
See this sandbox I created where I demo these two most extreme cases: effect on every render, and effect only on mount/unmount. Check the sandbox console to understand the behavior. Try to change the parent's effect dependencies to [count] and see the differences.
PS: when I started using hooks, this article helped me a lot https://overreacted.io/a-complete-guide-to-useeffect/
My app has a userService that exposes a useUserService hook with API functions such as getUser, getUsers, etc. I use a Hook for this because the API calls require information from my Session state, which is provided by a React Context Provider.
Providing the getUsers functions to a useEffect call makes the react-hooks/exhaustive-deps eslint rule unhappy, because it wants the getUsers function listed as a dep - however, listing it as a dep causes the effect to run in an infinite loop, because each time the component is re-rendered, it re-runs the useUserService hook, which recreates the getUsers function.
This can be remedied by wrapping the functions in useCallback, but then the useCallback dependency array runs into a similar lint rule. I figure I must be doing something wrong here, because I can't imagine I'm supposed to wrap every single one of these functions in useCallback().
I've recreated the issue in Codesandbox.
1: Encounter eslint warning: https://codesandbox.io/s/usecallback-lint-part-1-76bcf?file=/src/useFetch.ts
2: Remedy eslint warning by sprinkling useCallback in, leading to another eslint warning: https://codesandbox.io/s/usecallback-lint-part-2-uwhhf?file=/src/App.js
3: Remedy that eslint rule by going deeper: https://codesandbox.io/s/usecallback-lint-part-3-6wfwj?file=/src/apiService.ts
Everything works completely fine if I just ignore the lint warning... but should I?
If you want to keep the exact API and constraints you've already chosen, that is the canonical solution — you need to ensure that everything "all the way down" has useCallback or useMemo around it.
So this is unfortunately correct:
I can't imagine I'm supposed to wrap every single one of these functions in useCallback()
There is a concrete practical reason for it. If something at the very bottom of the chain changes (in your example, it would be the "session state from React's context" you're referring to), you somehow need this change to propagate to the effects. Since otherwise they'd keep using stale context.
However, my general suggestion would be to try to avoid building APIs like this in the first place. In particular, building an API like useFetch that takes an arbitrary function as a callback, and then calling it in effect, poses all sorts of questions. Like what do you want to happen if that function closes over some state that changes? What if the consumer passes an inline function that's always "different"? If you only respected the initial function, would you be OK with the code being buggy when you're getting cond ? fn1 : fn2 as an argument?
So, generally speaking, I would strongly discourage building a helper like this, and instead rethink the API so that you don't need to inject a "way to fetch" into a data fetching function, and instead that data fetching function knows how to fetch by itself.
TLDR: a custom Hook taking a function that is then needed inside of an effect is often unnecessarily generic and leads to complications like this.
For a concrete example of how you could write this differently:
First, write down your API functions at top level. Not as Hooks — just plain top-level functions. Whatever they need (including "session context") should be in their arguments.
// api.js
export function fetchUser(session, userId) {
return axios(...)
}
Create Hooks to get any data they need from the Context.
function useSession() {
return useContext(Session)
}
Compose these things together.
function useFetch(callApi) {
const session = useSession()
useEffect(() => {
callApi(session).then(...)
// ...
}, [callApi, session])
}
And
import * as api from './api'
function MyComponent() {
const data = useFetch(api.fetchUser)
}
Here, api.fetchUser never "changes" so you don't have any useCallback at all.
Edit: I realized I skipped passing the arguments through, like userId. You could add an args array to useFetch that only takes serializable values, and use JSON.stringify(args) in your dependencies. You'd still have to disable the rule but crucially you're complying with the spirit of the rule — dependencies are specified. Which is pretty different from disabling it for functions, which leads to subtle bugs.
which is the best hook for dispatching API calls in a component. Usually I use useMemo for calling the API on the first render, and useEffect if I need extra side effects, is this correct? Becouse sometimes I get the following error:
'''index.js:1 Warning: Cannot update a component (Inscriptions) while rendering a different component (PaySummary). To locate the bad setState() call inside PaySummary, follow the stack trace as described in ...''''
That happens when I route to a component and rapidly change to another one, it doesn't "affect" the general behaivour becouse if i go back to the previous component it renders as expected correctly. So how should I do it?
Calling an API is a side effect and you should be using useEffect, not useMemo
Per the React docs for useEffect:
Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.
Per the React docs for useMemo:
Remember that the function passed to useMemo runs during rendering. Don’t do anything there that you wouldn’t normally do while rendering. For example, side effects belong in useEffect, not useMemo.
Performing those side effects (and modifying state) during rendering or with useMemo is the reason you encounter the errors you mention.
basically I rather to use useEffect in componentDidMount manner, with no dependency like below
useEffect(() => {
// Api call , or redux async action here...
}, [])
for calling api's at component mount state.
most of the time i find my self using useMemo for memoising the data at functional Component render level, for preventing the variable re-creation and persist the created data between renders except the dependency changes.
but for the context of your question, there is a hook called useLayoutEffect which is primarily used for actions to happen before painting the DOM, but as i said basically most of the time in projects i find calling apis in a simple useEffect with no dependencies aka, the did mount of your component, in order to load the required data for component!
A bit late but, while everything mentioned above is completely true; the error
'''index.js:1 Warning: Cannot update a component (Inscriptions) while rendering a different component (PaySummary). To locate the bad setState() call inside PaySummary, follow the stack trace as described in ...''''
Has to do with the fact that the API call is Asynchronous and when you rapidly change the pages, the set state call (for updating the data returned from the API call I assume) is still waiting to be called after the data is returned from the API. So, you have to always clean up your Async functions in useEffect to avoid this error.
I have 2 react functional components like this :
const Test = function (props) {
console.log("rendered");
useEffect(() => {}, []);
return <div>Testing</div>;
}
const Parent = function () {
return <div>Component: <Test /></div>;
}
As we can see above, there is no state or props change still browser console showing "rendered" twice but if i am commenting the useEffect it'll print only once. I tried to google it but didn't find any proper reason of this.
This is due to React.StrictMode.
From the React Docs - Detecting unexpected side effects
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
When building your project for production, this should not happen.
It's because of React.StrictMode. If there's no useEffect (or any statefulness), react might not render it twice in StrictMode because it doesn't need to. StrictMode renders components twice to make sure that everything works properly and there are no deprecated lifecycle methods or other practices that are problematic. It's whole purpose is to make sure that your application will work well with the more stringent requirements for concurrent mode. It only runs these checks in development mode so as not to impact performance in your production builds.
In this CodeSandbox, you can see that the strict-mode renders twice for the same components, while the normal mode doesn't.
I am new to React hooks. I'm using a useEffect() hook in a component, and that hook calls a function, searchForVideos() from my props:
useEffect(() => {
props.searchForVideos();
}, [currentPage]);
That function is mocked in my unit tests using Jest:
const searchForVideos = jest.fn();
So, based on my understanding, useEffect() should run for the first time immediately after component render. I can put a console.log() statement in the useEffect() callback and it does print to the console. However, this expect() statement fails:
const component = mountComponent();
setImmediate(() => {
expect(searchForVideos).toHaveBeenCalled();
});
This is strange, because I've confirmed the hook is running, and if it is running it should call the function. However, that line always fails.
Is there something I should know about to make the mocked functions work well with React hooks?
Update
In response to a comment, I made a change that fixed the problem. I do not understand why this worked, though.
const component = mountComponent();
requestAnimationFrame(() => {
expect(searchForVideos).toHaveBeenCalled();
done();
});
So I just replaced setImmediate() with requestAnimationFrame(), and now everything works. I've never touched requestAnimationFrame() before. My understanding of setImmediate() would be that it basically queues up the callback at the back of the event queue right away, so any other JavaScript tasks in the queue will run before it.
So ideally I'm seeking an explanation about these functions and why this change worked.
As far as your secondary question about why requestAnimationFrame fixed it, see the documentation excerpts below. It just gets into the specific timing of useEffect and useLayoutEffect -- specifically "useEffect is deferred until after the browser has painted". requestAnimationFrame delays your test code in a similar fashion. I would expect that if you changed your code to use useLayoutEffect, the original version of your test would work (but useEffect is the appropriate hook to use for your case).
From https://reactjs.org/docs/hooks-reference.html#timing-of-effects:
Unlike componentDidMount and componentDidUpdate, the function passed
to useEffect fires after layout and paint, during a deferred event.
This makes it suitable for the many common side effects, like setting
up subscriptions and event handlers, because most types of work
shouldn’t block the browser from updating the screen.
However, not all effects can be deferred. For example, a DOM mutation
that is visible to the user must fire synchronously before the next
paint so that the user does not perceive a visual inconsistency. (The
distinction is conceptually similar to passive versus active event
listeners.) For these types of effects, React provides one additional
Hook called useLayoutEffect. It has the same signature as useEffect,
and only differs in when it is fired.
Although useEffect is deferred until after the browser has painted,
it’s guaranteed to fire before any new renders. React will always
flush a previous render’s effects before starting a new update.
From https://reactjs.org/docs/hooks-reference.html#uselayouteffect:
The signature is identical to useEffect, but it fires synchronously
after all DOM mutations. Use this to read layout from the DOM and
synchronously re-render. Updates scheduled inside useLayoutEffect will
be flushed synchronously, before the browser has a chance to paint.
Prefer the standard useEffect when possible to avoid blocking visual
updates.