Home > front end >  Why is useParams returning undefined even with the correct syntax?
Why is useParams returning undefined even with the correct syntax?

Time:09-04

I know this question has been asked a lot, but I read every question and answer and my problem is not gone.

I'm trying to access http://localhost:3000/1 and the useParams should receive 1 to fetch data from an API, but I'm receiving undefined.

In Component.tsx I'm using console.log to receive the useParams but I'm getting undefined. All related files are posted as I'm using BrowserRouter correctly and other related React Router imports.

What is wrong in my code? Why am I not getting the correct params?

Component.tsx:

import { createContext, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { CharacterSchema, PageSchema } from "../interfaces/characterInterfaces";
import { IGetAllCharacters } from "../interfaces/contextInterfaces";
import { IChildren } from "../interfaces/reactInterfaces";
import api from "../services/api";

export const GetAllCharactersContext = createContext<IGetAllCharacters>({} as IGetAllCharacters);

export const GetAllCharactersInfo = ({ children }: IChildren) => {
  const [charactersList, setCharactersList] = useState<CharacterSchema[]>([]);

  let navigate = useNavigate();

  const { page } = useParams();
  console.log(page);

  useEffect(() => {
    api
      .get(`/characters?page=${page}`)
      .then((res) => {
        setCharactersList(res.data.data);
        window.localStorage.setItem("lastPage", String(page));
        return res;
      })
      .catch((err) => console.error(err));
  }, [page]);

  const nextPage = () => {
    navigate(`/2`);
  };

  const prevPage = () => {
    navigate(`/1`);
  };

  return (
    <GetAllCharactersContext.Provider value={{ charactersList, nextPage, prevPage }}>
      {children}
    </GetAllCharactersContext.Provider>
  );
};

AllRoutes.tsx:

const AllRoutes = () => {
  return (
    <Routes>
      <Route path="/" element={<Navigate to="/1" />} />
      <Route path="/:page" element={<Home />} />
      <Route path="*" element={<Home />} />
    </Routes>
  );
};

export default AllRoutes;

index.tsx:

    import React from "react";
    import ReactDOM from "react-dom/client";
    import App from "./App";
    import { BrowserRouter } from "react-router-dom";
    import Providers from "./contexts/Providers";
    
    const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
    root.render(
      <React.StrictMode>
        <BrowserRouter>
          <Providers>
            <App />
          </Providers>
        </BrowserRouter>
      </React.StrictMode>
    );

CodePudding user response:

Having this <Route path="/:page" element={<Home />} /> will let you consume page with useParams only in Home, the component rendered by Route for this specific url.

A way to accomplish what you want is to move the fetching part inside Home. To do so export as part of the context a function that fetches and updates the state:

// import what's needed
export const GetAllCharactersContext = createContext<IGetAllCharacters>({} as IGetAllCharacters);
export const GetAllCharactersInfo = ({ children }: IChildren) => {
  const [charactersList, setCharactersList] = useState<CharacterSchema[]>([]);
  const navigate = useNavigate();
  
  const fetchCharactersList = useCallback((page) => {
    api
      .get(`/characters?page=${page}`)
      .then((res) => {
        setCharactersList(res.data.data);
        window.localStorage.setItem("lastPage", String(page));
      })
      .catch((err) => console.error(err));
  }, []);


  const nextPage = () => {
    navigate(`/2`);
  };

  const prevPage = () => {
    navigate(`/1`);
  };

  return (
    <GetAllCharactersContext.Provider value={{ charactersList, fetchCharactersList, nextPage, prevPage }}>
      {children}
    </GetAllCharactersContext.Provider>
  );
};

And move that useEffect you had in the provider inside Home. Something like this:

// import what's needed
export default function Home() {
  const { fetchCharactersList, charactersList } = useContext(GetAllCharactersInfo);
  const { page } = useParams();

  useEffect(() => {
    if (!page) return;
    fetchCharactersList(page);
  }, [page]);

  // render data
  return <div></div>;
}

CodePudding user response:

Just tested this myself. useParams needs to be used inside the <Route> where the param is specified. It doesn't just pull them from the url. If you want to keep the structure the same I would suggest exposing a page setter from your context and setting it inside one of the components that can access the hook.

Alternatively you could use useSearchParams which actually takes data from the url search params itself, but you wouldn't be able to have it on the url then.

  • Related