Shaun
Shaun

Reputation: 185

How to use a hook that depends on context

I have a component that retrieves user data using a context. I am trying to make an api call using the user data, however I am getting an error message:

Uncaught 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

The issue seems to be that I need to call useAxios hook outside of an if statement. But the issue is that the data from the authContext is not yet retrieved. If I try using useAxios outside of the if statement then there is no data in authData.

// Portfolio.js
import {Newsfeed} from "../components/Newsfeed";
import {PortfolioList} from "../components/PortfolioList";
import React, {useEffect, useState, useContext} from "react";
import AuthContext from "../context/authContext";
import {useNavigate} from "react-router-dom";
import {useAxios} from "../hooks/useAxios";

export const Portfolio = () => {
    const {authData} = useContext(AuthContext);
    const navigate = useNavigate();
    let coin = null;

    useEffect(() => {
        if (!authData.signedIn) {
            navigate('/login');
        }

        if (authData.signedIn) {
            const { response, loading , error } = useAxios(`/coins/${authData.user.data.coins[0].name}`);
            coin = response;
        }

    }, []);

    if (authData.signedIn) {
        return (
            <main className="-mt-24 pb-8 mb-auto">
                <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:max-w-7xl lg:px-8">
                    <div className="sm:flex sm:items-center">
                        <div className="mb-7 sm:flex-auto">
                            <h1 className="text-xl font-semibold text-indigo-100">Portfolio</h1>
                            <p className="mt-2 text-sm text-gray-200">Welcome to your portfolio.</p>
                        </div>
                    </div>
                    <div className="grid grid-cols-1 gap-4 items-start lg:grid-cols-3 lg:gap-8">
                        {authData.signedIn && authData.user && coin && coin.data(
                            <Newsfeed
                                key={authData.user.data.coins[0].id}
                                coin={coin}
                            />
                        )}
                        <PortfolioList/>
                    </div>
                </div>
            </main>
        );
    }
}
// useAxios.js
import axios from "axios";
import { useEffect, useState } from "react"

export const useAxios = (param) => {
    const [response, setResponse] = useState(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState('');

    axios.defaults.baseURL = 'https://api.coingecko.com/api/v3';

    const fetchData = async (param) => {
        try {
            setLoading(true);
            const result = await axios(param, { withCredentials: false });
            setResponse(result.data);
        } catch(err) {
            setError(err);
        } finally {
            setLoading(false);
        }
    }

    useEffect(() => {
        fetchData(param);
    }, []);

    return {
        response, loading, error
    }
}
/// authContext.js
import React from "react";

const authData = {
    signedIn: false,
    user: null
};

export default React.createContext({authData: {...authData}, setAuthData: (val) => {}});

Would appreciate anyones advice on how to get around this...

Upvotes: 0

Views: 146

Answers (1)

sms
sms

Reputation: 1440

It seems like you want to call the useAxios hooks only after the authdata is fetched. For that you can create an child component , that will have the functionalities of useAxios hooks.

// Portfolio.js
import {Newsfeed} from "../components/Newsfeed";
import {PortfolioList} from "../components/PortfolioList";
import React, {useEffect, useState, useContext} from "react";
import AuthContext from "../context/authContext";
import {useNavigate} from "react-router-dom";
import AuthDataComponent from "./AuthDataComponent.js";

export const Portfolio = () => {
    const {authData} = useContext(AuthContext);
    const navigate = useNavigate();
    let coin = null;

    useEffect(() => {
        if (!authData.signedIn) {
            navigate('/login');
        }
    }, []);

    if (authData.signedIn) {
        return (
            <main className="-mt-24 pb-8 mb-auto">
                <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:max-w-7xl lg:px-8">
                    <div className="sm:flex sm:items-center">
                        <div className="mb-7 sm:flex-auto">
                            <h1 className="text-xl font-semibold text-indigo-100">Portfolio</h1>
                            <p className="mt-2 text-sm text-gray-200">Welcome to your portfolio.</p>
                        </div>
                     <div className="grid grid-cols-1 gap-4 items-start lg:grid-cols-3 lg:gap-8">
                        <AuthDataComponent authData={authData} coin={coin} />
                        <PortfolioList/>
                    </div>
                </div>
            </main>
        );
    }
}

// AuthDataComponent.js

import {useAxios} from "../hooks/useAxios";

export const AuthDataComponent =({authData,coin}) => {
 const { response, loading , error } = 
                             useAxios(`/coins/${authData.user.data.coins[0].name}`);
 useEffect(() => {
    coin = response
 },[response]);

 return (<>
       {authData.signedIn && authData.user && coin && coin.data &&(
                            <Newsfeed
                                key={authData.user.data.coins[0].id}
                                coin={coin}
                            />
                        )}
        </>)         
}

Upvotes: 1

Related Questions