Reputation: 346
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.
Also, this is the whole code in sandbox.
Upvotes: 0
Views: 4562
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;
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>
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]);
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:
Upvotes: 1
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