Invalid hook call.
Tags: react
Published: 2021-04-22 22:13:32

Hi, 

Im pretty new to react and ive run into the Invalid hook call issue. Googling around i see the most common fix is for when there are two instances of react. When I run npm ls react I only see 1.

The issue only arose for me after I added a Context to my app but I have no idea what the issue is.

Im using a custom hook to do server calls. If they fail I want to show the errors in a modal. I have the Modal in my App component but it is not open until there are errors. Below is my code.

My App.tsx
```
import {
    defaultTheme,
    Grid,
    Provider as ProviderV3,
} from "@adobe/react-spectrum";
import { FC, useState, useEffect, useContext } from "react";

import Toolbar from './components/Toolbar';
import OwnerSearch from './components/OwnerSearch/OwnerSearch';
import NewOwnerSearch from './components/OwnerSearch/NewOwnerSearch';
import GlobalContext, { GlobalContextProvider } from './api/GlobalContext';

import './App.scss';
import Modal from "./components/Modal";

// // Add this in node_modules/react-dom/index.js
// window.React1 = require('react');

// // Add this in your component file
// require('react-dom');
// window.React2 = require('react');
// console.log('Conor: ' + window.React1 === window.React2);

const App: FC = () => {

    const ctx = useContext(GlobalContext)
    const [isModalOpen, setIsModalOpen] = useState(false);
    const [errors, setErrors] = useState(['']);

    let modal = (
        <Modal
            heading="Warning"
            content={<div>{ctx.errors.join(';')}</div>}
            buttonList={
                [
                    { label: "OK", clickHandler: () => { }, closesModal: true },
                    { label: "Cancel", clickHandler: () => { alert("cancelled") }, closesModal: false }
                ]
            }
            isOpen={isModalOpen}
            setIsOpen={setIsModalOpen} />
    );

    useEffect(() => {
        if (ctx.errors.length > 0) {
            setIsModalOpen(true);
        }
    }, [ctx.errors]);

    //
    return (
        <GlobalContext.Provider value={{errors: errors, setErrors: setErrors}}>
            <ProviderV3 theme={defaultTheme}>
                <Toolbar></Toolbar>
                <Grid
                    margin='25px'
                    columns='50% 50%'
                    gap='10px'
                    maxWidth='100vw'>
                    <OwnerSearch />
                    <NewOwnerSearch />
                </Grid>
            </ProviderV3>
            {modal}
            </GlobalContext.Provider>
    );
};

export default App;
```

my component that uses my hook to get data:
```
import {
	TextField,
	ProgressCircle,
	Form,
	Flex,
	Button
} from '@adobe/react-spectrum';
import '@spectrum-web-components/card/sp-card.js';
import { FC, useState, useEffect, useContext } from 'react';
import GlobalContext from '../../api/GlobalContext';
import useHttp from '../../hooks/useHttp';
import React from 'react';

import './OwnerSearch.scss';

declare global {
	namespace JSX {
		interface IntrinsicElements {
			'sp-card': React.DetailedHTMLProps<ModHTMLAttributes<HTMLElement>, HTMLElement>;
		}
	}
}

const NewOwnerSearch: FC = () => { 
	const ctx = useContext(GlobalContext);
	const [owners, setOwners] = useState([]);
	const [searchText, setSearchText] = useState('msft');	

	const { isLoading, error, sendRequest: setOwnersData } = useHttp({url: 'http://liberate-api.com/api/ownership/' + searchText}, setOwners);

	const handleSubmit = (e: any /*JS Submit event*/) => {
		e.preventDefault();
		setOwnersData();
	}

	useEffect(() => 
	{
		setOwnersData();
	}, [setOwnersData]);

	if(error != '')
	{
		//ctx.addError(error);
		ctx.setErrors((prevErrors: string[]) => 
		{
			//prevErrors.push(err.message)
			let newArray = prevErrors.map((er) => {return er});
			newArray.push(error);
			return newArray;
		}
	);
	}

	const numberWithCommas = (x: number) => {
		return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
	}

	return (
		<sp-card
			heading='Owner Data'
			subheading='Search Public Owner Data'>
			<div slot='footer'>
				<Flex
					direction='column'>
					<Form onSubmit={(e) => handleSubmit(e)}>
						<Flex
							direction='row'>
							<TextField
								marginEnd='15px'
								placeholder='Search Owners'
								onChange={(a) => setSearchText(a)} 
								value={searchText}
								/>
							<Button type='submit' variant='cta'>
								Search Data
							</Button>
						</Flex>
					</Form>
					{
						(isLoading) ?
						(<Flex justifyContent='center'>
							<ProgressCircle aria-label="Loading…" isIndeterminate />
						</Flex>) :
						(<Flex
							direction='column'>
							{owners.map((x: any, y: number) => {
								return (
									<div
										key={x.ownerId}
										className={(y % 2 == 0) ? 'even' : 'odd'}>
										<Flex
											justifyContent='space-between'>
											<p>{x.ownerName}</p>
											<p>{numberWithCommas(x.sharesHeld)}</p>
										</Flex>
									</div>
								)
							})}
						</Flex>)
					}
				</Flex>
			</div>
		</sp-card>
	);
};

export default React.memo(NewOwnerSearch);
```

and my custom hook:
```
import {useCallback, useState} from 'react';

const useHttp = (requestObj: any, setData: Function) => 
{
    const [isLoading, setIsLoading] = useState(false); 
    const [error, setError] = useState('');
        
    const sendRequest = useCallback(() =>
    {
        setIsLoading(true);
        setError('');
        
        fetch(requestObj.url, {
            method: requestObj.method ? requestObj.method: 'GET',
            headers: requestObj.headers ? requestObj.headers : {},
            body: requestObj.body ? JSON.stringify(requestObj.body) : null
        })
        .then(res => res.json())
        .then(data => {
            setIsLoading(false);
            setData(data);                
        })
        .catch(err => 
        {
            setError(err.message);
            setIsLoading(false); 
            console.log('There was an error');
        });
    }, []);

    return {
        isLoading: isLoading,
        error: error,
        sendRequest: sendRequest
    }
} 

export default useHttp;
```

finally, here is the error im getting:

```
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:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
    at resolveDispatcher (react.development.js:1476)
    at useContext (react.development.js:1484)
    at useProvider (module.js:239)
    at $bc3300334f45fd1ec62a173e70ad86$var$Provider (module.js:95)
    at describeNativeComponentFrame (react-dom.development.js:946)
    at describeFunctionComponentFrame (react-dom.development.js:1034)
    at describeFiber (react-dom.development.js:1119)
    at getStackByFiberInDevAndProd (react-dom.development.js:1138)
    at createCapturedValue (react-dom.development.js:20023)
    at throwException (react-dom.development.js:20351)
```