Search code examples
reactjstypescriptreact-router-domredux-toolkitrtk-query

Issue with Passing Vehicle Type Parameter to API


I am encountering an issue with passing the type of vehicle as a parameter to an API in my React application. The problem arises when attempting to pass the variable "vehicle" to the API endpoint; it receives undefined instead of "carros", "motos", or "caminhoes".

Here is the relevant code from my project:

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { Brand } from '../pages/VehicleBrand'

const api = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: 'https://parallelum.com.br/fipe/api/v1',
  }),
  endpoints: (builder) => ({
    getBrand: builder.query<Brand[], string>({
      query: (vehicle) => `${vehicle}/marcas`,
    }),
  }),
})

export const { useGetBrandQuery } = api

export default api
import { PayloadAction, createSlice } from '@reduxjs/toolkit'

export type vehicleState = {
  vehicle: string
}

const initialState: vehicleState = {
  vehicle: 'carros',
}

const vehicleSlice = createSlice({
  name: 'vehicle',
  initialState,
  reducers: {
    change: (state, action: PayloadAction<string>) => {
      state.vehicle = action.payload
    },
  },
})

export const { change } = vehicleSlice.actions

export default vehicleSlice.reducer
import { useParams } from 'react-router-dom'
import MainBox from '../../components/MainBox'
import { useGetBrandQuery } from '../../services/api'

export type Brand = {
  codigo: string
  nome: string
}

type VehicleParams = {
  vehicle: string
}

const VehicleBrand = () => {
  const { vehicle } = useParams() as VehicleParams
  const { data: brand } = useGetBrandQuery(vehicle!)

  return (
    <div className="container">
      <MainBox
        children="Selecione a marca do veículo"
        to="/VehicleModel"
        selectIsVisible={true}
        btnChildren="Buscar"
        resultIsVisible={false}
        brand={brand}
      />
    </div>
  )
}

export default VehicleBrand
import { configureStore } from '@reduxjs/toolkit'
import vehicleSlice from './reducers/vehicle'
import api from '../services/api'

export const store = configureStore({
  reducer: {
    vehicle: vehicleSlice,
    [api.reducerPath]: api.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(api.middleware),
})

export type RootState = ReturnType<typeof store.getState>

export type AppDispatch = typeof store.dispatch
import { useDispatch } from 'react-redux'
import * as S from './styles'
import { change } from '../../store/reducers/vehicle'

const Header = () => {
  const dispatch = useDispatch()

  const handleChangeVehicle = (vehicle: string) => {
    dispatch(change(vehicle))
  }

  return (
    <S.HeaderDiv>
      <S.Container className="container">
        <S.Title to="/">ConsultaFipe</S.Title>
        <nav>
          <ul>
            <S.NavLink onClick={() => handleChangeVehicle('carros')}>
              Carros
            </S.NavLink>
            <S.NavLink onClick={() => handleChangeVehicle('motos')}>
              Motos
            </S.NavLink>
            <S.NavLink onClick={() => handleChangeVehicle('caminhoes')}>
              Caminhões
            </S.NavLink>
          </ul>
        </nav>
      </S.Container>
    </S.HeaderDiv>
  )
}

export default Header

I have tried debugging the issue but haven't been able to find a solution. Any insights or suggestions would be greatly appreciated.

I need to read the value of "vehicle" in the fetch in the api query. The "GET" will be done like this: "GET: https://parallelum.com.br/fipe/api/v1/carros/marcas" where the value is "car" will receive the value of vehicle.

import { Brand } from '../pages/VehicleBrand'

const api = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: 'https://parallelum.com.br/fipe/api/v1',
  }),
  endpoints: (builder) => ({
    getBrand: builder.query<Brand[], string>({
      query: (vehicle) => `${vehicle}/marcas`,
    }),
  }),
})

export const { useGetBrandQuery } = api

export default api

The console return this error: VM8:6 GET https://parallelum.com.br/fipe/api/v1/undefined/marcas 400 (Bad Request)

This is the routes.tsx file:

import { Routes, Route } from 'react-router-dom'
import Home from './pages/Home'
import VehicleModel from './pages/VehicleModel'
import VehicleYear from './pages/VehicleYear'
import VehicleBrand from './pages/VehicleBrand'
import VehicleInfo from './pages/VehicleInfo'

const PagesRouter = () => {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/VehicleBrand" element={<VehicleBrand />} />
      <Route path="/VehicleModel" element={<VehicleModel />} />
      <Route path="/VehicleYear" element={<VehicleYear />} />
      <Route path="/VehicleInfo" element={<VehicleInfo />} />
    </Routes>
  )
}

export default PagesRouter

Solution

  • Issue

    It appears you have navigation items that set a vehicle value in your Redux store, e.g. state.vehicle.vehicle while at the same time the VehicleBrand component is expecting to read a vehicle value from the route params.

    The issue is that there aren't any routes that provide any route params and VehicleBrand doesn't read the current vehicle value from state.

    Solutions

    Navigating to and read vehicle from route parameters

    Update the route definitions to include a vehicle path parameter.

    const PagesRouter = () => {
      return (
        <Routes>
          <Route path="/" element={<Home />} />
          <Route
            path="/VehicleBrand/:vehicle" // <-- add path parameter
            element={<VehicleBrand />}
          />
          <Route path="/VehicleModel" element={<VehicleModel />} />
          <Route path="/VehicleYear" element={<VehicleYear />} />
          <Route path="/VehicleInfo" element={<VehicleInfo />} />
        </Routes>
      );
    };
    

    Update the navigation to include a vehicle value in the target path. This is just an example of passing the target path as a prop to be used as a Link component target.

    const Header = () => {
      const dispatch = useDispatch();
    
      const handleChangeVehicle = (vehicle: string) => {
        dispatch(change(vehicle));
      };
    
      return (
        <S.HeaderDiv>
          <S.Container className="container">
            <S.Title to="/">ConsultaFipe</S.Title>
            <nav>
              <ul>
                <S.NavLink
                  to="/VehicleBrand/carros"
                  onClick={() => handleChangeVehicle('carros')}
                >
                  Carros
                </S.NavLink>
                <S.NavLink
                  to="/VehicleBrand/motos"
                  onClick={() => handleChangeVehicle('motos')}
                >
                  Motos
                </S.NavLink>
                <S.NavLink
                  to="/VehicleBrand/caminhoes"
                  onClick={() => handleChangeVehicle('caminhoes')}
                >
                  Caminhões
                </S.NavLink>
              </ul>
            </nav>
          </S.Container>
        </S.HeaderDiv>
      );
    };
    

    Update VehicleBrand to skip the query if vehicle parameter value is falsey.

    const VehicleBrand = () => {
      const { vehicle } = useParams() as VehicleParams;
      const { data: brand } = useGetBrandQuery(vehicle, { skip: !vehicle });
    
      return (
        <div className="container">
          <MainBox
            children="Selecione a marca do veículo"
            to="/VehicleModel"
            selectIsVisible={true}
            btnChildren="Buscar"
            resultIsVisible={false}
            brand={brand}
          />
        </div>
      );
    };
    

    Read vehicle from state

    Select the vehicle value from state.

    const VehicleBrand = () => {
      const { vehicle } = useSelector((state: AppState) => state.vehicle);
      const { data: brand } = useGetBrandQuery(vehicle, { skip: !vehicle });
    
      return (
        <div className="container">
          <MainBox
            children="Selecione a marca do veículo"
            to="/VehicleModel"
            selectIsVisible={true}
            btnChildren="Buscar"
            resultIsVisible={false}
            brand={brand}
          />
        </div>
      );
    };