Using hooks in nested functions - reactjs

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

Related

React Hook "useEffect" cannot be called inside a callback error occurs

The error below sometimes occurs I don't know why. Every code seems to work but only an error occurs.
React Hook "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
Search for the keywords to learn more about each error.
import { useEffect, useState } from 'react'
import { useAuthContext } from 'src/contexts/Auth'
import { brandRepository } from 'src/repositories/BrandRepository'
export default function TestBrand() {
const [brands, setBrands] = useState<any[]>()
const { currentUser } = useAuthContext()
useEffect(() => {
if(!currentUser) return
useEffect(() => {
brandRepository.list(currentUser.id).then(async (docs: any[]) => {
if(!docs) return
await setBrands(docs)
})
}, [])
}, [currentUser])
if(!currentUser) {
return <div>Loading...</div>
}
return (
<div>test</div>
)
}
You cant call useEffect or other hooks inside a function or CB you can only call hooks inside Function component
https://reactjs.org/docs/hooks-rules.html
Blockquote
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
You cannot use useEffect inside another useEffect. Try using both the hooks separately as it forbids the rules of hooks. Better don't use the second useEffect as I do not see any specific use of it. Refer this https://reactjs.org/docs/hooks-rules.html
It seems that in useEffect you are making a network request for one time if the currentUser exists. In this case you don't need 2 useEffect. if can do it like this
useEffect(() => {
if(!currentUser) return
brandRepository.list(currentUser.id).then(async (docs: any[]) => {
if(!docs) return
await setBrands(docs)
})
}, [currentUser])

React Hook "useState" is called in function that is neither a React function | Typescript

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

replacing componentDidMount with useEffect

I was going through React Hooks docs and it mentioned
If you’re familiar with React class lifecycle methods, you can think
of useEffect Hook as componentDidMount, componentDidUpdate, and
componentWillUnmount combined.
Suppose I have a class component right now where in componentDidMount I am doing something like this
componentDidMount() {
MapboxGL.setTelemetryEnabled(false);
}
As far as I can recall, Component did mount is only called once in lifecycle?
If I were to use react hooks then it would be something like this
useEffect(() => {
MapboxGL.setTelemetryEnabled(false);
});
This would call my function everytime state changes in react functional hooks component? Wouldn't it be redundant to call MapboxGL.setTelemetryEnabled(false); to call this everytime? when you only want to do it once component have mounted?
React docs have showed how useEffect can replace multiple lifecycle methods but I am still unable to comprehend how react hooks can replace componentDidMount?
Also, Just a side note question, Can you make a full fledge app using hooks (something like foursquare or instagram?)
You need to add a dependency array for it to know when to recall this hook. An empty dep array will only call it once aka "on mount". And if you don't provide a dep array then it will just be called on every re-render.
useEffect(() => {
MapboxGL.setTelemetryEnabled(false);
}, []);
You can create a flag and if the flag is false/true then only perform that action. something as simple as this
useEffect(() => {
if (something) {
MapboxGL.setTelemetryEnabled(false);
setSomething(false)
}
});
or if you only need hook once, you can do what Matt have suggested
useEffect(() => {
MapboxGL.setTelemetryEnabled(false);
}, []);

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

GraphQL HOC messes with ref to children | React

I am using React and Apollo for my project.
I got this in my component MenuModal:
onClick = () => {
console.log(this.child)
this.child.onSubmit(); // do stuff
};
render() {
return (
<Modal.Content scrolling>
<MenuEdit
ref={ref => (this.child = ref)} // Using ref to access it's properties
selectedValues={selectedValues}
match={match}
menu={menu}
/>
My component MenuEdit has a function defined in class:
onSubmit = values => {
console.log('calling inner form submit', values);
if (this.child) {
this.child.submitFromOutside();
}
};
I should be able to call onSubmit from MenuModal right?
But I am currently getting this:
And when I console.log this.child in my onClick function I can see this:
So there's no onSubmit function there. When seeing GraphQL I wondered if it had something to do with me exporting the component with the graphQL HOC.
export default compose(
graphql(UPDATE_MENU, { name: 'updateMenu' }),
withApollo,
withRouter
)(MenuEdit);
And when I changed it just to:
export default MenuEdit;
I can see my function
So I wonder how I write my export so I still can access my function in my child. Thanks.
The HOC wrapps your component into another component. You can see this in the React devtools. You will see that the component renders a component around your wrapped component. Something like this
<Apollo(ChildComponent)>
<ChildComponent />
</Apollo>
Your ref then points to the Apollo(ChildComponen) element instance.
What you are doing here looks like an antipattern to me. In React we usually don't call functions on rendered elements (except sometimes DOM elements). The idea is rather that children call functions of their parents by receiving them as properties. The best thing in your case is to get rid of the reference and move your state and actions up the component chain. Alternatively you can use the new render prop style in react-apollo.
There was a contribution to the Apollo repository to address this issue...
https://github.com/apollographql/react-apollo/pull/410
Wrapping your component export like this withApollo(Component, { withRef: true }) will expose your child methods. Accessible using ref.current.wrappedInstance.

Resources