Calling useNavigate('url') inside layer.click() is not working - reactjs

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]);

Related

How to start animation before component is removed in React?

I would like to know how do I trigger an animation before removing a functional React component.
I am using the npm package to manage animations via JS. So I just need to call the function. For example function animate.
import React from "react";
useEffect(() => {
function animateStart() {
console.log('AnimateStart')
}
},[]);
export default function () {
return(
<div className={'component'}/>
)
}
This is how I am triggering the animation when the component appears. I would like to somehow catch the removal and postpone it for the duration of the animation.
At the moment I'm calling the delete animation from the parent component. This forces me to store the animateExit function in another component. It is not comfortable :(
Try this:
useEffect(() => {
function animateStart() {
console.log('AnimateStart')
}
return () => {/* whatever you want to do, it will be called everytime when this component is unmounted*/}
},[]);

What invokves the 2nd function call when using React Hooks?

I wrote the following React exercise which uses no hooks and renders a button.
const Button = ({ onClick }) => <button onClick={onClick}>Do Nothing</button>;
const Base = () => {
const onClickFunction = (() => {
console.log("Creating OnClick Function");
return () => {};
})();
return (
<div className="App">
<h1>Hello</h1>
<Button onClick={onClickFunction} />
</div>
);
};
onClickFunction uses a self-invoking function, so that I can place a console.log to see the following behaviour. In this example, when Base is rendered, the message Creating OnClick Function appears only once đź‘Ť
If I change Base to the following however, adding a hook usage:
const Button = ({ onClick }) => <button onClick={onClick}>Do Nothing</button>;
const Base = () => {
const notUsedRef = React.useRef();
const onClickFunction = (() => {
console.log("Creating OnClick Function");
return () => {};
})();
return (
<div className="App">
<h1>Hello</h1>
<Button onClick={onClickFunction} />
</div>
);
};
You will see the Creating OnClick Function message twice.
This CodeSandbox illustrates what I've been seeing: https://codesandbox.io/s/dawn-forest-99clo?file=/src/App.js
Using React DevTools Profiler, we can see there is no rerender of this component.
Using <React.Profiler, it reports this component also didn't update.
I know that using React.useCallback wouldn't trigger a second invokation, however the question would still stand why we are in the situation Base is called twice.
My question is: why and what is triggering Base to be invoked when there is no need for a rerender.
This is due to the way React implements hooks.
If you invoke any hook, even if you don't use the resulting value, you are telling React to render twice before mounting, even if the props don't change. You can substitute the usage of useRef by useState, useEffect, etc. Try below.
You can also wrap your component with React.memo. Every function defined inside the function is recreated in every render.
https://codesandbox.io/s/elastic-water-y18w0?file=/src/App.js
EDIT: Only happens during development and in components wrapped by React.StrictMode. In the words of gaearon:
It's an intentional feature of the StrictMode. This only happens in
development, and helps find accidental side effects put into the
render phase. We only do this for components with Hooks because those
are more likely to accidentally have side effects in the wrong place.
https://github.com/facebook/react/issues/15074

Is there a way to register event listeners AFTER rendering the map in React-Leaflet?

After rendering my map, I may need to change the function registered to an event. Is there a way to do this?
The current solution I've found involved setting a ref and then setting the "container" object directly, but this seems kind of hackish.
What I currently have:
() => {
const test = useRef(null);
if (test.current)
test.current.container.onclick = e => console.log("clicked");
return <Map ref={test} />
}
Try to add onClick in Map component. react-leaflet has onClick props
<Map onClick={(e) => console.log("clicked")} ref={test} />
OR
Use useEffect hook in your functional component. It will trigger once rendered.
useEffect(() => {
if (test.current)
test.current.container.onclick = e => console.log("clicked");
}, []);
Note: Dont forget to import useEffect.
import React, { useEffect } from 'react';

Using hooks in nested functions

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} ... />

Warning when using react hooks in HoC

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}>)
}

Resources