what's the difference between components and custom hooks? - reactjs

here is my code
function Label({title}){
return <h1>{title}</h1>
}
function useLabel({title}){
return <h1> {title}</h1>
}
function Diff(){
return <div>
<Label title='this is component'/>
{useLabel({title:'this is custom hook'})}
</div>
}
in the code, I have defined the component Label and the custom hook useLabel. they do exactly the same job except that are called using different syntax.
my questions are:
other than calling syntax, what's is the difference between custom
hooks and Components?
can I always use custom hooks instead of
component?

custom hook useLabel
You've given it a name that's conventionally for hooks, but I recommend you don't call it a custom hook as that may cause confusion. Custom hook typically refers to a function that calls one or more of the built in hooks provided by react, such as useState, or useEffect. Because it does this, it brings along the requirement that it needs to follow the rules of hooks.
For example, the following is a custom hook that gets and returns the window width:
const useWindowWidth = () => {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
}
}, [])
return width;
}
What you've written is a helper function, but it doesn't have to follow the rules of hooks, since it never calls hooks. A helper function that returns a react element can occasionally be useful if you have to do the same thing multiple times, but it's basically no different than just writing a longer Diff component. As far as react knows, it's the same as writing the <h1> in the body of Diff.
So your question boils down to: when should i use one component or multiple components.
Sometimes, that decision will be made for performance reasons. If it's all one component, then it all needs to rerender as a group. But if it's split into separate components, then potentially only a subset of them need to rerender.
Other times, the decision is more about how to make the code understandable to us puny humans. Breaking up components at sensible boundaries can let you mentally group related code together, and then ignore it when you don't need to know the details.

Custom hooks are primarily for the abstraction of stateful logic and state manipulation, so that you can write the hook once and use it in multiple places, or without having to worry about implementation details.
While a custom hook can technically return anything, it's pretty strange for it to return a JSX element, because a component could do the same thing less confusingly. One should want to use a custom hook when wanting to return something other than a JSX element while still working within React's framework, for example:
const isOnline = useFriendStatus(props.friend.id);
can I always use custom hooks instead of component?
It'd technically be possible, but it'd be really confusing. Best to always use components when you're returning JSX.

Related

Keeping track of the second result of useState [duplicate]

Is useState's setter able to change during a component life ?
For instance, let's say we've got a useCallback which will update the state.
If the setter is able to change, it must be set as a dependency for the callback since the callback use it.
const [state, setState] = useState(false);
const callback = useCallback(
() => setState(true),
[setState] // <--
);
The setter function won't change during component life.
From Hooks FAQ:
(The identity of the setCount function is guaranteed to be stable so it’s safe to omit.)
The setter function (setState) returned from useState changes on component re-mount, but either way, the callback will get a new instance.
It's a good practice to add state setter in the dependency array ([setState]) when using custom-hooks. For example, useDispatch of react-redux gets new instance on every render, you may get undesired behavior without:
// Custom hook
import { useDispatch } from "react-redux";
export const CounterComponent = ({ value }) => {
// Always new instance
const dispatch = useDispatch();
// Should be in a callback
const incrementCounter = useCallback(
() => dispatch({ type: "increment-counter" }),
[dispatch]
);
return (
<div>
<span>{value}</span>
// May render unnecessarily due to the changed reference
<MyIncrementButton onIncrement={dispatch} />
// In callback, all fine
<MyIncrementButton onIncrement={incrementCounter} />
</div>
);
};
The short answer is, no, the setter of useState() is not able change, and the React docs explicitly guarantee this and even provide examples proving that the setter can be omitted.
I would suggest that you do not add anything to the dependencies list of your useCallback() unless you know its value can change. Just like you wouldn't add any functions imported from modules or module-level functions, constant expressions defined outside the component, etc. adding those things is just superfluous and makes it harder to read your handlers.
All that being said, this is all very specific to the function that is returned by useState() and there is no reason to extend that line of reasoning to every possible custom hook that may return a function. The reason is that the React docs explicitly guarantee the stable behavior of useState() and its setters, but it does not say that the same must be true for any custom hook.
React hooks are still kind of a new and experimental concept and we need to make sure we encourage each other to make them as readable as possible, and more importantly, to understand what they actually do and why. If we don't it will be seen as evidence that hooks are a "bad idea," which will prohibit adoption and wider understanding of them. That would be bad; in my experience they tend to produce much cleaner alternatives to the class-based components that React is usually associated with, not to mention the fact that they can allow organizational techniques that simply aren't possible with classes.

Performance difference from putting function outside of React component?

I know that it's going to be small, but is there a performance difference if you put functions outside of a React component?
Eg version 1:
const handleFetch = () => {
// Make API call
}
const MyComponent = () => {
useEffect(()=>{
handleFetch();
})
return(<p>hi</p>)
}
Vs version 2:
const MyComponent = () => {
const handleFetch = () => {
// Make API call
}
useEffect(()=>{
handleFetch();
})
return(<p>hi</p>)
}
In version 1 would handleFetch not be recreated when MyComponent re-renders?
is there a performance difference if you put functions outside of a React component?
Yes but you will likely never run into a noticeable performance downgrade of deciding to define a function inside a component. Almost always, the rest of your application's performance will overshadow the cost of putting functions in your components. Quoted from React's FAQ regarding functions defined in functional components in general:
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.
https://reactjs.org/docs/hooks-faq.html#are-hooks-slow-because-of-creating-functions-in-render
In version 1 would handleFetch not be recreated when MyComponent re-renders?
No, it will not be recreated because handleFetch is defined outside of the functional component. It's only in version 2 handleFetch will be recreated when MyComponent re-renders.
Another note: useCallback will not avoid recreating functions (you still pass a new function into useCallback too).
As a general rule for me, define the function inside the component first then extract it if it's not a hassle or it can be reused among multiple components. Sometimes, when I do extract it, I find I need to add 2 or more extra parameters to pass variables from my component to the function. But if I left it inside the functional component, I wouldn't need any parameters.
The approved answer isn't fully correct. There is one really important distinction about this sentence:
Another note: useCallback will not avoid recreating functions (you still pass a new function into useCallback too).
The function you pass to useCallback is returned every render. This means you have the same function, assuming the dependencies you defined haven't changed.
As an exercise to the reader define a function inside of a useCallback and store that result into a variable and then in a useEffect have that variable be the sole dependency. The useEffect will not re-run bc it's the same reference (strict equality.)

React hooks: is `useCallback` not so needed usually?

I am recently refactoring a web app using React Hooks. I encounter a problem regarding useCallback. Based on description of Kent: https://kentcdodds.com/blog/usememo-and-usecallback, useCallback is to pass in identical function reference to sub-components, to avoid re-render of sub-components, so that the performance is better. However, it's used together with React.memo. And as Kent said:
MOST OF THE TIME YOU SHOULD NOT BOTHER OPTIMIZING UNNECESSARY RERENDERS. React is VERY fast and there are so many things I can think of for you to do with your time that would be better than optimizing things like this. In fact, the need to optimize stuff with what I'm about to show you is so rare that I've literally never needed to do it ...
So, my question is: am I right to claim that we do not need to use useCallback usually? except when the callback is expensive to create, using useCallback avoids re-creating the callback for every render.
Say, for a onClick or onChange event handler, 2 lines or less, shall we just not use useCallback to wrap it?
I find the useCallback() is necessary when I don't want the function reference to change. For example, when I'm using React.memo on some child component that should not be re-rendered as a result of a reference change in one of its methods that comes through props.
Example:
In the example below Child1 will always re-render if Parent re-renders, because parentMethod1 will get a new reference on every render. And Child2 will not re-render, because the parentMethod2 will preserve its reference across renders (you can pass a dependency array to make it change and be re-created when new input values come).
Note: Assuming the Child components are being memoized with React.memo()
function Parent() {
const parentMethod1 = () => DO SOMETHING;
const parentMethod2 = useCallback(() => DO SOMETHING,[]);
return(
<React.Fragment>
<Child1
propA=parentMethod1
/>
<Child2
propA=parentMethod2
/>
</React.Fragment>
);
}
On the other hand, if the function is expensive to run, you can memoize its results using the useMemo hook. Then you will only run it when new values come, otherwise it will give you a memoized result from previous calculations using those same values.
https://reactjs.org/docs/hooks-reference.html#usecallback
useCallback
Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
useMemo
Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
I think you are right. From how it's designed, useCallback should be almost useless in React. It can not be used directly to prevent a child render.
What can save a child render is to wrap entire render using a useMemo.
const Title = () => {
...
const child = useMemo(() => {
return <Child a={"Hello World"} />
}, [])
return (
<>
{child}
<div onClick={onClick}>{count}</div>
</>
)
}
The above approach is a bit different than React.memo because it acts directly on the parent Title, instead of Child. But it's more or less answer your question why it's useless, except when you use it as the shortcut for useMemo.
Article explaining this, https://javascript.plainenglish.io/can-usememo-skip-a-child-render-94e61f5ad981
back to useCallback
Now let's go back to see if a callback wrapped with or without useCallback is useful.
<div onClick={onClick}>kk</div>
The only thing it might save is that when it's under reconciliation, onClick (with useCallback) points to the same function instance.
However I don't know if React actually does any optimization at that step. Because assigning a different callback to the attribute might take additional memory and time. But adding a new variable in general takes additional memory as well.
So this type of optimization is more like a coding optimization, more or less subjective. Not objective enough to be applied in a solid case.
Of course, if you want to fix a function instance for any third party function, ex. debounce. That might be a good use, but still smell fishy, because useMemo seems much more versatile to cover this case as well.
All in all, I'm only pointing out, useCallback isn't doing what's the public believe it can do, such as to bailout child component.

useMemo vs useState for React hooks constants

Defining a calculated (initialized) constant using React hooks can be performed in two ways that seem functionally equivalent. I don't want to get into the use cases for this, but suffice to say that there are cases where a constant value can be derived from initial props or state that isn't expected to change (think route data, bound dispatch, etc).
First, useState
const [calculatedConstant] = useState(calculateConstantFactory);
Second, useMemo
const calculatedConstant = useMemo(calculateConstantFactory, []);
Both of these seem functionally equivalent, but without reading the source code, I'm not sure which is better in terms of performance or other considerations.
Has anyone done the leg work on this? Which would you use and why?
Also, I know some people will recoil at the assumption that state can be "considered constant". I'm not sure what to tell you there. But even without state, I may want to define a constant within a component that has no state at all, for example, creating a block of JSX that doesn't change.
I could define this outside of the component, but then it's consuming memory, even when the component in question is not instantiated anywhere in the app. To fix this, I would have to create a memoization function and then manually release the internal memoized state. That's an awful lot of hassle for something hooks give us for free.
Edit: Added examples of the approaches talked about in this discussion.
https://codesandbox.io/s/cranky-platform-2b15l
You may rely on useMemo as a performance optimization, not as a semantic guarantee
Meaning, semantically useMemo is not the correct approach; you are using it for the wrong reason. So even though it works as intended now, you are using it incorrectly and it could lead to unpredictable behavior in the future.
useState is only the correct choice if you don't want your rendering to be blocked while the value is being computed.
If the value isn't needed in the first render of the component, you could use useRef and useEffect together:
const calculatedConstant = useRef(null);
useEffect(() => {
calculatedConstant.current = calculateConstantFactory()
}, [])
// use the value in calcaulatedConstant.current
This is the same as initializing an instance field in componentDidMount. And it doesn't block your layout / paint while the factory function is being run. Performance-wise, I doubt any benchmark would show a significant difference.
The problem is that after initializing the ref, the component won't update to reflect this value (which is the whole purpose of a ref).
If you absolutely need the value to be used on the component's first render, you can do this:
const calculatedConstant = useRef(null);
if (!calculatedConstant.current) {
calculatedConstant.current = calculateConstantFactory();
}
// use the value in calculatedConstant.current;
This one will block your component from rendering before the value is set up.
If you don't want rendering to be blocked, you need useState together with useEffect:
const [calculated, setCalculated] = useState();
useEffect(() => {
setCalculated(calculateConstantFactory())
}, [])
// use the value in calculated
Basically if you need the component to re-render itself: use state. If that's not necessary, use a ref.

Is it better to pass context to a custom hook or call useContext inside the custom hook?

I have a growing number of custom hooks and many of them access the same react context via the useContext hook. In many components there is the need to use more than one of these custom hooks.
Is it better to call useContext once per component and pass the context into my custom hooks or is it better to call useContext within each custom hook? There may not be a right or wrong answer, but by "better" I mean is there a best practice? Does one approach make more sense than the other?
I would suggest passing the context in, personally: I believe that will make the custom hook more clear, more flexible and more testable. It decouples the logic that operates on the context data from the logic responsible for getting that data.
Clarity
If you use useContext inside a custom hook, that becomes an implicit contract for the hook: it's not clear from looking at the call signature that it depends on values from context. Explicit data flow is better than implicit data flow, generally. (Of course, the Context API exists because sometimes implicit data flow is useful, but in most cases I think it's better to be explicit)
Flexibility
You might at some point find a component that needs to leverage the logic contained in the custom hook, but needs to provide a different value than the one on the context, or perhaps wants to modify the value. In this case, it'd be very convenient to do this:
const MySpecialCaseComponent = () => {
const context = useContext(MyContext);
useMyCustomHook({
...context,
propToOverride: someValue
});
return (<div></div>)
}
That's highly inconvenient if the custom hook reads straight from context - you'd probably have to introduce a new component, wrapped in a new context provider.
Testability
It's easier to test a custom hook if it doesn't depend on context API. Perhaps in the simplest cases, you can just call your custom hook with test data and check the return value.
Or you can write a test like:
test("Test my custom hook", () => {
const TestComponent = () => {
useMyCustomHook({ /** test data */ });
return (/* ... */);
};
const element = shallow(<TestComponent />);
// do testing here
})
If you use context in your hook, then you'd have to render your test component inside a <MyContext> provider, and that makes things more complicated. Especially if you're trying to do shallow render (and even more so if you're using react-test-renderer/shallow, testing components with context is more complicated.
TL;DR I don't think it's wrong to useContext inside a custom hook, but I think explicit data flow should be your first resort, for all the usual reasons that explicit data flow is generally preferred over implicit data flow.

Resources