Table of Contents:
Context:
- The ContextProvider manages state and provides it to the App
- The App and its child components use that the context to render the current state.
- When the ContextProvider fetches new data, the App is re-rendered
- The App components (a form, for example) can get the state-setter from context and update the state
Routing + Products:
- The user visits
/products
- The
Routes
component reacts, rendering theProducts
component - The
Products
page pulls the products from context and renders a list ofLink
components for each product - The user clicks on the fifth product link, taking them to
/products/5
- The
Routes
component reacts, rendering theProduct
component - The
Product
page pulls the products from Context, finds the product whose ID matches the path parameterid
, and renders the found product (or a fallback).
Context
is the "glue" that connects the "Provider" with the "Consumer"Context.Provider
manages state and determines the context valuesuseState
to create shared state values- If you want to fetch:
useEffect
to fetch on render and set the state
useContext
gets ("consumes") the Context values in child components
The ProductsContextProvider
creates the products
state, renders the ProductsContext.Provider
to provide the products
state through the contextValues
object, and fetches data from the API to update that state.
import { useEffect, useState } from "react";
import ProductsContext from "./ProductsContext"
import fetchData from '../utils/fetchData';
const ProductsContextProvider = ({ children }) => {
const [products, setProducts] = useState([])
const [error, setError] = useState(null);
useEffect(() => {
const doFetch = async () => {
const [data, error] = await fetchData('https://dummyjson.com/products')
if (data) setProducts(data.products);
if (error) setError(error);
}
doFetch();
}, [])
const contextValues = { products, error }
return (
<ProductsContext.Provider value={contextValues}>
{children}
</ProductsContext.Provider>
)
}
export default ProductsContextProvider
The Products
component uses the context value products
to render a <Link>
for each product.
import { Link } from "react-router-dom";
import ProductsContext from "../context/ProductsContext";
import { useContext } from "react";
const Products = () => {
// use destructuring to get the products
const { products } = useContext(ProductsContext);
// Use the products to render a list of links
return (
<>
<h1>Products</h1>
<ul>
{
products.map((product) => {
return (
<li>
<Link to={`/products/${product.id}`}>
{product.title}
</Link>
</li>
)
})
}
</ul >
</>
)
}
export default Products;
Link
s provide navigation ("take me to/about
")Routes
/Route
respond to changes in navigation ("I'm at/about
, show me the<About />
component")- Path parameters allows the URL to communicate which resource is being displayed:
/products/:id
lets the user navigate to a URL like/products/4
to communicate that they want the product with theid
of 4
useParams()
returns an object with key:value pairs for the path parameters present in the current URL
A main.jsx
file that uses both a BrowserRouter and context:
import { BrowserRouter } from 'react-router-dom'
import ProductsContextProvider from './context/ProductsContextProvider.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter >
<ProductsContextProvider>
<App />
</ProductsContextProvider>
</BrowserRouter >
)
The App
component defines the routes. The NavBar
component is rendered regardless of the current browser location (the current URL).
function App() {
return (
<>
<NavBar />
<main>
<Routes>
<Route path='/' element={<Dashboard />} />
<Route path='/about' element={<About />} />
<Route path='/contacts' element={<Contacts />} />
<Route path='/products' element={<Products />} />
<Route path='/products/:id' element={<Product />} />
<Route path='*' element={<NotFound />} />
</Routes>
</main>
<footer></footer>
</>
)
}
The NavBar
component has Link
components that can take the user to another page without causing the page to reload or fetch a new resource:
import { Link } from "react-router-dom";
const NavBar = () => {
return (<nav>
<ul>
<li><Link to='/'>Dashboard</Link></li>
<li><Link to='/about'>About</Link></li>
<li><Link to='/contacts'>Contacts</Link></li>
<li><Link to='/products'>Products</Link></li>
<li><Link to='https://www.reddit.com/'>Reddit</Link></li>
</ul>
</nav>
)
}
The Product
component is rendered for the path '/products/:id'
where id
is the path parameter. The component uses useParams
to get the params.id
value. It also pulls products
data from the context and uses the params.id
value to find the matching product and render its data (or a fallback message).
import { useParams, Navigate, Link } from "react-router-dom";
import ProductsContext from "../context/ProductsContext";
import { useContext } from "react";
const Product = () => {
const contextValues = useContext(ProductsContext);
// If the URL is `/products/3` then `params.id` is `"3"`
const params = useParams();
const product = contextValues.products.find((product) => product.id === Number(params.id))
if (!product) return (
<>
<p>product not found</p>
<Link to='/'>Go Home</Link>
</>
)
return (
<>
<h1>{product.title}</h1>
<p>{product.description}</p>
<p>{product.price}</p>
<p>Rating: {product.rating}</p>
<img src={product.images[0]} />
</>
)
}
export default Product;