Joaquin Palacios
Joaquin Palacios

Reputation: 346

React router dom dynamic routing

I am doing a React.js project. I am retrieving dat from the Star Wars API rendering a list of films on the screen and now I am trying to route every film to its own page through react-router-dom. Unfortunately, I am not able to make it work. it crash when I try to routing dynamically.

UPDATE AFTER ANSWER OF REZA

This is the App.js:

import './App.css';    
import { Route, Routes } from "react-router-dom";    
import Home from './components/Home';
import ItemContainer from './components/ItemContainer';
import Navbar from './components/Navbar';

function App() {
  return (
      <>
      <Navbar />
        <Routes>
            <Route exact path='/' element={<Home />} />
            <Route exact path="/:movieId" element={<ItemContainer />} />
        </Routes> 
        </> 
  );
}    
export default App;

This is the ItemContainer:

import { useEffect, useState } from "react";    
import MovieDetail from "../MovieDetail";
import { useParams } from "react-router-dom";    
const ShowMovie = (movieId) => {
    const [result, setResult] = useState([]);

    const fetchData = async () => {
        const res = await fetch("https://www.swapi.tech/api/films/");
        const json = await res.json();
        setResult(json.result);
    }
    useEffect(() => {
        fetchData();
    }, []);
    return new Promise((res) => 
    res(result.find((value) => value.properties.title === movieId)))
}

const ItemContainer = () => {
    const [films, setFilms] = useState([]);
    const { movieId } = useParams([]);
    console.log('params movieId container', movieId)

    useEffect(() => {
        ShowMovie(movieId).then((value) => {
            setFilms(value.properties.title)
        })
    }, [movieId])
    return (
            <MovieDetail key={films.properties.title} films={films} />
    );
}     
export default ItemContainer;

The console.log doesn't give anything.

UPDATE

Also, this is the whole code in sandbox.

Upvotes: 0

Views: 4562

Answers (2)

Drew Reese
Drew Reese

Reputation: 202751

ShowMovie is declared like a React component, but used like a utility function. You shouldn't directly invoke React function components. React functions are also to be synchronous, pure functions. ShowMovie is returning a Promise with makes it an asynchronous function.

Convert ShowMovie into a utility function, which will basically call fetch and process the JSON response.

import { useEffect, useState } from "react";    
import MovieDetail from "../MovieDetail";
import { useParams } from "react-router-dom";

const showMovie = async (movieId) => {
  const res = await fetch("https://www.swapi.tech/api/films/");
  const json = await res.json();
  const movie = json.result.find((value) => value.properties.episode_id === Number(movieId)));

  if (!movie) {
    throw new Error("No match found.");
  }

  return movie;
}

const ItemContainer = () => {
  const [films, setFilms] = useState({});
  const { movieId } = useParams();

  useEffect(() => {
    console.log('params movieId container', movieId);

    showMovie(movieId)
      .then((movie) => {
        setFilms(movie);
      })
      .catch(error => {
        // handle error/log it/show message/ignore/etc...

        setFilms({}); // maintain state invariant of object
      });
  }, [movieId]);

  return (
    <MovieDetail key={films.properties?.title} films={films} />
  );
};

export default ItemContainer;

Update

  1. The route path should include movieId, the param you are accessing in ItemContainer. The sub-path "film" should match what you link from in Home. In Home ensure you link to the /"film/...." path.

    <Routes>
      <Route path="/films" element={<Home />} />
      <Route path="/film/:movieId" element={<ItemContainer />} />
      <Route path="/" element={<Navigate replace to="/films" />} />
    </Routes>
    
  2. In ItemContainer you should be matching a movie object's episode_id property to the movieId value. Store the entire movie object into state, not just the title.

    const showMovie = async (movieId) => {
      const res = await fetch("https://www.swapi.tech/api/films/");
      const json = await res.json();
      const movie = json.result.find((value) => value.properties.episode_id === Number(movieId)));
    
      if (!movie) {
        throw new Error("No match found.");
      }
    
      return movie;
    }
    

    ...

    useEffect(() => {
      console.log("params movieId container", movieId);
    
      showMovie(movieId)
        .then((movie) => {
          setFilms(movie);
        })
        .catch((error) => {
          // handle error/log it/show message/ignore/etc...
    
          setFilms({}); // maintain state invariant of object
        });
    }, [movieId]);
    
  3. You should also use Optional Chaining on the more deeply nested films prop object properties in MovieDetail, or conditionally render MovieDetail only if the films state has something to display.

    const MovieDetail = ({ films }) => {
      return (
        <div>
          <h1>{films.properties?.title}</h1>
          <h3>{films.description}</h3>
        </div>
      );
    };
    

Demo:

Edit confident-mendel-hscz5

Upvotes: 1

Reza Ghorbani
Reza Ghorbani

Reputation: 537

Modify App.js like this:

function App() {
  return (
    <>
      <Navbar />
      <Routes>
        <Route exact path="/" element={<Home />} />
        <Route exact path="/MovieDetail/:movieId" element={<ItemContainer />} />
      </Routes>
    </>
  );
}

Upvotes: 2

Related Questions