What is a react callback in scalajs-react? - reactjs

Here it is written:
Included is a type CallbackTo[A] which captures effects designated for
use in React callbacks.
What is a React callback in this context ?

As far as I know, just the normal meaning of callback - a function that is called in response to an event. So for example in a React Component there are many functions like componentDidMount that are called at different stages in the lifecycle of the component. In scalajs-react these are implemented as functions that return a CallbackTo[Unit], which can also be written as Callback due to a type alias. For example when adding a componentDidMount callback to a ReactComponentB, we use def componentDidMount(f: DuringCallbackM[P, S, B, N] => Callback): ReactComponentB[P, S, B, N].
In javascript a component is expected to just immediately run any side-effects of the event, in the componentDidMount function (or the other callbacks). In scalajs-react, the component instead wraps these effects in a Callback, and returns that. This allows for the scalajs-react system to delay actual execution of the code in the Callback - this is done later by calling runNow(). This also means that Callbacks are combined using map, flatMap, >> etc. They will not run unless runNow() is eventually called, so if you are not returning a Callback to some other code to execute later, just creating it will do nothing.
There's much more about Callback in the docs.

Related

Use Case for passing function as an dependency in useEffect in React

I started learning to react and came across the code snippet where the function was passed as a dependency array in useEffect. I want to know the use case where such function is being passed as the dependency and why do we need to pass the function as a dependency?
First: This only makes sense if the code in the useEffect callback uses the function. So let's take that as a baseline. :-)
Fundamentally, you'd do that so the code in the useEffect callback is using the most up-to-date version of the function.
Here are a couple of examples where that would be important:
The function is a prop. Since your code doesn't know why it got a new version of the function, it's important to re-run the effect with the up-to-date version of the function.
The function uses state information it closes over (rather than using the callback form of a state setter). If you didn't re-run the effect with the updated function, the function would use stale state information. (But I wouldn't do it that way. Instead, I'd have the function use the callback form of the state setter.)
There are likely others, but they all boil down to ensuring the effect uses the most recent version of the function.
I am also learning React and this article helped me understand functions in the dependency array of useEffect.
Functions in dependency array of useEffect
A function is an object. It has its own identity like an object. A component re-renders each time on of its state changes. When a component re-renders, the function which is defined inside the component gets a new identity.
I made a bit clear code example here. The example from the article was unclear for me to understand. Use the console tab to see the console logs.
It depends on the use of the useEffect and the definition of the function. Basically, if you put a function inside a useEffect array, each time the function will change, or more accurately, it's reference, your effect will be called again, with the new function reference.
This is good in case you want to always use the latest function, but it can also be tricky. If the passed function is defined inside a component, it means that on every component render the function will be redefined, meaning your effect will be called on each component render. It can be heavy sometimes, depends on what your effect does.
It can still be avoided though, if the component in which the function is defined is using useCallback in order to memoize the function. This way, the function will have its own dependencies array, and will only be redefined (and change reference) when you decide it's needed.

Obeying react-hooks/exhaustive-deps leads to infinite loops and/or lots of useCallback()

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.

Api call in render method of reactjs

I have a code in which an API call is made inside the render method in a react app, is it correct?
The API call is made from a function placed inside a const function which is placed in another file
render method is place for presenting various elements to user and these elements can be used to trigger an api-call for various purposes. For example - checking validity of text entered in input-element, sending form-data async-ly to server, etc.
And this is a nice practice to put the code into various modules or separate files for better management, etc. for ex. call is wrapped inside a function and placed in a different file. Webpack helps in filling all various modules in place.
So it is alright to put api calls in render and calling a function defined inside another file for the same.
It is not correct.
1) If the api call is waiting for the call to complete(await or promise resolve), Then it is essentially blocking render.
2) React might call render function many times[it may or not lead to DOM update], which means you will be making redundant api calls as many times.
It is ok to do so, if it is in response to an event.
So No, just put the api call in componentDidMount or ComponentDidUpdate or if you are using hook, add a useEffect.
Good Luck!
It really depends on the use case.
There is nothing wrong with doing so as long as you aren't calling it everytime it re-renders. I assume you are making the request upon triggering of certain events(such as onClick). If yes, it may be cleaner to encapsulate it within a method.
async doSomething = () => {
// do the rest
// make API request
await getRequest();
}
return <>
<button onClick={() => this.doSomething()}/>
</>;
If the request is to be made only once and when the component is initialised, you should make that request within the componentDidMount lifecycle hook. According to the documentation for componentDidMount,
componentDidMount() is invoked immediately after a component is
mounted (inserted into the tree). Initialization that requires DOM
nodes should go here. If you need to load data from a remote endpoint,
this is a good place to instantiate the network request.
Otherwise, calling it on render() will result in the API request to be triggered everytime there is a re-render.

React useEffect hook not calling mocked function

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.

Should "component" functions in Om be called directly?

As I'm only starting to fully understand, om.core/build and om.next's factory functions return React element objects, which refer to component functions/classes, and the actual component is only instantiated later by React's reconciler. That is, (om.core/build some-component data) doesn't actually call some-component immediately.
However, we often represent simple, "stateless" components as just functions which take props and return a React element. In the (pure) React world, you'd use one of these functions like a component class, as React.createElement(AStatelessComponent, {some: "props"}), or more conveniently in JSX as <AStatelessComponent some="props" />. Those too return a React element which references AStatelessComponent, which won't actually be called until later.
But in Om, when we have a simple component like this (and by "we" I mean me and my team, at least), we call the function directly. Thus,
(render [this]
(om/div {}
(a-stateless-component {:some "data"})))
Here, a-stateless-component is called immediately, and whatever it returns is inserted directly into the div, rather than being substituted later by the React reconciler.
Is there a preferred way to React.createElement in Om? Or is it preferred to just call functions like this directly, even though it skips creating a component instance in the render tree?
In Om, if you want to instantiate a stateless component you need to call js/React.createElement directly.
Why you would want to do that depends:
if you call React.createElement you get a "tracked" instance in React's reconciler
if you don't, you get inlining but the stateless component now cannot be distinguished from its parent in React's render tree.
EDIT: I just realized that om.next/factory is permissive enough that it allows you to instantiate the stateless components you were talking about. So you can achieve what you want both by calling js/React.createElement directly on a function of props, or by calling om.next/factory with that same function as argument.
Here's a working example. The following code:
((om/factory #(dom/div nil "Hello, World")))
results in the following component (in React's devtools):

Resources