React function components naming issue - reactjs

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;

Related

Can't use hook inside Higher order component

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;

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

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

How do i access Reusable NetInfo code from function component to Class Component

I have created a custom function component which has eventlistener from NetInfo for internet connection, which returns a bool value and method name. I wanted to call the method in every class component to check the internet connect and use the bool value to check the connection status.
Below is my code.
//function Component
import React, { PureComponent, useEffect, useState } from 'react';
import NetInfo from "#react-native-community/netinfo";
export default () => {
const [isInternetReachable, setIsInternetReachable] = useState(false)
const InternetChecker = () => {
useEffect(() => {
// Subscribe
const unsubscribe = NetInfo.addEventListener((state) => {
setIsInternetReachable(state.isInternetReachable);
console.log("Connection type", state.type);
console.log("Is internet Reachable?", isInternetReachable);
});
return () => {
unsubscribe();
};
},[])
}
return [InternetChecker, isInternetReachable];
};
Class Component trying to access InternetChecker() method
class HomeScreen extends React.Component {
render() {
const { navigate } = this.props.navigation;
const [InternetChecker, isInternetReachable] = InternetHanlder();
// if (!isInternetReachable) {
// <NoNetworkSign />
// }
InternetChecker()
if (isInternetReachable){
console.log("Internet is Reachable");
} else {
console.log("No Internet Connection");
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.container}>
</View>
</SafeAreaView>
)
};
When i try to access in the above way, i'm getting invalid hook call, Hooks can only be called from inside the body of a function component. How can we call it from a Class Component.
Any help appreciated.
Add a name to the function
import React, { PureComponent, useEffect, useState } from 'react';
import NetInfo from "#react-native-community/netinfo";
export default ConnectivityChecker = () => {
const [isInternetReachable, setIsInternetReachable] = useState(false)
const InternetChecker = () => {
useEffect(() => {
// Subscribe
const unsubscribe = NetInfo.addEventListener((state) => {
setIsInternetReachable(state.isInternetReachable);
console.log("Connection type", state.type);
console.log("Is internet Reachable?", isInternetReachable);
});
return () => {
unsubscribe();
};
},[])
}
return [InternetChecker, isInternetReachable];
};
where you want to use this function
you should import it
First you should read the Hooks Rules, The first rule is do not call a react hook inside a class based Component, Thus simply update the HomeScreen component as a FC Component then try again it will surly Work.
And your custom hooks should be like this:
export const useInternetStatus = () => {// I make a name to your hook is best in inspection and testing
const [isInternetReachable, setIsInternetReachable] = useState(false)
const InternetChecker = () => {
useEffect(() => {
// Subscribe
const unsubscribe = NetInfo.addEventListener((state) => {
setIsInternetReachable(state.isInternetReachable);
console.log("Connection type", state.type);
console.log("Is internet Reachable?", isInternetReachable);
});
return () => {
unsubscribe();
};
},[isInternetReachable]) // in order to re-call the hooks whenever the netInfo status changed
}
return [InternetChecker, isInternetReachable];
};
The function you have shown is not a functional component as it doesn't return something that can be rendered. Also you are calling it like a hook. What you can do is to first fix the issues with your custom hook:
It returns a hook (a function InternetChecker that calls useEffect). That is not needed. The hook should just call the useEffect itself, so it will subscribe to the NetInfo when mounting.
The unsubscribe function should, as its name already suggests, unsubscribe from the NetInfo. Otherwise it will lead to memory leaks as your listener will be called even when your component already unmounted.
const useInternetStatus = () => {
const [reachable, setReachable] = useState(false);
useEffect(() => {
const subscribe = state => setReachable(state.isInternetReachable);
NetInfo.addEventListener(subscribe);
return () => NetInfo.removeEventListener(subscribe);
},[]);
return reachable;
};
Then you could re-write your HomeScreen component to be a functional component too which allows you to use the hook inside of it:
const HomeScreen = () => {
const isInternetReachable = useInternetStatus();
console.log(isInternetReachable);
return (
<SafeAreaView style={styles.container}>
<View style={styles.container}>
</View>
</SafeAreaView>
);
};

React Redux Invalid hook call

I am sure if I follow the rules but I have this error on this function
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
in this:
import React, { useState, useEffect } from 'react';
import { getAll } from './../../services/events/api';
import EventsList from '../../containers/EventsList';
import IEvent from './../../services/events/models/IEvent';
import Loading from './../../components/Loading';
import Error from './../../components/Error';
const EventList = () => {
const [events, setEvents] = useState<Array<IEvent>>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const fetch = async () => {
try {
setIsLoading(true);
const events: Array<IEvent> = await getAll();
setEvents(events);
setIsLoading(false);
} catch (error) {
setIsLoading(false);
setError(error);
console.log(error);
}
};
useEffect(() => {
fetch();
}, []);
return (
<>
{error ? (
<Error title="Sorry" subtitle="Error loading events" />
) : isLoading ? (
<Loading />
) : (
<EventsList events={events} />
)}
</>
);
};
export default EventList;
where is the error? for Invalid hook call

Resources