Last active
June 3, 2022 01:08
-
-
Save mfrancois3k/ceb8ab4cc79cdf525b342ea18a3ec52e to your computer and use it in GitHub Desktop.
Routes Ultimate Guide refrence https://github.com/codeiotic/react-router/tree/main/examples
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Refences | |
//https://stackblitz.com/github/remix-run/react-router/tree/main/examples/auth?file=src%2FApp.tsx | |
https://gist.github.com/mfrancois3k/ceb8ab4cc79cdf525b342ea18a3ec52e | |
import * as React from "react"; | |
import { | |
Routes, | |
Route, | |
Link, | |
useNavigate, | |
useLocation, | |
Navigate, | |
Outlet | |
} from "react-router-dom"; | |
import { fakeAuthProvider } from "./auth"; | |
export default function App() { | |
return ( | |
<AuthProvider> | |
<h1>Auth Example</h1> | |
<p> | |
This example demonstrates a simple login flow with three pages: a public | |
page, a protected page, and a login page. In order to see the protected | |
page, you must first login. Pretty standard stuff. | |
</p> | |
<p> | |
First, visit the public page. Then, visit the protected page. You're not | |
yet logged in, so you are redirected to the login page. After you login, | |
you are redirected back to the protected page. | |
</p> | |
<p> | |
Notice the URL change each time. If you click the back button at this | |
point, would you expect to go back to the login page? No! You're already | |
logged in. Try it out, and you'll see you go back to the page you | |
visited just *before* logging in, the public page. | |
</p> | |
<Routes> | |
<Route element={<Layout />}> | |
<Route path="/" element={<PublicPage />} /> | |
<Route path="/login" element={<LoginPage />} /> | |
<Route | |
path="/protected" | |
element={ | |
<RequireAuth> | |
<ProtectedPage /> | |
</RequireAuth> | |
} | |
/> | |
</Route> | |
</Routes> | |
</AuthProvider> | |
); | |
} | |
function Layout() { | |
return ( | |
<div> | |
<AuthStatus /> | |
<ul> | |
<li> | |
<Link to="/">Public Page</Link> | |
</li> | |
<li> | |
<Link to="/protected">Protected Page</Link> | |
</li> | |
</ul> | |
<Outlet /> | |
</div> | |
); | |
} | |
interface AuthContextType { | |
user: any; | |
signin: (user: string, callback: VoidFunction) => void; | |
signout: (callback: VoidFunction) => void; | |
} | |
let AuthContext = React.createContext<AuthContextType>(null!); | |
function AuthProvider({ children }: { children: React.ReactNode }) { | |
let [user, setUser] = React.useState<any>(null); | |
let signin = (newUser: string, callback: VoidFunction) => { | |
return fakeAuthProvider.signin(() => { | |
setUser(newUser); | |
callback(); | |
}); | |
}; | |
let signout = (callback: VoidFunction) => { | |
return fakeAuthProvider.signout(() => { | |
setUser(null); | |
callback(); | |
}); | |
}; | |
let value = { user, signin, signout }; | |
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; | |
} | |
function useAuth() { | |
return React.useContext(AuthContext); | |
} | |
function AuthStatus() { | |
let auth = useAuth(); | |
let navigate = useNavigate(); | |
if (!auth.user) { | |
return <p>You are not logged in.</p>; | |
} | |
return ( | |
<p> | |
Welcome {auth.user}!{" "} | |
<button | |
onClick={() => { | |
auth.signout(() => navigate("/")); | |
}} | |
> | |
Sign out | |
</button> | |
</p> | |
); | |
} | |
function RequireAuth({ children }: { children: JSX.Element }) { | |
let auth = useAuth(); | |
let location = useLocation(); | |
if (!auth.user) { | |
// Redirect them to the /login page, but save the current location they were | |
// trying to go to when they were redirected. This allows us to send them | |
// along to that page after they login, which is a nicer user experience | |
// than dropping them off on the home page. | |
return <Navigate to="/login" state={{ from: location }} />; | |
} | |
return children; | |
} | |
function LoginPage() { | |
let navigate = useNavigate(); | |
let location = useLocation(); | |
let auth = useAuth(); | |
let from = location.state?.from?.pathname || "/"; | |
function handleSubmit(event: React.FormEvent<HTMLFormElement>) { | |
event.preventDefault(); | |
let formData = new FormData(event.currentTarget); | |
let username = formData.get("username") as string; | |
auth.signin(username, () => { | |
// Send them back to the page they tried to visit when they were | |
// redirected to the login page. Use { replace: true } so we don't create | |
// another entry in the history stack for the login page. This means that | |
// when they get to the protected page and click the back button, they | |
// won't end up back on the login page, which is also really nice for the | |
// user experience. | |
navigate(from, { replace: true }); | |
}); | |
} | |
return ( | |
<div> | |
<p>You must log in to view the page at {from}</p> | |
<form onSubmit={handleSubmit}> | |
<label> | |
Username: <input name="username" type="text" /> | |
</label>{" "} | |
<button type="submit">Login</button> | |
</form> | |
</div> | |
); | |
} | |
function PublicPage() { | |
return <h3>Public</h3>; | |
} | |
function ProtectedPage() { | |
return <h3>Protected</h3>; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Reference | |
https://stackblitz.com/github/remix-run/react-router/tree/main/examples/multi-app?file=inbox%2FApp.jsx | |
import { Routes, Route, useParams, Link, Outlet } from "react-router-dom"; | |
import "./index.css"; | |
import { getMessageById, messages } from "./messages"; | |
import { NoMatch } from "./no-match"; | |
export default function InboxApp() { | |
return ( | |
<Routes> | |
{/* Routes in this app don't need to worry about which URL prefix they are | |
mounted at. They can just assume they are mounted at /. Then, if they | |
are moved under a different basename later on, all routes and links will | |
continue to work. */} | |
<Route path="/" element={<Layout />}> | |
<Route index element={<Inbox />} /> | |
<Route path=":id" element={<Message />} /> | |
<Route path="*" element={<NoMatch />} /> | |
</Route> | |
</Routes> | |
); | |
} | |
function Layout() { | |
return ( | |
<div> | |
<h1>Welcome to the Inbox app!</h1> | |
<nav> | |
<ul> | |
<li> | |
{/* Using a normal link here will cause the browser to reload the | |
document when following this link, which is exactly what we want | |
when transitioning to the "Home" app so we execute its entry | |
point (see home/main.jsx). */} | |
<a href="/">Home</a> | |
</li> | |
<li> | |
<a href="/about">About</a> | |
</li> | |
<li> | |
<Link to="/">Inbox</Link> | |
</li> | |
</ul> | |
</nav> | |
<hr /> | |
<Outlet /> | |
</div> | |
); | |
} | |
function Inbox() { | |
return ( | |
<div> | |
<div style={{ maxWidth: 800, margin: "0 auto" }}> | |
{messages.map((message) => ( | |
<Link | |
to={message.id} | |
key={message.id} | |
style={{ | |
display: "flex", | |
borderBottom: "1px solid #ccc", | |
padding: "10px", | |
width: "100%", | |
textDecoration: "none", | |
color: "#000", | |
}} | |
> | |
<span | |
style={{ | |
flexBasis: 100, | |
marginRight: "1rem", | |
}} | |
> | |
{message.from.name} | |
</span> | |
<div | |
style={{ | |
flexGrow: 1, | |
textOverflow: "ellipsis", | |
width: "100%", | |
whiteSpace: "nowrap", | |
overflow: "hidden", | |
marginRight: "1rem", | |
}} | |
> | |
<span>{message.subject}</span> | |
<div style={{ color: "#999", display: "inline" }}> | |
<span>{" — "}</span> | |
<span>{message.body}</span> | |
</div> | |
</div> | |
<span style={{ flexShrink: 0 }}> | |
{new Date(message.date).toDateString()} | |
</span> | |
</Link> | |
))} | |
</div> | |
</div> | |
); | |
} | |
function Message() { | |
let { id } = useParams(); | |
let message = getMessageById(id); | |
if (!message) { | |
return <NoMatch />; | |
} | |
return ( | |
<div> | |
<h2>{message.subject}</h2> | |
<div> | |
<h3 style={{ fontSize: 14 }}> | |
<span>{message.from.name}</span>{" "} | |
<span><{message.from.email}></span> | |
</h3> | |
<div>{message.body}</div> | |
</div> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Reference | |
https://stackblitz.com/github/remix-run/react-router/tree/main/examples/ssr?file=src%2FApp.tsx | |
import * as React from "react"; | |
import { Routes, Route, Outlet, Link } from "react-router-dom"; | |
export default function App() { | |
return ( | |
<div> | |
<h1>Server Rendering Example</h1> | |
<p> | |
If you check out the HTML source of this page, you'll notice that it | |
already contains the HTML markup of the app that was sent from the | |
server! | |
</p> | |
<p> | |
This is great for search engines that need to index this page. It's also | |
great for users because server-rendered pages tend to load more quickly | |
on mobile devices and over slow networks. | |
</p> | |
<p> | |
Another thing to notice is that when you click one of the links below | |
and navigate to a different URL, then hit the refresh button on your | |
browser, the server is able to generate the HTML markup for that page as | |
well because you're using React Router on the server. This creates a | |
seamless experience both for your users navigating around your site and | |
for developers on your team who get to use the same routing library in | |
both places. | |
</p> | |
{/* Routes nest inside one another. Nested route paths build upon | |
parent route paths, and nested route elements render inside | |
parent route elements. See the note about <Outlet> below. */} | |
<Routes> | |
<Route path="/" element={<Layout />}> | |
<Route index element={<Home />} /> | |
<Route path="about" element={<About />} /> | |
<Route path="dashboard" element={<Dashboard />} /> | |
{/* Using path="*"" means "match anything", so this route | |
acts like a catch-all for URLs that we don't have explicit | |
routes for. */} | |
<Route path="*" element={<NoMatch />} /> | |
</Route> | |
</Routes> | |
</div> | |
); | |
} | |
function Layout() { | |
return ( | |
<div> | |
{/* A "layout route" is a good place to put markup you want to | |
share across all the pages on your site, like navigation. */} | |
<nav> | |
<ul> | |
<li> | |
<Link to="/">Home</Link> | |
</li> | |
<li> | |
<Link to="/about">About</Link> | |
</li> | |
<li> | |
<Link to="/dashboard">Dashboard</Link> | |
</li> | |
<li> | |
<Link to="/nothing-here">Nothing Here</Link> | |
</li> | |
</ul> | |
</nav> | |
<hr /> | |
{/* An <Outlet> renders whatever child route is currently active, | |
so you can think about this <Outlet> as a placeholder for | |
the child routes we defined above. */} | |
<Outlet /> | |
</div> | |
); | |
} | |
function Home() { | |
return ( | |
<div> | |
<h2>Home</h2> | |
</div> | |
); | |
} | |
function About() { | |
return ( | |
<div> | |
<h2>About</h2> | |
</div> | |
); | |
} | |
function Dashboard() { | |
return ( | |
<div> | |
<h2>Dashboard</h2> | |
</div> | |
); | |
} | |
function NoMatch() { | |
return ( | |
<div> | |
<h2>Nothing to see here!</h2> | |
<p> | |
<Link to="/">Go to the home page</Link> | |
</p> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Refences | |
//https://stackblitz.com/github/remix-run/react-router/tree/main/examples/auth?file=src%2FApp.tsx | |
import * as React from "react"; | |
import { | |
Routes, | |
Route, | |
Link, | |
useNavigate, | |
useLocation, | |
Navigate, | |
Outlet | |
} from "react-router-dom"; | |
import { fakeAuthProvider } from "./auth"; | |
export default function App() { | |
return ( | |
<AuthProvider> | |
<h1>Auth Example</h1> | |
<p> | |
This example demonstrates a simple login flow with three pages: a public | |
page, a protected page, and a login page. In order to see the protected | |
page, you must first login. Pretty standard stuff. | |
</p> | |
<p> | |
First, visit the public page. Then, visit the protected page. You're not | |
yet logged in, so you are redirected to the login page. After you login, | |
you are redirected back to the protected page. | |
</p> | |
<p> | |
Notice the URL change each time. If you click the back button at this | |
point, would you expect to go back to the login page? No! You're already | |
logged in. Try it out, and you'll see you go back to the page you | |
visited just *before* logging in, the public page. | |
</p> | |
<Routes> | |
<Route element={<Layout />}> | |
<Route path="/" element={<PublicPage />} /> | |
<Route path="/login" element={<LoginPage />} /> | |
<Route | |
path="/protected" | |
element={ | |
<RequireAuth> | |
<ProtectedPage /> | |
</RequireAuth> | |
} | |
/> | |
</Route> | |
</Routes> | |
</AuthProvider> | |
); | |
} | |
function Layout() { | |
return ( | |
<div> | |
<AuthStatus /> | |
<ul> | |
<li> | |
<Link to="/">Public Page</Link> | |
</li> | |
<li> | |
<Link to="/protected">Protected Page</Link> | |
</li> | |
</ul> | |
<Outlet /> | |
</div> | |
); | |
} | |
interface AuthContextType { | |
user: any; | |
signin: (user: string, callback: VoidFunction) => void; | |
signout: (callback: VoidFunction) => void; | |
} | |
let AuthContext = React.createContext<AuthContextType>(null!); | |
function AuthProvider({ children }: { children: React.ReactNode }) { | |
let [user, setUser] = React.useState<any>(null); | |
let signin = (newUser: string, callback: VoidFunction) => { | |
return fakeAuthProvider.signin(() => { | |
setUser(newUser); | |
callback(); | |
}); | |
}; | |
let signout = (callback: VoidFunction) => { | |
return fakeAuthProvider.signout(() => { | |
setUser(null); | |
callback(); | |
}); | |
}; | |
let value = { user, signin, signout }; | |
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; | |
} | |
function useAuth() { | |
return React.useContext(AuthContext); | |
} | |
function AuthStatus() { | |
let auth = useAuth(); | |
let navigate = useNavigate(); | |
if (!auth.user) { | |
return <p>You are not logged in.</p>; | |
} | |
return ( | |
<p> | |
Welcome {auth.user}!{" "} | |
<button | |
onClick={() => { | |
auth.signout(() => navigate("/")); | |
}} | |
> | |
Sign out | |
</button> | |
</p> | |
); | |
} | |
function RequireAuth({ children }: { children: JSX.Element }) { | |
let auth = useAuth(); | |
let location = useLocation(); | |
if (!auth.user) { | |
// Redirect them to the /login page, but save the current location they were | |
// trying to go to when they were redirected. This allows us to send them | |
// along to that page after they login, which is a nicer user experience | |
// than dropping them off on the home page. | |
return <Navigate to="/login" state={{ from: location }} />; | |
} | |
return children; | |
} | |
function LoginPage() { | |
let navigate = useNavigate(); | |
let location = useLocation(); | |
let auth = useAuth(); | |
let from = location.state?.from?.pathname || "/"; | |
function handleSubmit(event: React.FormEvent<HTMLFormElement>) { | |
event.preventDefault(); | |
let formData = new FormData(event.currentTarget); | |
let username = formData.get("username") as string; | |
auth.signin(username, () => { | |
// Send them back to the page they tried to visit when they were | |
// redirected to the login page. Use { replace: true } so we don't create | |
// another entry in the history stack for the login page. This means that | |
// when they get to the protected page and click the back button, they | |
// won't end up back on the login page, which is also really nice for the | |
// user experience. | |
navigate(from, { replace: true }); | |
}); | |
} | |
return ( | |
<div> | |
<p>You must log in to view the page at {from}</p> | |
<form onSubmit={handleSubmit}> | |
<label> | |
Username: <input name="username" type="text" /> | |
</label>{" "} | |
<button type="submit">Login</button> | |
</form> | |
</div> | |
); | |
} | |
function PublicPage() { | |
return <h3>Public</h3>; | |
} | |
function ProtectedPage() { | |
return <h3>Protected</h3>; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// reference | |
https://stackblitz.com/github/remix-run/react-router/tree/main/examples/basic?file=src%2FApp.tsx | |
import * as React from "react"; | |
import { Routes, Route, Outlet, Link } from "react-router-dom"; | |
export default function App() { | |
return ( | |
<div> | |
<h1>Basic Example</h1> | |
<p> | |
This example demonstrates some of the core features of React Router | |
including nested <code><Route></code>s,{" "} | |
<code><Outlet></code>s, <code><Link></code>s, and using a | |
"*" route (aka "splat route") to render a "not found" page when someone | |
visits an unrecognized URL. | |
</p> | |
{/* Routes nest inside one another. Nested route paths build upon | |
parent route paths, and nested route elements render inside | |
parent route elements. See the note about <Outlet> below. */} | |
<Routes> | |
<Route path="/" element={<Layout />}> | |
<Route index element={<Home />} /> | |
<Route path="about" element={<About />} /> | |
<Route path="dashboard" element={<Dashboard />} /> | |
{/* Using path="*"" means "match anything", so this route | |
acts like a catch-all for URLs that we don't have explicit | |
routes for. */} | |
<Route path="*" element={<NoMatch />} /> | |
</Route> | |
</Routes> | |
</div> | |
); | |
} | |
function Layout() { | |
return ( | |
<div> | |
{/* A "layout route" is a good place to put markup you want to | |
share across all the pages on your site, like navigation. */} | |
<nav> | |
<ul> | |
<li> | |
<Link to="/">Home</Link> | |
</li> | |
<li> | |
<Link to="/about">About</Link> | |
</li> | |
<li> | |
<Link to="/dashboard">Dashboard</Link> | |
</li> | |
<li> | |
<Link to="/nothing-here">Nothing Here</Link> | |
</li> | |
</ul> | |
</nav> | |
<hr /> | |
{/* An <Outlet> renders whatever child route is currently active, | |
so you can think about this <Outlet> as a placeholder for | |
the child routes we defined above. */} | |
<Outlet /> | |
</div> | |
); | |
} | |
function Home() { | |
return ( | |
<div> | |
<h2>Home</h2> | |
</div> | |
); | |
} | |
function About() { | |
return ( | |
<div> | |
<h2>About</h2> | |
</div> | |
); | |
} | |
function Dashboard() { | |
return ( | |
<div> | |
<h2>Dashboard</h2> | |
</div> | |
); | |
} | |
function NoMatch() { | |
return ( | |
<div> | |
<h2>Nothing to see here!</h2> | |
<p> | |
<Link to="/">Go to the home page</Link> | |
</p> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from "react"; | |
import { | |
Routes, | |
Route, | |
Outlet, | |
Link, | |
useSearchParams, | |
useParams | |
} from "react-router-dom"; | |
import type { LinkProps } from "react-router-dom"; | |
import VisuallyHidden from "@reach/visually-hidden"; | |
import { brands, filterByBrand, getSneakerById, SNEAKERS } from "./snkrs"; | |
export default function App() { | |
return ( | |
<div> | |
<h1>Custom Filter Link Example</h1> | |
<p> | |
This example demonstrates how to create a "filter link" like one that is | |
commonly used to filter a list of products on an e-commerce website. The | |
<code><BrandLink></code> component is a custom{" "} | |
<code><Link></code> that knows whether or not it is currently | |
"active" by what is in the URL query string. | |
</p> | |
<Routes> | |
<Route path="/" element={<Layout />}> | |
<Route index element={<SneakerGrid />} /> | |
<Route path="/sneakers/:id" element={<SneakerView />} /> | |
<Route path="*" element={<NoMatch />} /> | |
</Route> | |
</Routes> | |
</div> | |
); | |
} | |
interface BrandLinkProps extends Omit<LinkProps, "to"> { | |
brand: string; | |
} | |
function BrandLink({ brand, children, ...props }: BrandLinkProps) { | |
let [searchParams] = useSearchParams(); | |
let isActive = searchParams.get("brand") === brand; | |
return ( | |
<Link | |
to={`/?brand=${brand}`} | |
{...props} | |
style={{ | |
...props.style, | |
color: isActive ? "red" : "black" | |
}} | |
> | |
{children} | |
</Link> | |
); | |
} | |
function Layout() { | |
return ( | |
<div> | |
<nav> | |
<h3>Filter by brand</h3> | |
<ul> | |
<li> | |
<Link to="/">All</Link> | |
</li> | |
{brands.map(brand => ( | |
<li key={brand}> | |
<BrandLink brand={brand}>{brand}</BrandLink> | |
</li> | |
))} | |
</ul> | |
</nav> | |
<hr /> | |
<Outlet /> | |
</div> | |
); | |
} | |
function SneakerGrid() { | |
let [searchParams] = useSearchParams(); | |
let brand = searchParams.get("brand"); | |
const sneakers = React.useMemo(() => { | |
if (!brand) return SNEAKERS; | |
return filterByBrand(brand); | |
}, [brand]); | |
return ( | |
<main> | |
<h2>Sneakers</h2> | |
<div | |
style={{ | |
display: "grid", | |
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))", | |
gap: "12px 24px" | |
}} | |
> | |
{sneakers.map(snkr => { | |
let name = `${snkr.brand} ${snkr.model} ${snkr.colorway}`; | |
return ( | |
<div key={snkr.id} style={{ position: "relative" }}> | |
<img | |
width={200} | |
height={200} | |
src={snkr.imageUrl} | |
alt={name} | |
style={{ | |
borderRadius: "8px", | |
width: "100%", | |
height: "auto", | |
aspectRatio: "1 / 1" | |
}} | |
/> | |
<Link | |
style={{ position: "absolute", inset: 0 }} | |
to={`/sneakers/${snkr.id}`} | |
> | |
<VisuallyHidden>{name}</VisuallyHidden> | |
</Link> | |
<div> | |
<p>{name}</p> | |
</div> | |
</div> | |
); | |
})} | |
</div> | |
</main> | |
); | |
} | |
function SneakerView() { | |
let { id } = useParams<"id">(); | |
if (!id) { | |
return <NoMatch />; | |
} | |
let snkr = getSneakerById(id); | |
if (!snkr) { | |
return <NoMatch />; | |
} | |
let name = `${snkr.brand} ${snkr.model} ${snkr.colorway}`; | |
return ( | |
<div> | |
<h2>{name}</h2> | |
<img | |
width={400} | |
height={400} | |
style={{ | |
borderRadius: "8px", | |
maxWidth: "100%", | |
aspectRatio: "1 / 1" | |
}} | |
src={snkr.imageUrl} | |
alt={name} | |
/> | |
</div> | |
); | |
} | |
function NoMatch() { | |
return ( | |
<div> | |
<h2>Nothing to see here!</h2> | |
<p> | |
<Link to="/">Go to the home page</Link> | |
</p> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Reference | |
https://stackblitz.com/github/remix-run/react-router/tree/main/examples/custom-link?file=src%2FApp.tsx | |
import * as React from "react"; | |
import { | |
Routes, | |
Route, | |
Outlet, | |
Link, | |
useMatch, | |
useResolvedPath | |
} from "react-router-dom"; | |
import type { LinkProps } from "react-router-dom"; | |
export default function App() { | |
return ( | |
<div> | |
<h1>Custom Link Example</h1> | |
<p> | |
This example demonstrates how to create a custom{" "} | |
<code><Link></code> component that knows whether or not it is | |
"active" using the low-level <code>useResolvedPath()</code> and | |
<code>useMatch()</code> hooks. | |
</p> | |
<Routes> | |
<Route path="/" element={<Layout />}> | |
<Route index element={<Home />} /> | |
<Route path="about" element={<About />} /> | |
<Route path="*" element={<NoMatch />} /> | |
</Route> | |
</Routes> | |
</div> | |
); | |
} | |
function CustomLink({ children, to, ...props }: LinkProps) { | |
let resolved = useResolvedPath(to); | |
let match = useMatch({ path: resolved.pathname, end: true }); | |
return ( | |
<div> | |
<Link | |
style={{ textDecoration: match ? "underline" : "none" }} | |
to={to} | |
{...props} | |
> | |
{children} | |
</Link> | |
{match && " (active)"} | |
</div> | |
); | |
} | |
function Layout() { | |
return ( | |
<div> | |
<nav> | |
<ul> | |
<li> | |
<CustomLink to="/">Home</CustomLink> | |
</li> | |
<li> | |
<CustomLink to="/about">About</CustomLink> | |
</li> | |
</ul> | |
</nav> | |
<hr /> | |
<Outlet /> | |
</div> | |
); | |
} | |
function Home() { | |
return ( | |
<div> | |
<h1>Home</h1> | |
</div> | |
); | |
} | |
function About() { | |
return ( | |
<div> | |
<h1>About</h1> | |
</div> | |
); | |
} | |
function NoMatch() { | |
return ( | |
<div> | |
<h1>Nothing to see here!</h1> | |
<p> | |
<Link to="/">Go to the home page</Link> | |
</p> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Reference | |
https://stackblitz.com/github/remix-run/react-router/tree/main/examples/custom-query-parsing?file=src%2FApp.tsx | |
import * as React from "react"; | |
import * as JSURL from "jsurl"; | |
import type { NavigateOptions } from "react-router-dom"; | |
import { Routes, Route, Link, useSearchParams } from "react-router-dom"; | |
export default function App() { | |
return ( | |
<div> | |
<h1>Custom Query Parsing Example</h1> | |
<p> | |
This example demonstrates how to store a complex data structure in a URL | |
query parameter. | |
</p> | |
<p> | |
Each time a field in the form below changes, the URL is updated with a | |
serialized version of the form's values. To see the effect this has, | |
manipulate some fields in the form. Then, copy the URL in the address | |
bar and paste it into a new tab in your browser to see the form in the | |
exact same state as when you left it! | |
</p> | |
<Routes> | |
<Route index element={<Home />} /> | |
<Route path="*" element={<NoMatch />} /> | |
</Routes> | |
</div> | |
); | |
} | |
/** | |
* This custom hook is a wrapper around `useSearchParams()` that parses and | |
* serializes the search param value using the JSURL library, which permits any | |
* JavaScript value to be safely URL-encoded. | |
* | |
* It's a good example of how React hooks offer a great deal of flexibility when | |
* you compose them together! | |
* | |
* TODO: rethink the generic type here, users can put whatever they want in the | |
* URL, probably best to use runtime validation with a type predicate: | |
* https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates | |
*/ | |
function useQueryParam<T>( | |
key: string | |
): [T | undefined, (newQuery: T, options?: NavigateOptions) => void] { | |
let [searchParams, setSearchParams] = useSearchParams(); | |
let paramValue = searchParams.get(key); | |
let value = React.useMemo(() => JSURL.parse(paramValue), [paramValue]); | |
let setValue = React.useCallback( | |
(newValue: T, options?: NavigateOptions) => { | |
let newSearchParams = new URLSearchParams(searchParams); | |
newSearchParams.set(key, JSURL.stringify(newValue)); | |
setSearchParams(newSearchParams, options); | |
}, | |
[key, searchParams, setSearchParams] | |
); | |
return [value, setValue]; | |
} | |
interface Pizza { | |
toppings: string[]; | |
crust: string; | |
extraSauce: boolean; | |
} | |
function Home() { | |
let [pizza, setPizza] = useQueryParam<Pizza>("pizza"); | |
if (!pizza) { | |
pizza = { toppings: [], crust: "regular", extraSauce: false }; | |
} | |
function handleChange(event: React.ChangeEvent<HTMLFormElement>) { | |
let form = event.currentTarget; | |
let formData = new FormData(form); | |
// This complex data structure is preserved in the URL in the | |
// `pizza` query parameter each time a value in the form changes! | |
let pizza: Pizza = { | |
toppings: formData.getAll("toppings") as string[], | |
crust: formData.get("crust") as string, | |
extraSauce: formData.get("extraSauce") === "on" | |
}; | |
setPizza(pizza, { replace: true }); | |
} | |
return ( | |
<div> | |
<form onChange={handleChange}> | |
<p>What would you like on your pizza?</p> | |
<p> | |
<label> | |
<input | |
defaultChecked={pizza.toppings.includes("pepperoni")} | |
type="checkbox" | |
name="toppings" | |
value="pepperoni" | |
/>{" "} | |
Pepperoni | |
</label> | |
<br /> | |
<label> | |
<input | |
defaultChecked={pizza.toppings.includes("bell-peppers")} | |
type="checkbox" | |
name="toppings" | |
value="bell-peppers" | |
/>{" "} | |
Bell Peppers | |
</label> | |
<br /> | |
<label> | |
<input | |
type="checkbox" | |
name="toppings" | |
value="olives" | |
defaultChecked={pizza.toppings.includes("olives")} | |
/>{" "} | |
Olives | |
</label> | |
</p> | |
<p> | |
<label> | |
<input | |
type="radio" | |
name="crust" | |
value="regular" | |
defaultChecked={pizza.crust === "regular"} | |
/>{" "} | |
Regular Crust | |
</label> | |
<br /> | |
<label> | |
<input | |
type="radio" | |
name="crust" | |
value="thin" | |
defaultChecked={pizza.crust === "thin"} | |
/>{" "} | |
Thin Crust | |
</label> | |
<br /> | |
<label> | |
<input | |
type="radio" | |
name="crust" | |
value="deep-dish" | |
defaultChecked={pizza.crust === "dish-dish"} | |
/>{" "} | |
Deep Dish | |
</label> | |
</p> | |
<p> | |
<label> | |
<input | |
type="checkbox" | |
name="extraSauce" | |
defaultChecked={pizza.extraSauce} | |
/>{" "} | |
Extra Sauce | |
</label> | |
</p> | |
</form> | |
<hr /> | |
<p>The current form values are:</p> | |
<pre>{JSON.stringify(pizza || {}, null, 2)}</pre> | |
</div> | |
); | |
} | |
function NoMatch() { | |
return ( | |
<div> | |
<h2>Nothing to see here!</h2> | |
<p> | |
<Link to="/">Go to the home page</Link> | |
</p> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Reference | |
https://stackblitz.com/github/remix-run/react-router/tree/main/examples/lazy-loading?file=src%2FApp.tsx | |
import * as React from "react"; | |
import { Routes, Route, Outlet, Link } from "react-router-dom"; | |
const About = React.lazy(() => import("./pages/About")); | |
const Dashboard = React.lazy(() => import("./pages/Dashboard")); | |
export default function App() { | |
return ( | |
<div> | |
<h1>Lazy Loading Example</h1> | |
<p> | |
This example demonstrates how to lazily load both route elements and | |
even entire portions of your route hierarchy on demand. To get the full | |
effect of this demo, be sure to open your Network tab and watch the new | |
bundles load dynamically as you navigate around. | |
</p> | |
<p> | |
The "About" page is not loaded until you click on the link. When you do, | |
a <code><React.Suspense fallback></code> renders while the code is | |
loaded via a dynamic <code>import()</code> statement. Once the code | |
loads, the fallback is replaced by the actual code for that page. | |
</p> | |
<p> | |
The "Dashboard" page does the same thing, but takes it even one step | |
further by <em>dynamically defining additional routes</em> once the page | |
loads! Since React Router lets you declare your routes as | |
<code><Route></code> elements, you can easily define more routes | |
by placing an additional <code><Routes></code> element anywhere | |
further down the element tree. Just be sure the parent route ends with a{" "} | |
<code>*</code> like <code><Route path="dashboard/*"></code> in | |
this case. | |
</p> | |
<Routes> | |
<Route path="/" element={<Layout />}> | |
<Route index element={<Home />} /> | |
<Route | |
path="about" | |
element={ | |
<React.Suspense fallback={<>...</>}> | |
<About /> | |
</React.Suspense> | |
} | |
/> | |
<Route | |
path="dashboard/*" | |
element={ | |
<React.Suspense fallback={<>...</>}> | |
<Dashboard /> | |
</React.Suspense> | |
} | |
/> | |
<Route path="*" element={<NoMatch />} /> | |
</Route> | |
</Routes> | |
</div> | |
); | |
} | |
function Layout() { | |
return ( | |
<div> | |
<nav> | |
<ul> | |
<li> | |
<Link to="/">Home</Link> | |
</li> | |
<li> | |
<Link to="/about">About</Link> | |
</li> | |
<li> | |
<Link to="/dashboard/messages">Messages (Dashboard)</Link> | |
</li> | |
</ul> | |
</nav> | |
<hr /> | |
<Outlet /> | |
</div> | |
); | |
} | |
function Home() { | |
return ( | |
<div> | |
<h2>Home</h2> | |
</div> | |
); | |
} | |
function NoMatch() { | |
return ( | |
<div> | |
<h2>Nothing to see here!</h2> | |
<p> | |
<Link to="/">Go to the home page</Link> | |
</p> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Reference | |
https://stackblitz.com/github/remix-run/react-router/tree/main/examples/modal?file=src%2FApp.tsx | |
import * as React from "react"; | |
import { | |
Routes, | |
Route, | |
Outlet, | |
Link, | |
useLocation, | |
useNavigate, | |
useParams | |
} from "react-router-dom"; | |
import { Dialog } from "@reach/dialog"; | |
import "@reach/dialog/styles.css"; | |
import { IMAGES, getImageById } from "./images"; | |
export default function App() { | |
let location = useLocation(); | |
// The `backgroundLocation` state is the location that we were at when one of | |
// the gallery links was clicked. If it's there, use it as the location for | |
// the <Routes> so we show the gallery in the background, behind the modal. | |
let state = location.state as { backgroundLocation?: Location }; | |
return ( | |
<div> | |
<h1>Modal Example</h1> | |
<p> | |
This is an example of how to create a contextual modal navigation with | |
React Router where the navigation path the user takes determines if the | |
page is rendered in the modal or not (popularized by pinterest, | |
instagram, and others in the 2010s). This type of modal is typically | |
used as a kind of "detail" view to focus on a particular object in a | |
collection (like a pinterest board) while not taking you completely out | |
of context of the parent page. But, when the same URL is visited | |
directly (rather than from the collection page) it renders as it's own | |
full page instead of in a modal. | |
</p> | |
<p> | |
In this example, notice how the URL updates when the modal opens (if you | |
are viewing the example in StackBlitz you may need to open in a new | |
browser window). Even though the URL is updated to the specific item in | |
the modal, the background page is still showing behind it. | |
</p> | |
<p> | |
Next, copy and paste the URL to a new browser tab and notice that it | |
shows that specific item not in a modal, but directly on the page. This | |
is the view that someone would see if they clicked on a link that you | |
sent them when you had the modal open. They don't have the context you | |
did when you opened the modal, so they don't see it in the context of | |
the background page. | |
</p> | |
<Routes location={state?.backgroundLocation || location}> | |
<Route path="/" element={<Layout />}> | |
<Route index element={<Home />} /> | |
<Route path="gallery" element={<Gallery />} /> | |
<Route path="/img/:id" element={<ImageView />} /> | |
<Route path="*" element={<NoMatch />} /> | |
</Route> | |
</Routes> | |
{/* Show the modal when a `backgroundLocation` is set */} | |
{state?.backgroundLocation && ( | |
<Routes> | |
<Route path="/img/:id" element={<Modal />} /> | |
</Routes> | |
)} | |
</div> | |
); | |
} | |
function Layout() { | |
return ( | |
<div> | |
<nav> | |
<ul> | |
<li> | |
<Link to="/">Home</Link> | |
</li> | |
<li> | |
<Link to="/gallery">Gallery</Link> | |
</li> | |
</ul> | |
</nav> | |
<hr /> | |
<Outlet /> | |
</div> | |
); | |
} | |
function Home() { | |
return ( | |
<div> | |
<h2>Home</h2> | |
<h3>Featured Images</h3> | |
<ul> | |
<li> | |
<Link to="/img/1">Image 1</Link> | |
</li> | |
<li> | |
<Link to="/img/2">Image 2</Link> | |
</li> | |
</ul> | |
</div> | |
); | |
} | |
function Gallery() { | |
let location = useLocation(); | |
return ( | |
<div style={{ padding: "0 24px" }}> | |
<h2>Gallery</h2> | |
<div | |
style={{ | |
display: "grid", | |
gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", | |
gap: "24px" | |
}} | |
> | |
{IMAGES.map(image => ( | |
<Link | |
key={image.id} | |
to={`/img/${image.id}`} | |
// This is the trick! Set the `backgroundLocation` in location state | |
// so that when we open the modal we still see the current page in | |
// the background. | |
state={{ backgroundLocation: location }} | |
> | |
<img | |
width={200} | |
height={200} | |
style={{ | |
width: "100%", | |
aspectRatio: "1 / 1", | |
height: "auto", | |
borderRadius: "8px" | |
}} | |
src={image.src} | |
alt={image.title} | |
/> | |
</Link> | |
))} | |
</div> | |
</div> | |
); | |
} | |
function ImageView() { | |
let { id } = useParams<"id">(); | |
let image = getImageById(Number(id)); | |
if (!image) return <div>Image not found</div>; | |
return ( | |
<div> | |
<h1>{image.title}</h1> | |
<img width={400} height={400} src={image.src} alt="" /> | |
</div> | |
); | |
} | |
function Modal() { | |
let navigate = useNavigate(); | |
let { id } = useParams<"id">(); | |
let image = getImageById(Number(id)); | |
let buttonRef = React.useRef<HTMLButtonElement>(null); | |
function onDismiss() { | |
navigate(-1); | |
} | |
if (!image) return null; | |
return ( | |
<Dialog | |
aria-labelledby="label" | |
onDismiss={onDismiss} | |
initialFocusRef={buttonRef} | |
> | |
<div | |
style={{ | |
display: "grid", | |
justifyContent: "center", | |
padding: "8px 8px" | |
}} | |
> | |
<h1 id="label" style={{ margin: 0 }}> | |
{image.title} | |
</h1> | |
<img | |
style={{ | |
margin: "16px 0", | |
borderRadius: "8px", | |
width: "100%", | |
height: "auto" | |
}} | |
width={400} | |
height={400} | |
src={image.src} | |
alt="" | |
/> | |
<button | |
style={{ display: "block" }} | |
ref={buttonRef} | |
onClick={onDismiss} | |
> | |
Close | |
</button> | |
</div> | |
</Dialog> | |
); | |
} | |
function NoMatch() { | |
return ( | |
<div> | |
<h2>Nothing to see here!</h2> | |
<p> | |
<Link to="/">Go to the home page</Link> | |
</p> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// reference | |
https://stackblitz.com/edit/react-router-active-classname-ejgsgl?file=src%2FApp.js | |
// also use this repo as link for example for react app | |
https://github.com/Adrion10/react-nested-routes-main/tree/master/src | |
import React from 'react'; | |
import { | |
BrowserRouter as Router, | |
Routes, | |
Route, | |
NavLink, | |
} from 'react-router-dom'; | |
import './style.css'; | |
const Users = () => <div>Users</div>; | |
const Posts = () => <div>Posts</div>; | |
const Tests = () => <div>Tests</div>; | |
const App = () => { | |
return ( | |
<div className="app"> | |
<Router> | |
<div className="nav"> | |
<NavLink | |
to="users" | |
className={({ isActive }) => (isActive ? 'active' : 'inactive')} | |
> | |
Users | |
</NavLink> | |
<NavLink | |
to="posts" | |
className={({ isActive }) => (isActive ? 'active' : 'inactive')} | |
> | |
Posts | |
</NavLink> | |
<NavLink | |
to="tests" | |
className={({ isActive }) => (isActive ? 'active' : 'inactive')} | |
> | |
Tests | |
</NavLink> | |
</div> | |
<Routes> | |
<Route path="users" element={<Users />} /> | |
<Route path="posts" element={<Posts />} /> | |
<Route path="tests" element={<Tests />} /> | |
</Routes> | |
</Router> | |
</div> | |
); | |
}; | |
export default App; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Reference | |
https://stackblitz.com/github/remix-run/react-router/tree/main/examples/route-objects?file=src%2FApp.tsx | |
import * as React from "react"; | |
import type { RouteObject } from "react-router-dom"; | |
import { Outlet, Link, useRoutes, useParams } from "react-router-dom"; | |
export default function App() { | |
let routes: RouteObject[] = [ | |
{ | |
path: "/", | |
element: <Layout />, | |
children: [ | |
{ index: true, element: <Home /> }, | |
{ | |
path: "/courses", | |
element: <Courses />, | |
children: [ | |
{ index: true, element: <CoursesIndex /> }, | |
{ path: "/courses/:id", element: <Course /> }, | |
], | |
}, | |
{ path: "*", element: <NoMatch /> }, | |
], | |
}, | |
]; | |
// The useRoutes() hook allows you to define your routes as JavaScript objects | |
// instead of <Routes> and <Route> elements. This is really just a style | |
// preference for those who prefer to not use JSX for their routes config. | |
let element = useRoutes(routes); | |
return ( | |
<div> | |
<h1>Route Objects Example</h1> | |
<p> | |
This example demonstrates how to use React Router's "route object" API | |
instead of the JSX API to configure your routes. Both APIs are | |
first-class. In fact, React Router actually uses the object-based API | |
internally by creating route objects from your{" "} | |
<code><Route></code> | |
elements. | |
</p> | |
<p> | |
React Router exposes a <code>useRoutes()</code> hook that allows you to | |
hook into the same matching algorithm that <code><Routes></code>{" "} | |
uses internally to decide which <code><Route></code> to render. | |
When you use this hook, you get back an element that will render your | |
entire route hierarchy. | |
</p> | |
{element} | |
</div> | |
); | |
} | |
function Layout() { | |
return ( | |
<div> | |
<nav> | |
<ul> | |
<li> | |
<Link to="/">Home</Link> | |
</li> | |
<li> | |
<Link to="/courses">Courses</Link> | |
</li> | |
<li> | |
<Link to="/nothing-here">Nothing Here</Link> | |
</li> | |
</ul> | |
</nav> | |
<hr /> | |
<Outlet /> | |
</div> | |
); | |
} | |
function Home() { | |
return ( | |
<div> | |
<h2>Home</h2> | |
</div> | |
); | |
} | |
function Courses() { | |
return ( | |
<div> | |
<h2>Courses</h2> | |
<Outlet /> | |
</div> | |
); | |
} | |
function CoursesIndex() { | |
return ( | |
<div> | |
<p>Please choose a course:</p> | |
<nav> | |
<ul> | |
<li> | |
<Link to="react-fundamentals">React Fundamentals</Link> | |
</li> | |
<li> | |
<Link to="advanced-react">Advanced React</Link> | |
</li> | |
<li> | |
<Link to="react-router">React Router</Link> | |
</li> | |
</ul> | |
</nav> | |
</div> | |
); | |
} | |
function Course() { | |
let { id } = useParams<"id">(); | |
return ( | |
<div> | |
<h2> | |
Welcome to the {id!.split("-").map(capitalizeString).join(" ")} course! | |
</h2> | |
<p>This is a great course. You're gonna love it!</p> | |
<Link to="/courses">See all courses</Link> | |
</div> | |
); | |
} | |
function capitalizeString(s: string): string { | |
return s.charAt(0).toUpperCase() + s.slice(1); | |
} | |
function NoMatch() { | |
return ( | |
<div> | |
<h2>It looks like you're lost...</h2> | |
<p> | |
<Link to="/">Go to the home page</Link> | |
</p> | |
</div> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Reference | |
https://stackblitz.com/github/remix-run/react-router/tree/main/examples/search-params?file=src%2FApp.tsx | |
import * as React from "react"; | |
import { Link, Route, Routes, useSearchParams } from "react-router-dom"; | |
export default function App() { | |
return ( | |
<div> | |
<h1>Search Params Example</h1> | |
<p> | |
This example demonstrates a simple search page that makes a request for | |
user data to the GitHub API and displays information for that user on | |
the page. The example uses the <code>useSearchParams()</code> hook to | |
read and write the URL query string. | |
</p> | |
<Routes> | |
<Route path="/" element={<Home />} /> | |
<Route path="*" element={<NoMatch />} /> | |
</Routes> | |
</div> | |
); | |
} | |
function randomUser() { | |
let users = ["chaance", "jacob-ebey", "mcansh", "mjackson", "ryanflorence"]; | |
return users[Math.floor(Math.random() * users.length)]; | |
} | |
function Home() { | |
let [searchParams, setSearchParams] = useSearchParams(); | |
// searchParams is a URLSearchParams object. | |
// See https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams | |
let user = searchParams.get("user"); | |
let [userData, setUserData] = React.useState<any>(null); | |
React.useEffect(() => { | |
let abortController = new AbortController(); | |
async function getGitHubUser() { | |
let response = await fetch(`https://api.github.com/users/${user}`, { | |
signal: abortController.signal, | |
}); | |
if (!abortController.signal.aborted) { | |
let data = await response.json(); | |
setUserData(data); | |
} | |
} | |
if (user) { | |
getGitHubUser(); | |
} | |
return () => { | |
abortController.abort(); | |
}; | |
}, [user]); | |
function handleSubmit(event: React.FormEvent<HTMLFormElement>) { | |
event.preventDefault(); | |
let formData = new FormData(event.currentTarget); | |
let newUser = formData.get("user") as string; | |
if (!newUser) return; | |
setSearchParams({ user: newUser }); | |
} | |
function handleRandomSubmit(event: React.FormEvent<HTMLFormElement>) { | |
event.preventDefault(); | |
let newUser = randomUser(); | |
// our new random user is the same as our current one, let's try again | |
if (newUser === user) { | |
handleRandomSubmit(event); | |
} else { | |
setSearchParams({ user: newUser }); | |
} | |
} | |
return ( | |
<div> | |
<div style={{ display: "flex" }}> | |
<form onSubmit={handleSubmit}> | |
<label> | |
<input defaultValue={user ?? undefined} type="text" name="user" /> | |
</label> | |
<button type="submit">Search</button> | |
</form> | |
<form onSubmit={handleRandomSubmit}> | |
<input type="hidden" name="random" /> | |
<button type="submit">Random</button> | |
</form> | |
</div> | |
{userData && ( | |
<div | |
style={{ | |
padding: "24px", | |
margin: "24px 0", | |
borderTop: "1px solid #eaeaea", | |
display: "flex", | |
alignItems: "center", | |
gap: "16px", | |
}} | |
> | |
<img | |
style={{ borderRadius: "50%" }} | |
width={200} | |
height={200} | |
src={userData.avatar_url} | |
alt={userData.login} | |
/> | |
<div> | |
<h2>{userData.name}</h2> | |
<p>{userData.bio}</p> | |
</div> | |
</div> | |
)} | |
</div> | |
); | |
} | |
function NoMatch() { | |
return ( | |
<div> | |
<h2>Nothing to see here!</h2> | |
<p> | |
<Link to="/">Go to the home page</Link> | |
</p> | |
</div> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment