Is it safe to use hooks in createSelector? - javascript

I have just found out that I can use data hooks in createSelector functions and it works. An example:
// This is a normal hook
const useUserReducer = () => {
const userAccessData = useSelector(state => state?.userAccessData)
return userAccessData
}
// Here I use the hook as first argument!
export const useUserReducerFromCreateSelector = createSelector(useUserReducer, (result) => {
console.log(result) // userAccessData printed correctly
return result
})
Then I use it in my component as a normal hook:
const Component = () => {
const result = useUserReducerFromCreateSelector([])
console.log(result) // userAccessData printed correctly
return (
<>
{JSON.stringify(result)}
</>
)
}
I dont see any documentation about this, so I wonder if its safe to use it. It would help me a lot creating reusable selectors.
(I tested while changing the state at various points in time and I always see the correct state)

It is certainly an abuse if it is working. createSelector is only supposed to be a pure state selector function, so naming the returned selector function like a React hook, i.e. useUserReducerFromCreateSelector, is likely to cause some linter warnings eventually.
The potential issue is that Reselect and createSelector creates memoized selector functions. If the input value to a selector doesn't change, then the selector function returns the previously computed selector value. This means that a selector using a React hook like this potentially conditionally calling a React hook which is a violation of the Rules of Hooks.
Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function,
before any early returns. By following this rule, you ensure that
Hooks are called in the same order each time a component renders.
That’s what allows React to correctly preserve the state of Hooks
between multiple useState and useEffect calls. (If you’re curious,
we’ll explain this in depth below.)
Only Call Hooks from React Functions
Don’t call Hooks from regular JavaScript functions. Instead, you
can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks (we’ll learn about them on the next page).
By following this rule, you ensure that all stateful logic in a
component is clearly visible from its source code.
I don't consider it safe to use any React hook in a selector function like this.
Split out the logic of selecting the state from the useUserReducerFromCreateSelector hook to be used in your selector functions.
Example:
const userAccessData = state => state?.userAccessData || {};
const computedUserAccessData = createSelector(
[userAccessData],
data => {
// logic to compute derived state, etc...
return newUserAccessData;
},
);

I was really intrigued seeing this particular use in the redux-toolkit github repo issues and nobody complaining about it so I decided to ask the same question in the reselect github page.
Here is the response of Mark Erikson (redux maintainer):
No, this is not safe!
You're technically getting away with it because of how you're using
that in a component. But if you were to try to use that selector
outside of a component, it would break.
I'd really recommend sticking with keeping these concepts separate.
Write and name selectors as selectors. Write and name hooks as hooks.
Don't try and mix the two :)
To be clear, the code that you wrote above should run. It's ultimately
"just" composition of functions and calling them in a particular
order.
But given how hooks work, and how selectors work, it's best to keep
those concepts separate when writing the code to avoid confusion.

Related

Do React hooks really have to start with "use"?

Lets' create a very simple hook useDouble that returns the double of a number:
export default function useDouble(nb) {
return nb * 2;
}
The documentation (https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook) reads:
A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks
But if I change useDouble to double, it still works. I even tried to call other hooks from the double hook, and it still works.
Demo: https://codesandbox.io/s/laughing-gareth-usb8g?file=/src/components/WithUseDouble.js
So my question is: is naming hooks useXxxxx just a convention, or can it really break something somehow? And if it can, could you show an example?
Thanks
React hook definition according to React docs:
A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
A perfect definition of a custom hook would be (notice the removal of "use" prefix and "may"):
A custom Hook is a JavaScript function that calls other Hooks.
So we could distinguish between helper functions and custom hooks.
But, we can't tell if certain functions are actually using hooks (we do know in runtime). Thats why we use static code analyzing tools (like eslint) where we analyze text (lexical) and not meaning (semantics).
... This convention is very important. Without it, we wouldn’t be able to automatically check for violations of Rules of Hooks because we couldn’t tell if a certain function contains calls to Hooks inside of it. (source)
Hence:
// #1 a function
// CAN'T BREAK ANYTHING
function double(nb) {
return nb * 2;
}
// #2 Still a function, does not use hooks
// CAN'T BREAK ANYTHING
function useDouble(nb) {
return nb * 2;
}
// #3 a custom hook because hooks are used,
// CAN BREAK, RULES OF HOOKS
function useDouble(nb) {
const [state, setState] = useState(nb);
const doubleState = (n) => setState(n*2);
return [state,doubleState];
}
Is naming hooks useXxxxx just a convention.
Yes, to help static analyzer to warn for errors.
Can it really break something somehow?
Example #2 can't break your application since it just a "helper function" that not violating Rules of Hooks, although there will be a warning.
Could you show an example?
// #2 from above
function useDouble(nb) { return nb * 2; }
// <WithUseDouble/>
function WithUseDouble() {
// A warning violating Rules of Hooks
// but useDouble is actually a "helper function" with "wrong" naming
// WON'T break anything
if (true) {
return <h1>8 times 2 equals {useDouble(8)} (with useDouble hook)</h1>
}
return null;
}
Do I have to name my custom Hooks starting with “use”? Please do. This
convention is very important. Without it, we wouldn’t be able to
automatically check for violations of rules of Hooks because we
couldn’t tell if a certain function contains calls to Hooks inside of
it
From reactjs/docs.
And in large components that use several functions, the "use" prefix also helps in easily identifying if a function is a custom hook.

Should you pass setter functions into the dependency array of a React hook?

Recently I saw a few examples of passing setter functions into hook dependency arrays in my coworkers' React code, and it doesn't look right to me. For example:
const MyComponent = () => {
const [loading, setLoading] = useState(true);
useEffect(() => {
doSomeBigLongNetworkRequest();
setLoading(false);
}, [setLoading, /* other deps */]);
// ...
}
My feeling is that they have misunderstood the purpose of the dependency array, which, as I understand it, is to indicate which pieces of state to monitor so that the hook can fire again when they change, not to simply indicate that the hook needs to use the setLoading function. And since the setLoading function never actually changes, including it in the dependencies does nothing.
Am I correct, or does including the setter in the array make sense somehow? My other thought was that maybe this was just a linter error, since the linter cannot recognize that the function is a setter, and thinks it might change.
I should also add that in the instances I've seen, they've included the setter but not the variable. So in the example above, setLoading, but not loading would be in the dependency array, and the hook does not actually need the value of loading.
Yes, you are right there is no need to include them. Here is quote from docs:
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
In general again based on docs the recommendation about dependency array is:
If you use this optimization, make sure the array includes all values
from the component scope (such as props and state) that change over
time and that are used by the effect. Otherwise, your code will
reference stale values from previous renders.

How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?

I just started using hooks in react and am creating a prototype custom hook for a framework.
The hook should take an object as an argument for initialization and cleanup (setting up/removing callbacks for example).
Here is my simplified Code so far:
export function useManager(InitObj) {
const [manager] = useState(() => new Manager());
useEffect(() => {
manager.addRefs(InitObj)
return () => manager.removeRefs(InitObj)
}, [manager]);
return manager;
}
to be used like this:
useManager({ cb1: setData1, cb2: setData2... })
In future Iterations the Manager might be a shared instance, so I need to be able to be specific about what I remove upon cleanup.
I put console.log all over the place to see If i correctly understand which code will be run during a render call. From what I can tell this code does 100% what I expeted it to do!
Unfortunately (and understandably) I get a warning because I did not include InitObj in the effects dependencies. But since I get an object literal simply putting it in there will cause the effect to be cleaned up/rerun on every render call since {} != {} which would be completely unnecessary.
My research so far only revealed blog posts like this one, but here only primitive data is used that is easily classified as "the same" (1 == 1)
So far I have found 3 possible solutions that I am not completely happy with:
using useMemo to memoize the object literal outside the hook
useManager(useMemo(() => { cb: setData }, []))
This adds more responsibility on the developer using my code => not desirable!
using useState inside the hook
const [iniOBj] = useState(InitObj);
A lot better already, but it adds state that does not feel like state. And it costs (minimal) execution time and memory, I would like to avoid that if possible.
using // eslint-disable-next-line react-hooks/exhaustive-deps
Works for sure, but there might still be other dependencies that might be missed if I simply deactivate the warning.
So my question is:
How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?
I half expect that the useState option will be my best choice, but since I am new to hooks there might still be something that eluded my understanding so far.

React Hooks - What's happening under the hood?

I've been trying out React Hooks and they do seem to simplify things like storing state. However, they seem to do a lot of things by magic and I can't find a good article about how they actually work.
The first thing that seems to be magic is how calling a function like useState() causes a re-render of your functional component each time you call the setXXX method it returns?
How does something like useEffect() fake a componentDidMount when functional components don't even have the ability to run code on Mount/Unmount?
How does useContext() actually get access to the context and how does it even know which component is calling it?
And that doesn't even begin to cover all of the 3rd party hooks that are already springing up like useDataLoader which allows you to use the following...
const { data, error, loading, retry } = useDataLoader(getData, id)
How do data, error, loading and retry re-render your component when they change?
Sorry, lots of questions but I guess most of them can be summed up in one question, which is:
How does the function behind the hook actually get access to the functional/stateless component that is calling it so that it can remember things between re-renders and initiate a re-render with new data?
React hook makes use of hidden state of a component, it's stored inside a fiber, a fiber is an entity that corresponds to component instance (in a broader sense, because functional components don't create instances as class components).
It's React renderer that gives a hook the access to respective context, state, etc. and incidentally, it's React renderer that calls component function. So it can associate component instance with hook functions that are called inside of component function.
This snippet explains how it works:
let currentlyRenderedCompInstance;
const compStates = new Map(); // maps component instances to their states
const compInstances = new Map(); // maps component functions to instances
function useState(initialState) {
if (!compStates.has(currentlyRenderedCompInstance))
compStates.set(currentlyRenderedCompInstance, initialState);
return [
compStates.get(currentlyRenderedCompInstance) // state
val => compStates.set(currentlyRenderedCompInstance, val) // state setter
];
}
function render(comp, props) {
const compInstanceToken = Symbol('Renderer token for ' + comp.name);
if (!compInstances.has(comp))
compInstances.set(comp, new Set());
compInstances.get(comp).add(compInstanceToken);
currentlyRenderedCompInstance = compInstanceToken;
return {
instance: compInstanceToken,
children: comp(props)
};
}
Similarly to how useState can access currently rendered component instance token through currentlyRenderedCompInstance, other built-in hooks can do this as well and maintain state for this component instance.
Dan Abramov created a blog post just a couple days ago that covers this:
https://overreacted.io/how-does-setstate-know-what-to-do/
The second half specifically goes into details regarding hooks like useState.
For those interested in a deep dive into some of the implementation details, I have a related answer here: How do react hooks determine the component that they are for?
I would recommend reading https://eliav2.github.io/how-react-hooks-work/
It includes detailed explanations about what is going on when using react hooks and demonstrate it with many interactive examples.
Note - the article does not explain in technical terms how React schedule calls for later phases, but rather demonstrates what are the rules that react uses to schedule calls for later phases.
The URL in another answer given by Eliav Louski is so far the best React explaination I have come across. This page should replace React's official tutorial as it removes all the magic from hooks and friends.

React functional component reinitialises local functions and variables on re render (React Hooks)

So I have started using React hooks now. I have been experimenting with the API for some time now. i really like the idea of bringing the state to functional components. but there is this one thing which keeps on bothering me and it doesn't feel right in the gut when i am trying to use it. I tried posting on RFCs but it's too crowded there now. everything seems lost there.
Here is a piece of code from my example.
import React, { useState } from "react";
function Counter() {
const [counterState,incrementCounterState] = useCommontState(0);
function doSomething (){
// does something and then calls incrementCounterState
// with the updated state.
}
return (
<div>
<p>{counterState}</p>
<button onClick={incrementCounterState}>increase</button>
....
.... // some jsx calling local scoped functions.
....
</div>
);
}
function useCommontState(defaultValue){
var [state, setState] = useState(0);
function increment(){
setState(defaultValue+=1);
}
return [state, increment]
}
export default Counter;
I can easily take out state and setState methods out and create a custom hook but my problem is with the local functions that are used by the component. since state is now part of the component there will be cases where some logic will decide what to do next with the state.
Also, when the component re-renders on state change everything gets reinitialized. which is my problem. I know that useState has its own way of handling the issue. but my problem is with my own functions. the click handlers. on change events, callbacks for child components etc. all that will be reinitialized everytime the component renders. this doesn't feel right to me.
Are there any ways by which we can work around it. it's a new API. we are not even sure if it will make into react 17. but has anyone come across any better way to do it?
I had the same concerns as well when I first saw the proposal, but this was addressed in the React Docs Hooks Proposal FAQ:
Are Hooks slow because of creating functions in render?
No. In modern browsers, the raw performance of closures compared to classes doesn’t differ significantly except in extreme scenarios.
My takeaway is that although you have additional overhead now in the repeated declarations per render, you have additional wins elsewhere:
Hooks avoid a lot of the overhead that classes require, like the cost of creating class instances and binding event handlers in the constructor.
Idiomatic code using Hooks doesn’t need the deep component tree nesting that is prevalent in codebases that use higher-order components, render props, and context. With smaller component trees, React has less work to do.
Overall the benefits might be more than the downsides which makes hooks worth using.
You can always simplify the code to take functions out so that they aren't initialised always, by passing the required values as constants.
import React, { useState } from "react";
function doSomething (counterState, incrementCounterState){
// does something and then calls incrementCounterState
// with the updated state.
}
function Counter() {
const [counterState,incrementCounterState] = useCommontState(0);
return (
<div>
<p>{counterState}</p>
<button onClick={incrementCounterState}>increase</button>
....
.... // some jsx calling local scoped functions.
....
</div>
);
}
function increment(defaultValue, setState){
setState(defaultValue + 1);
}
function useCommontState(defaultValue){
var [state, setState] = useState(0);
return [state, increment]
}
export default Counter;
Also in my opinion the function design being suggested in all the demos and docs is for people to get comfortable with it and then think about the re-initialization aspects. Also the cost that re-initialization would significanly be overpowered by the other benefits that it provides.
I'm using createOnce helper function to prevent reinitialises, But I'm not sure if it's correct or not.
utils/createOnce.js
import { useMemo } from 'react';
export const createOnce = toCreate => useMemo(() => toCreate, []);
SomeComponent.js
...
const someFunction = createOnce((counter) => {
// whatever
return counter + 1;
});
...

Categories

Resources