my problem is that when I want to get a route from the server dynamically, when that route is loaded, not found route is displayed in a fraction of a second, which is not good at all, and the reason is simple, and that is that the route that path If it is equal to *, it will be shown in all routes if there is no route. I don't know if you understood my meaning or not because I am writing part of my sentence with Google Translate. Look at the code below to better understand my problem.
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import axios from "axios";
import { Books } from "./components/Books";
import { Book } from "./components/Book";
const App = () => {
const [books, setBooks] = useState([]);
useEffect(() => {
loadBooks();
});
function loadBooks() {
axios.get("http://localhost:3001/books").then((data) => setBooks(data));
}
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/books">
<Route index element={<Books />} />
{books.map((book, index) => (
<Route
key={index}
path={book.name}
element={<Book name={book.name} />}
/>
))}
<Route path="*" element={<h1>Not found!<h1/>}/>
</Routes>
</Router>
);
}
When they have a list of books, they are taken from the server. If I manually search for example localhost:3000/books/book1 in the search field at the top of the browser, it first shows the not found page very quickly, then the route of the book itself. How can I do this without showing the not found page when the desired route is loaded?
To avoid rendering potentially unreachable routes I'd suggest conditionally rendering some fallback UI until the book data has been fetched.
const App = () => {
const [books, setBooks] = useState(null);
useEffect(() => {
function loadBooks() {
axios.get("http://localhost:3001/books")
.then((data) => setBooks(data));
}
loadBooks();
}, []);
if (!books) {
return null; // or loading indicator/spinner/etc
}
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/books">
<Route index element={<Books />} />
{books.map((book) => (
<Route
key={book.name}
path={book.name}
element={<Book name={book.name} />}
/>
))}
</Route>
<Route path="*" element={<h1>Not found!</h1>}/>
</Routes>
</Router>
);
}
Alternatively I'd say it may be better to not conditionally render a bunch of routes and to instead unconditionally render just a single route with a dynamic path param that correlates to a specific book name. e.g. "/books/:name"
.
Example:
const BookLayout = () => {
const [books, setBooks] = useState([]);
useEffect(() => {
function loadBooks() {
axios.get("http://localhost:3001/books")
.then((data) => setBooks(data));
}
loadBooks();
}, []);
return <Outlet context={books} />
};
const App = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/books" element={<BookLayout />}>
<Route index element={<Books />} />
<Route path=":name" element={<Book />} /> // <-- "/books/:name"
</Route>
<Route path="*" element={<h1>Not found!</h1>}/>
</Routes>
</Router>
);
}
Books
and Book
will use the useOutletContext
hook to access the books
array. Book
would use the useParams
hook to get the current book name
route path param value and render any fallback UI while the books data is fetched.