Search code examples
javascripttypescriptreact-hooksfrontendrtk-query

RTK Query custom hook Typescript occurs Invalid hook call. Hooks can only be called inside of the body of a function component


Just learning some nexts.js with rtkquery with typescript and occur this error in useEffect in
GoogleMapComponent.tsx, code below. React site said that i broke some rules of hooks using, but this is functional component, using inside another hook, not a plain js function. dont know what to do. thanks guys.

import React, {ReactElement, useEffect, useState} from 'react';
import GoogleMapReact from 'google-map-react';
import {useGetForecastQuery} from "../redux/slices/weather.slice";

export default function GoogleMap({coords}: { coords: { lat: number; lng: number } }) {
    const [point, setPoint] = useState([45, 55]);

    useEffect(() => {
        console.log(useGetForecastQuery({location: `${point[0]},${point[1]}`, alerts: 'no', aqi: 'no', days: 2}))
    }, [point])


    const style = {
        width: '450px',
        height: '450px'
    };

    const AnyReactComponent = ({text, lat, lng} : {text:string, lat:number, lng:number}): ReactElement => {
        return (
            <>
                {`${text}  ${lat}  ${lng}`}
            </>
        )
    }

    const onMapClick = ({
                          x,
                          y,
                          lat,
                          lng,
                          event
                      }: { x: number, y: number, lat: number, lng: number, event: any }) => {
        setPoint([lat, lng]);
    }
    return (
        <>
            <div style={style}>
                <GoogleMapReact
                    options={{
                        panControl: false,
                        mapTypeControl: true,
                        scrollwheel: true,
                    }}
                    onClick={onMapClick}
                    bootstrapURLKeys={{
                        key: process.env.NEXT_PUBLIC_REACT_APP_GOOGLE_MAPS_API_KEY!,
                        language: 'ua',
                        region: 'ua',
                    }}
                    layerTypes={['TrafficLayer', 'TransitLayer']}
                    defaultCenter={coords}
                    center={coords}
                    defaultZoom={1}
                    margin={[0, 0, 0, 0]}
                >
                    <AnyReactComponent text='Marker' lat={point[0]} lng={point[1]}/>
                </GoogleMapReact>
            </div>
            <div>
                Google Map
            </div>
        </>
    )
}

WeatherSlice.ts

// Need to use the React-specific entry point to allow generating React hooks
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type RootForecast from '../../types/forecast.type'
import type {ForecastQueryOptions} from '../../types/query-options.type'
// Define a service using a base URL and expected endpoints

const api_key = process.env.NEXT_PUBLIC_WEATHER_API_KEY;

export const weatherApi = createApi({
    reducerPath: 'weatherApi',
    baseQuery: fetchBaseQuery({ baseUrl: 'https://api.weatherapi.com/v1/' }),
    endpoints: (builder) => ({
        getForecast: builder.query<RootForecast, ForecastQueryOptions>({
            query: (forecastQueryOptions: ForecastQueryOptions) => `forecast.json
            ?
            key=${api_key}&
            q=${forecastQueryOptions.location}&
            aqi=${forecastQueryOptions.aqi}&
            alerts=${forecastQueryOptions.alerts}`,
        }),
    }),
})

// Export hooks for usage in function components, which are
// auto-generated based on the defined endpoints
export const { useGetForecastQuery } = weatherApi

forecast.type.ts autogenerated from json

export interface Location {
    name: string;
    region: string;
    country: string;
    lat: number;
    lon: number;
    tz_id: string;
    localtime_epoch: number;
    localtime: string;
}

export interface Condition {
    text: string;
    icon: string;
    code: number;
}

export interface Current {
    last_updated_epoch: number;
    last_updated: string;
    temp_c: number;
    temp_f: number;
    is_day: number;
    condition: Condition;
    wind_mph: number;
    wind_kph: number;
    wind_degree: number;
    wind_dir: string;
    pressure_mb: number;
    pressure_in: number;
    precip_mm: number;
    precip_in: number;
    humidity: number;
    cloud: number;
    feelslike_c: number;
    feelslike_f: number;
    vis_km: number;
    vis_miles: number;
    uv: number;
    gust_mph: number;
    gust_kph: number;
}

export interface Condition2 {
    text: string;
    icon: string;
    code: number;
}

export interface Day {
    maxtemp_c: number;
    maxtemp_f: number;
    mintemp_c: number;
    mintemp_f: number;
    avgtemp_c: number;
    avgtemp_f: number;
    maxwind_mph: number;
    maxwind_kph: number;
    totalprecip_mm: number;
    totalprecip_in: number;
    avgvis_km: number;
    avgvis_miles: number;
    avghumidity: number;
    daily_will_it_rain: number;
    daily_chance_of_rain: number;
    daily_will_it_snow: number;
    daily_chance_of_snow: number;
    condition: Condition2;
    uv: number;
}

export interface Astro {
    sunrise: string;
    sunset: string;
    moonrise: string;
    moonset: string;
    moon_phase: string;
    moon_illumination: string;
}

export interface Condition3 {
    text: string;
    icon: string;
    code: number;
}

export interface Hour {
    time_epoch: number;
    time: string;
    temp_c: number;
    temp_f: number;
    is_day: number;
    condition: Condition3;
    wind_mph: number;
    wind_kph: number;
    wind_degree: number;
    wind_dir: string;
    pressure_mb: number;
    pressure_in: number;
    precip_mm: number;
    precip_in: number;
    humidity: number;
    cloud: number;
    feelslike_c: number;
    feelslike_f: number;
    windchill_c: number;
    windchill_f: number;
    heatindex_c: number;
    heatindex_f: number;
    dewpoint_c: number;
    dewpoint_f: number;
    will_it_rain: number;
    chance_of_rain: number;
    will_it_snow: number;
    chance_of_snow: number;
    vis_km: number;
    vis_miles: number;
    gust_mph: number;
    gust_kph: number;
    uv: number;
}

export interface Forecastday {
    date: string;
    date_epoch: number;
    day: Day;
    astro: Astro;
    hour: Hour[];
}

export interface Forecast {
    forecastday: Forecastday[];
}

export default interface RootForecast {
    location: Location;
    current: Current;
    forecast: Forecast;
}

and query-options.type.ts

export interface ForecastQueryOptions {
    location: string,
    days: number,
    aqi: string,
    alerts: string
}

Solution

  • You're using the useGetForecastQuery-hook inside another function (callback to useEffect in your case). That's not allowed with hook functions, hooks can only be called at top-level inside a component or hook function.

    See: https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level