I've created a a higher order component that is supposed to add some additional functionality to my components. However, when I use react hooks in this component, I get the following eslint warning.
React Hook "React.useEffect" cannot be called inside a callback. React
Hooks must be called in a React function component or a custom React
Hook function. (react-hooks/rules-of-hooks)
Why am I getting this warning? Is it considered bad practice to use hooks in a HoC?
Minimal example:
const Hello = props => <p>Greetings {props.name}</p>;
const Wrapper = Component => props => {
React.useEffect(() => {
// Do something here
}, []);
return <Component {...props} />;
};
export default Wrapper(Hello)
codesandbox:
https://codesandbox.io/s/proud-tree-5kscc
Convert the
props => {
React.useEffect(() => {
// Do something here
}, []);
return <Component {...props} />;
};
inside your HOC to a function (react-hooks/rules-of-hooks is throwing that warning you showed when used in an arrow function returned by a HOC)
So, change it to
const Wrapper = Component =>
function Comp(props) {
React.useEffect(() => {
console.log("useEffect");
}, []);
return <Component {...props} />;
};
and the effect gets triggered.
Here is a working example on codesandbox
The official React Hooks documentation says:
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks.
As #AsafAviv said, you should refactor your HOC into a custom hook to avoid violation the Rules of Hooks.
The reason is described in the FAQ by the way:
How does React associate Hook calls with components?
React keeps track of the currently rendering component. Thanks to the Rules of Hooks, we know that Hooks are only called from React components (or custom Hooks — which are also only called from React components).
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state.
You can use react hooks in the functional components or in the custom Hooks.
rewrite your HOC:
const Hello = props => <p>Greetings {props.name}</p>;
const HookDoSomething = () => {
React.useEffect(() => {
// Do something here
}, []);
}
const Wrapper = Component => props => {
HookDoSomething()
return <Component {...props} />;
};
export default Wrapper(Hello)
Inside the file where you have your HoC defined, simply add the following to the top of the file:
/* eslint-disable react-hooks/rules-of-hooks */
Hooks and higher-order components are two completely different things. Anyone who says a HoC can be replaced by a hook has either never actually written a HoC or playing semantics games.
When I write a HoC, I often have to disable the rules-of-hooks eslint rule because the rule is too stringent wrt what it thinks is a hook or component. HoC is more akin to a component than a hook, but the rule does not recognize this.
Short Answer: You just need to change the callback to a PascalCase function or useSomething function. This is because eslint rule has some heuristic that you need to follow in order for it to detect components.
You will need to change your code to
const Wrapper = Component => {
return function WithWrapper(props){
React.useEffect(() => {
// Do something here
}, []);
return <Component {...props} />;
}
}
just change the name to lower case
like :
withWrrapperHOC = Comp => props => {
useEffect(()=>{
//...
},[])
return (<Comp {...props}>)
}
Related
In this project, I'm using react leaflet to show a map which generated by GEOJSON features object. I just to go to a dynamic country route when clicking on each of the country layer.
const geoJsonRef = useRef();
useEffect(()=>{
geoJsonRef.current.getLayers().map((layer)=>{
layer.on({
click : ()=>{
useNavigate( 'go/to/country' ); // here is the error occurs
}
});
});
}, [state]);
return(
<MapContainer style={containerStyle} center={[10, 0]} zoom={2} minZoom={2.4} maxZoom={10}>
<GeoJSON style={contryStyle} data={features} onEachFeature={onEachCountry} ref={geoJsonRef} />
</MapContainer>
);
Error:
React Hook "useNavigate" is called in function "click" that is neither a React
function component nor a custom React Hook function. React component names
must start with an uppercase letter. React Hook names must start with the word "use"
react-hooks/rules-of-hooks
From this error I can understand that, it is incorrect way of using useNavigate(). The same I got when attaching the click handler in the onEachCountry function.
I just need to know the correct way click handler to give the route.
Thank you!
React hooks can only be called from React function components or custom hooks, they cannot be called in callbacks, conditions, and loops as this breaks the Rules of Hooks.
Call the useNavigate hook first in the component body to return the navigate function, then call navigate in any callback/hook/etc...
const navigate = useNavigate();
useEffect(() => {
geoJsonRef
.current
.getLayers()
.map((layer) => {
layer.on({
click: () => {
navigate('go/to/country');
}
});
});
}, [state]);
I'm having the problem with calling useState hook in my component.
Everything is working fine. I can define props on Container as well as on Continer.Element.
But when I'm trying to call Hooks inside Container.Element - I'm getting an error.
const Container: React.FC<Props> & {Element: React.FC<ElementProps>} = () => {
return <Container.Element />
}
Container.Element = () => {
const [state, setState] = useState();
return <div>Some JSX code</div>
}
In your code, Container is a valid React component but not the Container.Element.
When you do Container.Element = () => {};: You are just declaring a js function that return some jsx called Element.
To use react hooks, you have to follow the rules of hooks :D
From the react docs :
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.
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.
If you need to use hook in your example, you will have to use it in the Container component.
const Container: React.FC<Props> & {Element: React.FC<ElementProps>} = () => {
const [state, setState] = useState();
return <Container.Element />
}
Container.Element = () => {
return <div>Some JSX code</div>
}
I have the following component:
const [eventCounter, setEventCounter] = useState(0)
let subsectionRefs = data ? Object.values(data).reduce((acc, event) => {
acc[event.id] = useRef();
return acc;
}, {}) : {};
const setRankChangeFocus = eventID => {
setEventCounter(eventCounter + 1);
window.scrollTo(0, subsectionRefs[eventID].current.offsetTop);
};
return (
<Comp
key={event.id}
onRankChange={(value) => {
setRankChangeFocus(value);
}}
/>
)
Essentially, i want subsectionRefs to re-initalize itself every time setRankChangeFocus(value) is called from the child component Comp.
However, i'm getting the "Rendered more hooks than during the previous render" error when i load the page - why? What am i doing wrong?
You're calling your setEventCounter function from within setRankChangeFocus, which is neither a custom hook nor a proper React function component. See the Rules of Hooks.
You can't use hooks in conditions like you doing with useRef.
Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
In my project, I got rid of classes and I'm just using Hooks. Now that I'm trying to create a HOC, my linter is returning an error for using Hooks inside my curry function. This is the simplified version of my code:
const myCurryFunction = WrappedComponent => props => {
const [state, setState] = React.useState();
return <WrappedComponent {...props} />
}
And the full eslint error is this one:
React Hook "useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.
Any clue? I'll really appreciate any advice
Two choices for you.
Respect the rules of hooks, make changes to your code.
const myCurryFunction = WrappedComponent =>
function Comp(props) {
const [state, setState] = React.useState();
return
}
Turn off the lint for the source file.
I found this github issue:
https://github.com/facebook/react/issues/20531
The HOC pattern should work, but is very specific.
I'm trying to rewrite a React class component into a functional hooks-based component, but i cannot figure out how to do it. The component logic and JSX looks something like this:
export class LeftPanel extends React.Component<ILeftPanelProps, ILeftPanelState> {
const [menuItemsFullList, setMenuItemsFullList] = useState([{links: []}] as any[]);
useEffect(() => {
const { links } = props;
setMenuItemsFullList(links);
}, props.links);
....
return (<>
<SearchBox
onChange={_onSearch}
onClear={_onClearSearchBox}
/>
<NavList
listEntries={[menuItems]}
/>
</>)
Where the function i'm currently rewriting is onClearSearchBox:
private _onClearSearchBox() {
this.setState({ menuItems: { ...this.state.menuItemsFullList } });
}
I tried naively rewriting it using hooks which turned the setState into this:
function onClearSearchBox() {
useEffect(() => setMenuItems(menuItemsFullList));
}
This does not work and i do not know how to restructure the code, as i cannot call hooks inside a non-React component function. Moving it into the React component function as an inner function does not work either.
The error message i'm getting is:
Uncaught Invariant Violation: Invalid hook call. Hooks can only be
called inside of the body of a function component...
I believe my mindset is still stuck to the class-based structure, as i cannot figure out how i would go about and refactoring the LeftPanel. How should i go about refactoring _onClearSearchBox to make it work with hooks?
useEffect is the wrong hook for this, from the docs:
If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
In your example, you need control over when to want to call the code e.g. on a button click. I'd say useCallback would be the most appropriate hook here:
const onClearSearchbox = useCallback(() => {
setMenuItemsFullList(props.items);
}, [props.items]);
...
<SearchBox onClear={onClearSearchBox} ... />