Can't use hook inside Higher order component - reactjs

I want to use a useState hook inside my HOC, but that prevents the component from being rendered
here is my component
import WithAccessControl from "components/HOC/AccessControl";
function GoalPage(props: any) {
return <div>Who stole my goals?</div>;
}
export default WithAccessControl(GoalPage);
and this is my HOC which the component is passed to:
import React from "react";
const WithAccessControl = (Component: React.FC) => {
debugger;
[state, setState] = React.useState();
return Component;
};
export default WithAccessControl;
When I don't use useState() inside my HOC, It works fine, but after adding so, It just doesn't render without any console errors, and after adding a debugger to the code I noticed that the webpack is throwing an error.
This is how webpack throws the error from debugger :
__webpack_require__.i.push((options) => {
const originalFactory = options.factory;
options.factory = function (moduleObject, moduleExports, webpackRequire) {
__webpack_require__.$Refresh$.setup(options.id);
try {
originalFactory.call(this, moduleObject, moduleExports, webpackRequire);
} finally {
if (typeof Promise !== 'undefined' && moduleObject.exports instanceof Promise) {
options.module.exports = options.module.exports.then(
(result) => {
__webpack_require__.$Refresh$.cleanup(options.id);
return result;
},
(reason) => {
__webpack_require__.$Refresh$.cleanup(options.id);
return Promise.reject(reason);
}
);
} else {
__webpack_require__.$Refresh$.cleanup(options.id)
}
}
};
})
what is causing the error and WHY?

Well you cannot use a hook in normal functions, only in components. Your HOC is not a component, but a wrapper method that returns a component.
What you want would be something like (simple version)
import React from "react";
const WithAccessControl = (Component: React.FC) => {
debugger;
return (props) => {
const [state, setState] = React.useState();
return <Component {...props} />;
}
};
export default WithAccessControl;

Related

How to wrap a function that uses hooks inside a useEffect?

I wrote a function to make an API call. Typically, I'd just wrap it in a useEffect and throw it in the same file that needs it, but I'm trying to write my code a little cleaner. So I did the following.
In my component.js file, I have the following:
import { apiCall } from '../../../framework/api.js';
import { useEffect, useState } from 'react';
export const Table = () => {
const [ resp, setResp ] = useState();
useEffect(() => {
console.log(apiCall());
}, []);
return(
<>
{ resp &&
resp.map(([key, value]) => {
console.log("key: " + key);
return(
<SomeComponent />
);
})
}
</>
);
}
in my api.js file, I have the following:
import axios from 'axios';
import { useState } from 'react';
export const apiCall = () => {
const [ resp, setResp ] = useState();
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
if(resp) return resp;
}
This always returns an error (Invalid hook call. Hook calls can only be called inside the body of a function component.)
If I rewrite my component.js and include the axios call directly inside useEffect instead of calling the function apiCall() from the external file, it obviously works with no problems.
I think I know it has to do with the fact that I'm using hooks in my apiCall function, and wrapping that call in a useEffect in my component.js. However, if I don't wrap it in a useEffect, it'll just run continuously and I don't want that either.
You have to follow the custom hook naming convention for this to be able to work. You can check out the documentation for that here: https://reactjs.org/docs/hooks-custom.html
Anyway, I believe in this case this should work:
import axios from 'axios';
import { useState } from 'react';
export const useApiCall = () => {
const [ resp, setResp ] = useState();
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
if(resp) return resp;
}
And then in component.js, you would call useApiCall()
Usually, we do it like this
export const useApiCall = () => {
const [ resp, setResp ] = useState();
useEffect(() => {
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
}, []);
return resp;
}
and then use it like so
export const Table = () => {
const resp = useApiCall();
return(
<>
{ resp &&
resp.map(([key, value]) => {
console.log("key: " + key);
return(
<SomeComponent />
);
})
}
</>
);
}
The prefix "use" in the function name is important, this is how we define a custom hook.
React Hook "useState" is called in function "apiCall" 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".
You can use following methods.
import { useState } from 'react';
export const ApiCall = () => {
const [state, setState] = useState();
};
or
import { useState } from 'react';
export const useApiCall = () => {
const [state, setState] = useState();
};

useSelector Hook - Invalid hook call. Hooks can only be called inside of the body of a function component

Why i got this error?
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component.
Here's my code in useSession.js:
import { useSelector } from 'react-redux';
export const useSession = () => {
const session = useSelector(state => state.session)
return session
}
And code in Auth.js
import { useSession } from './useSession';
export const getServerSideProps = options => gssp => {
const { signedIn, redirectTo } = options;
return async ctx => {
const session = useSession();
if (signedIn && !session) {
return {
redirect: {
destination: redirectTo || '/login',
permanent: false,
}
}
}
const result = await gssp(ctx);
return {
...result,
props: {
...result.props,
session,
},
}
}
};
You are breaking the rules of hooks. Namely Don’t call Hooks from regular JavaScript functions. You can only use a hook from synchronous render of a react functional component. But here you are calling a hook from a plain javascript function that could be executed at any time.
To fix this you'll have to move the hook to the root level of your component.
For example:
function MyComp() {
const session = useSelector(state => state.session)
return <></>
}
If you want to encapsulate that logic into something reusable, you can make a custom hook.
export const useSession = () => { // note the name starts with `use`.
const session = useSelector(state => state.session)
return session
}
Which then must obey the rules of hooks itself:
function MyComp() {
const session = useSession()
return <></>
}

Passing useState Hook into another function on React

I want to pass in a boolean value in a useState hook that opens and closes a modal on click between two functions. However, I keep getting this error message: Cannot destructure property 'setOpenModal' of 'props' as it is undefined.
Main.js
import React, { useState, useEffect } from "react";
import * as Materials from "../components/Materials"; // <- Material.js
const Main = () => {
const [openModal, setOpenModal] = useState(false); //<- Opens (True) and Closes (False) Modal
const { MaterialContainer } = Materials.MaterialTable(); // <-Calling Function Under MaterialTable
return (
<MaterialContainer
openModal={openModal}
setOpenModal={setOpenModal}
/>
// This is how I am passing in Open/Close useState.
}
Material.js
export const MaterialTable = (props) => {
const { openModal, setOpenModal } = props; // <- Pointed in Error Message.
const openMaterialModal = (item) => {
console.log("Button Clicked");
setOpenModal(true); // <- Where I am passing in a true statement.
};
const MaterialContainer = () => (
<>
<Table>Stuff</Table>
</>
);
return {
MaterialContainer
}
}
Thanks in advance.
The MaterialTable component is entirely malformed from a React perspective, though valid JavaScript. It's just a normal function that defines a couple of constants and then returns nothing. (Well, in the original question it returned nothing. Now it returns an object.)
And when you call that function you indeed don't pass anything to it:
const { MaterialContainer } = Materials.MaterialTable();
So the props will be undefined.
Make MaterialTable itself a React component:
export const MaterialTable = (props) => {
// destructure the props passed to the component
const { openModal, setOpenModal } = props;
// a function I assume you plan to use in the JSX below later?
const openMaterialModal = (item) => {
console.log("Button Clicked");
setOpenModal(true);
};
// the rendering of the component
return (
<>
<Table>Stuff</Table>
</>
);
}
Then just import and use that component, without trying to destructure anything from it or invoke it manually:
import React, { useState, useEffect } from "react";
// import the component
import { MaterialTable } from "../components/Materials";
const Main = () => {
// use the state hook
const [openModal, setOpenModal] = useState(false);
// render the component, passing props
return (
<MaterialTable
openModal={openModal}
setOpenModal={setOpenModal}
/>
);
}

Is there a way that I can use a custom hook in a class-based component?

I have this hook:
import { useEffect, useState } from 'react';
import { auth } from "../firebase/auth-service"
const useFirebaseAuthentication = () => {
const [authUser, setAuthUser] = useState(null);
useEffect(() => {
const unlisten = auth.onAuthStateChanged(
authUser => {
authUser
? setAuthUser(authUser)
: setAuthUser(null);
},
);
return () => {
unlisten();
}
});
return authUser
}
export default useFirebaseAuthentication;
That I use like this:
const authUser = useFirebaseAuthentication();
Is there a way I could use this hook useFirebaseAuthentication() in a class-based component?
Class based components directly donot support Hooks.
Read React-FAQ
You can create a Higher Order Component, like this:
import React from 'react';
import { useFirebaseAuthentication} from '../hooks/useFirebaseAuthentication';
export const withFireBaseAuthHookHOC = (Component: any) => {
return (props: any) => {
const authUser = useFirebaseAuthentication();
return <Component authUser ={authUser} {...props} />;
};
};
Now just wrap your class based component with this HOC.
.

React function components naming issue

When my component name is WithErrorHandler I get the following error:
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.
But when I change it to withErrorHandler it works fine. (first letter is lower cased)
Can someone please explain what am I doing wrong here?
import React, { useState, useEffect } from 'react';
import Modal from '../../components/UI/Modal/Modal';
import WrapperComponent from '../WrapperComponent/WrapperComponent';
const WithErrorHandler = (WrappedComponent, axios) => {
return props => {
const [error, setError] = useState(null);
const reqInterceptor = axios.interceptors.request.use(req => {
setError(null);
return req;
});
const resInterceptor = axios.interceptors.response.use(res => res, error => {
setError(error);
});
useEffect(() => {
return () => {
axios.interceptors.request.eject(reqInterceptor);
axios.interceptors.response.eject(resInterceptor);
};
}, [reqInterceptor, resInterceptor]);
const closeModalHandler = () => setError(null);
return (
<WrapperComponent>
<Modal show={error} hide={closeModalHandler}>
{error ? error.message : null}
</Modal>
<WrappedComponent {...props} />
</WrapperComponent>
)
}
}
export default WithErrorHandler;
It looks like it's tripping the safeguard about hooks even though it's still valid code.
Just name it withErrorHandler as it's not a component, it's a function returning a component, known as an Higher-Order Component (HOC).
You could also give a name to the returned component.
// Use camelCase for the HOC function.
const withErrorHandler = (WrappedComponent, axios) => {
// Use PascalCase for the name of the component itself (optional but encouraged).
return function WithErrorHandler(props) => {
// This hook is at the right place already!
const [error, setError] = useState(null);
// ...
return /*...*/
}
}
export default withErrorHandler;

Resources