How to make Categories button functional - javascript

I got my current website displaying hard coded data from my data.js file. I implemented firebase as my database and I want to have my categories button work with the firbase database. FYI, I'm fairly new to developing websites and apps. I'm going to attach my app.js and categories.js file.
Here is my code so far.
const allCategories = ["all", ...new Set(items.map((item) => item.category))];
const MenuApp = () => {
const [menuItems, setMenuItems] = useState(items);
const [activeCategory, setActiveCategory] = useState("");
const [categories, setCategories] = useState(allCategories);
const filterItems = (category) => {
setActiveCategory(category);
if (category === "all") {
setMenuItems(items);
return;
}
const newItems = items.filter((item) => item.category === category);
setMenuItems(newItems);
};
return (
<main>
<section className="menu section">
<div className="title">
<img src={logo} className="logo" />
<h2>Menu List</h2>
<div className="underline"></div>
</div>
<Categories
categories={categories}
activeCategory={activeCategory}
filterItems={filterItems}
/>
<Menu items={menuItems} />
</section>
</main>
);
}
export default App;
const Categories = ({ categories, filterItems, activeCategory }) => {
return (
<div className="btn-container">
{categories.map((category, index) => {
return (
<button
type="button"
className={`${
activeCategory === category ? "filter-btn active" : "filter-btn"
}`}
key={index}
onClick={() => filterItems(category)}
>
{category}
</button>
);
})}
</div>
);
};
export default Categories;
I attched screenshots of how my menu page looks like.

here is how I would do it
import React, { useState } from "react";
import Home from "./Home";
import Navigation from './Navigation';
import {BrowserRouter, Route, Routes, Link } from 'react-router-dom';
import About from './About';
import Fire from './firebase/firebase-test'
import FireMenu from './firebase/firebase-menu'
import Menu from './Menu';
import items from "./data";
import Categories from "./Categories";
import { Full_Menu } from "./firebase/firebaseConfig";
import logo from "./img/logo.jpg"; // this could be a placeholder until we get their official logo
function App () {
return (
<BrowserRouter>
<div className="App">
<Navigation />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/menu" element={<MenuApp />} />
<Route path= "/fire" element={<Fire />}/>
<Route path="/firemenu" element={<FireMenu />}/>
</Routes>
</div>
</BrowserRouter>
);
}
const allCategories = ["all", ...new Set(items.map((item) => item.category))];
const MenuApp = () => {
const [menuItems, setMenuItems] = useState(items)
const [activeCategory, setActiveCategory] = useState("all");
const filterItems = items?.filter(item => {
switch (activeCategory) {
case 'all':
return true
case 'all day specials':
case 'appetizers':
case 'add ins':
return item.category === activeCategory
default:
return true
}
})
return (
<main>
<section className="menu section">
<div className="title">
<h2>Menu List</h2>
<div className="underline"></div>
</div>
<Categories
setActiveCategory={setActiveCategory}
activeCategory={activeCategory}
/>
<Menu items={filterItems} />
</section>
</main>
);
};
export default App;
import React from "react";
const allCategories = ['all','all day specials','appetizers','seafood platter', 'add ins', 'beef', 'chicken', 'chow mein', 'combinations', 'deep fried goodiness', 'dessert', 'fried rice',
'pork', 'seafood platter', 'sides', 'vegetable', 'whats cooking', 'wing']
const Categories = ({ activeCategory, setActiveCategory, }) => {
return (
<div className="btn-container">
{allCategories.map((category, index) => {
return (
<button
type="button"
className={`${
activeCategory === category ? "filter-btn active" : "filter-btn"
}`}
key={index}
onClick={() => setActiveCategory(category)}
>
{category}
</button>
);
})}
</div>
);
};
export default Categories;

Related

How to pass the data using the id params

I been stuck in this for a few days now, i cant find the way to pass the data dinamically from my SlideShow component to a Detail component, I used this as a way to display and filter the category within the same component.
import React, {useState} from 'react'
import styles from '../slideshow/SlideShow.module.css'
import BackToTopButton from '../BackToTopButton'
import { portafolio } from "../../data/dummydata"
import {
generatePath,
useNavigate
} from "react-router-dom";
const allCategory = [...new Set(portafolio.map((item) => item.category))]
const SlideShow = () => {
const [list, setList] = useState(portafolio)
const [category] = useState(allCategory)
const filterItems = (category) => {
const newItems = portafolio.filter((item) => item.category === category)
setList(newItems)
}
const [id, setId] = useState();
const navigate = useNavigate();
const handleProceed = (e) => {
id && navigate(generatePath("/project/:id", { id, filterItems }));
};
return (
<>
<div name='top' className={styles.subnav}>
<div className={styles.var}>
<ul className={styles.menu}>
{category.map((category) => (
<li>
<button onClick={() => filterItems(category)}>{category}</button>
</li>
))}
</ul>
</div>
</div>
<div className={styles.slideshow}>
<div className={styles.container}>
{list.map((items, i) => (
<div>
<a href={`/project/${id}`} key={i} onClick={(e) => {
setId(items.id);
}}>
<img onClick={handleProceed} src={items.cover} alt='' />
</a>
<div className={styles.text}>
<h5>{items.shortdes}</h5>
</div>
</div>
))}
</div>
<BackToTopButton />
</div>
</>
)
}
export default SlideShow
and here is the component where i want to display it, but it only passes the id parameter but not the rest of the data
import React from 'react'
import NavBar from '../navbar/NavBar'
import Footer from '../footer/Footer'
import styles from "../projectinfo/Description.module.css"
import { useParams } from 'react-router-dom'
const Projectinfo = () => {
const { id } = useParams();
console.log(id);
return (
<div >
<NavBar />
<div className={styles.Description}>
<div>
<div className={styles.main}>
<h2>{id}</h2>
<h4>{id.description}</h4>
</div>
<div className={styles.slideshow}>
<img src={id.cover} alt='' />
</div>
<h4>{id.shortdes}</h4>
</div>
</div>
<Footer />
</div>
)
}
export default Projectinfo
and here is the index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Info from './Info';
import Menu from './Menu';
import ProjectInfo from './components/projectinfo/Projectinfo';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<Routes>
<Route path='/' exact element={<Menu />} />
<Route path="/project/:id" element={<ProjectInfo />} />
<Route path='/info' element={<Info />} />
</Routes>
</BrowserRouter>
);

BrowserRouter not loading pages [duplicate]

This question already has an answer here:
Why I receive blank page? React
(1 answer)
Closed 11 months ago.
I've used BrowserRouter here, and wrapped the within a tag.
But the problem is, the ShowTodoList page is not rendering.
Is this syntactically correct?
App.js file
import {BrowserRouter, Route, Routes} from 'react-router-dom';
import React, { lazy } from 'react';
import "./App.scss";
const ShowTodoList = lazy(() => import("./components/showTodoList"));
const CreateTodo = lazy(() => import("./components/createTodo"));
function App() {
return (
<div className="app-contents">
TODO - LIST
<BrowserRouter>
<Routes>
<Route path="/" component={ShowTodoList} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
ShowTodoList.jsx
import { useState, useEffect } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import { UpdateTodo } from "./updateTodo";
function TodoCard({ data, handleEdit, handleDelete }) {
const { _id, title, description } = data;
return (
<li key={_id}>
<div className="title-description">
<h3>{title}</h3>
<p>{description}</p>
</div>
<div className="button-container">
<button className="button" name={_id} onClick={handleEdit}>
edit
</button>
<button className="button" name={_id} onClick={handleDelete}>
delete
</button>
</div>
</li>
);
}
export function ShowTodoList() {
const [todo, setTodo] = useState([]);
const [open, setOpen] = useState(false);
const [id, setId] = useState("");
const [update, setUpdate] = useState(false);
useEffect(
function () {
axios
.get("http://localhost:8000/api/todo")
.then((res) => {
console.log(res.data);
setTodo(res.data);
})
.catch((err) => {
console.log(err.message);
});
},
[update]
);
function handleEdit(e) {
setId(e.target.name);
setOpen(true);
}
function handleUpdate() {
console.log("update:", update, !update);
setUpdate(!update);
}
function handleDelete(e) {
axios.delete(`http://localhost:8000/api/todo/${e.target.name}`);
setTodo((data) => {
return data.filter((todo) => todo._id !== e.target.name);
});
}
function handleClose() {
setId("");
setOpen(false);
}
return (
<section className="container">
<Link to="/create-todo" className="button-new">
<button className="button">New</button>
</Link>
<section className="contents">
<h1>TODO</h1>
<ul className="list-container">
{todo.map((data) => (
<TodoCard
data={data}
handleEdit={handleEdit}
handleDelete={handleDelete}
/>
))}
</ul>
</section>
{open ? (
<section className="update-container">
<div className="update-contents">
<p onClick={handleClose} className="close">
×
</p>
<UpdateTodo
_id={id}
handleClose={handleClose}
handleUpdate={handleUpdate}
/>
</div>
</section>
) : (
""
)}
</section>
);
}
I'm getting a blank page as below:enter image description here
Could anybody please check the codes and point out where I'm going wrong?
If you are using react-router version 6, we don't have component props in this version, and you should use element instead of it.
import { BrowserRouter, Route, Routes } from "react-router-dom";
import React, { lazy, Suspense } from "react";
const ShowTodoList = lazy(() => import("./components/showTodoList"));
const CreateTodo = lazy(() => import("./components/createTodo"));
function App() {
return (
<div className="app-contents">
TODO - LIST
<Suspense fallback={<div>Loading...</div>}>
<BrowserRouter>
<Routes>
<Route path="/" element={<ShowTodoList />} />
</Routes>
</BrowserRouter>
</Suspense>
</div>
);
}
export default App;

How can I add the item to cart page in react

I am trying to add the items to a cart page when a user clicks the add to cart button.
import React from "react";
import "bootstrap";
import { useParams } from "react-router-dom";
function ItemDetail(handleClick) {
const params = useParams();
let { productCode, vendor, value} = params;
let item = {productCode, vendor, value};
console.log(item);
return (
<>
<div>
<p>product id: {productCode}</p>
<p>price: {value}</p>
<p>vendor: {vendor}</p>
<button onClick={() => handleClick(item)}>Add to Cart</button>
</div>
</>
);
}
export default ItemDetail;
This is the cart page. Where I am to, render the item details from Item Details Page.
import React, { useState, useEffect } from "react";
const Cart = ({ cart, setCart, handleChange }) => {
const [price, setPrice] = useState(0);
const handleRemove = (id) => {
const arr = cart.filter((item) => item.id !== id);
setCart(arr);
handlePrice();
};
const handlePrice = () => {
let ans = 0;
cart.map((item) => (ans += item.amount * item.price));
setPrice(ans);
};
useEffect(() => {
handlePrice();
});
console.log(setCart);
return (
<article>
{cart.map((item) => (
<div className="cart_box" key={item.id}>
<div>
<button onClick={() => handleChange(item, 1)}>+</button>
<button>{item.amount}</button>
<button onClick={() => handleChange(item, -1)}>-</button>
</div>
<div>
<span>{item.price}</span>
<button onClick={() => handleRemove(item.id)}>Remove</button>
</div>
</div>
))}
<div className="total">
<span>Total Price of your Cart</span>
<span>R - {price}</span>
</div>
</article>
);
};
export default Cart;
This is my item description page. I have fetched the items using params, this is only way I found easier for me.
import React, { useState, useEffect } from "react";
import { Row, Col } from "react-bootstrap";
import StyledCard from "../components/Card";
const Discover = (props, params, handleClick) => {
const token = "not-the-actual-token";
const [result, setResult] = useState([]);
useEffect(() => {
fetch(
"https://api.flash-internal.flash-group.com/ecommerceManagement/1.0.0/api/product/",
{
method: "GET",
headers: { Authorization: `Bearer ${token}` },
}
)
.then((res) => res.json())
.then((json) => setResult(json));
}, []);
const cardStyle = {
listStyle: "none",
margin: 5,
paddingLeft: 0,
minWidth: 240,
};
return (
<>
<div className="latestdeals container my-5">
<h1>All Products</h1>
<Row className="hotcards">
<Col className="colcard">
{(result?.result || []).map((item) => (
<div key={item.productCode} style={cardStyle}>
<a href={`/itemDetail/${item.productCode}/${item.value}/${item.vendor}`}>
{" "}
<StyledCard
key={item.productCode}
name={item.vendor}
title={item.description}
price={item.value}
handleClick={handleClick}
item={item}
/>
</a>
</div>
))}
</Col>
</Row>
</div>
</>
);
};
export default Discover;
This is my App page
import "./index.scss";
import React, { useState } from "react";
import {
BrowserRouter as Router,
Route,
Routes,
useParams,
} from "react-router-dom";
import AllCategories from "./pages/all-catergories";
import Home from "./pages/home";
import Entertainment from "./pages/entertainment";
// import Cart from "./pages/_cart";
import Login from "./pages/login";
import Netflix from "./pages/netflix";
import Orders from "./pages/orders";
import SignUp from "./pages/sign-up";
// import Data2 from "./Data2";
import Products from "./pages/products";
// import Shop from "./components/Shop";
// import ProductDetail from "./pages/ProductDetail";
import Discover from "./pages/discover";
import ItemDetail from "./pages/itemDetail";
import Cart from "./pages/cart";
function App() {
const [show, setShow] = useState(true);
const [cart, setCart] = useState([]);
const handleClick = (item) => {
if (cart.indexOf(item) !== -1) return;
setCart([...cart, item]);
};
const handleChange = (item, d) => {
const ind = cart.indexOf(item);
const arr = cart;
arr[ind].amount += d;
if (arr[ind].amount === 0) arr[ind].amount = 1;
setCart([...arr]);
};
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="all-categories" exact element={<AllCategories />} />
{/* <Route path="cart" exact element={<Cart />} /> */}
<Route path="entertainment" exact element={<Entertainment />} />
<Route path="login" exact element={<Login />} />
<Route path="discover" exact element={<Discover />} />
<Route path="netflix" exact element={<Netflix />} />
<Route path="orders" exact element={<Orders />} />
<Route path="sign-up" exact element={<SignUp />} />
<Route path="products" element={<Products />} />
<Route path="/itemDetail/:productCode/:value/:vendor" element={<ItemDetail />} />
<Route path="/itemDetail/" element={<ItemDetail handleClick={handleClick} />} />
<Route path="/Cart/" exact element={<Cart cart={cart} setCart={setCart} handleChange={handleChange}/>} />
</Routes>
</Router>
);
}
export default App;
Issues
You've issues declaring React components, several of them aren't using props correctly. function ItemDetail(handleClick) { ... } should be function ItemDetail({ handleClick }) { ... }, and const Discover = (props, params, handleClick) => { ... } should probably be something like const Discover = ({ params, handleClick, ...props }) => { ... }. React components receive a single props object argument.
handleChange in App is also mutating state.
Solution
App
Fix the state mutation and ensure props are passed correctly to routed components. Use an item GUID to search the cart instead of shallow reference equality when checking to add to the cart. When updating cart quantities it is necessary to shallow copy the cart array and cart items that are being updated. Use functional state updates whenever possible so it's ensured it's updating from the previous state and not any stale state value closed over in scope.
function App() {
const [show, setShow] = useState(true);
const [cart, setCart] = useState([]);
const handleClick = (item) => {
// Update cart item quantity if already in cart
if (cart.some((cartItem) => cartItem.productCode === item.productCode)) {
setCart((cart) =>
cart.map((cartItem) =>
cartItem.productCode === item.productCode
? {
...cartItem,
amount: cartItem.amount + 1
}
: cartItem
)
);
return;
}
// Add to cart
setCart((cart) => [
...cart,
{ ...item, amount: 1 } // <-- initial amount 1
]);
};
const handleChange = (productCode, d) => {
setCart((cart) =>
cart.flatMap((cartItem) =>
cartItem.productCode === productCode
? cartItem.amount + d < 1
? [] // <-- remove item if amount will be less than 1
: [
{
...cartItem,
amount: cartItem.amount + d
}
]
: [cartItem]
)
);
};
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="all-categories" element={<AllCategories />} />
<Route path="entertainment" element={<Entertainment />} />
<Route path="login" element={<Login />} />
<Route path="discover" element={<Discover />} />
<Route path="netflix" element={<Netflix />} />
<Route path="orders" element={<Orders />} />
<Route path="sign-up" element={<SignUp />} />
<Route path="products" element={<Products />} />
<Route
path="/itemDetail/:productCode/:value/:vendor"
element={<ItemDetail handleClick={handleClick} />}
/>
<Route
path="/Cart/"
element={(
<Cart
cart={cart}
setCart={setCart}
handleChange={handleChange}
/>
)}
/>
</Routes>
</Router>
);
}
ItemDetail
Access/destructure the handleClick prop correctly. Pass the item's productCode to the callback.
function ItemDetail({ handleClick }) {
const { productCode, vendor, value} = useParams();
const item = { productCode, vendor, value };
return (
<div>
<p>product id: {productCode}</p>
<p>price: {value}</p>
<p>vendor: {vendor}</p>
<button onClick={() => handleClick(item)}>Add to Cart</button>
</div>
);
}
Discover
Correctly access/destructure the handleClick callback. Use the Link component instead of the raw anchor (<a />) tag. The anchor tag will reload the app which very likely isn't what you want to happen. Based on the code I suspect you don't actually need this handleClick since the ItemDetail component is passed it and adds to the cart
import { Link } from 'react-router-dom';
const cardStyle = {
listStyle: "none",
margin: 5,
paddingLeft: 0,
minWidth: 240,
};
const Discover = () => {
const token = "not-the-actual-token";
const [result, setResult] = useState([]);
useEffect(() => {
fetch(
"https://api.flash-internal.flash-group.com/ecommerceManagement/1.0.0/api/product/",
{
method: "GET",
headers: { Authorization: `Bearer ${token}` },
}
)
.then((res) => {
if (!res.ok) {
throw new Error('Network response was not OK');
}
return res.json();
})
.then((data) => setResult(data.result))
.catch(error => {
// handle any rejected Promises, errors, etc...
});
}, []);
return (
<div className="latestdeals container my-5">
<h1>All Products</h1>
<Row className="hotcards">
<Col className="colcard">
{result.map((item) => (
<div key={item.productCode} style={cardStyle}>
<Link to={`/itemDetail/${item.productCode}/${item.value}/${item.vendor}`}>
<StyledCard
name={item.vendor}
title={item.description}
price={item.value}
item={item}
/>
</Link>
</div>
))}
</Col>
</Row>
</div>
);
};
Cart
Don't store the cart total in state, it is easily derived from the cart state.
const Cart = ({ cart, setCart, handleChange }) => {
const handleRemove = (productCode) => {
setCart(cart => cart.filter(item => item.productCode !== productCode));
};
const price = cart.reduce((total, item) => total + item.amount * item.price, 0);
return (
<article>
{cart.map((item) => (
<div className="cart_box" key={item.id}>
<div>
<button onClick={() => handleChange(item.productCode, 1)}>+</button>
<button>{item.amount}</button>
<button onClick={() => handleChange(item.productCode, -1)}>-</button>
</div>
<div>
<span>{item.price}</span>
<button onClick={() => handleRemove(item.productCode)}>Remove</button>
</div>
</div>
))}
<div className="total">
<span>Total Price of your Cart</span>
<span>R - {price}</span>
</div>
</article>
);
};

Why useEffect not updating state?

The structure of my application is :
When the user clicks on StartQuiz button in<Home> component,he is directed to the '/quiz' route. <Quiz> component is rendered after that.
My <Quiz> component is supposed to get the questions from the App component but is not getting that. <App> component gets the questions from the <Home> Component..
The Code is as follows:
Home.js file:
import ErrorMessage from '../errormessage';
const Home = (props) => {
const {name,setName,fetchQuestions}=props;
const [category,setCategory]=useState("");
const [difficulty,setDifficulty]=useState("");
const [error,setError]=useState(false);
const history=useNavigate();
const handlesubmit=()=>{
if(!category || !name || !difficulty)
setError(true);
else
{
fetchQuestions(category,difficulty);
history('/quiz') //pushes it in the quiz route
}
}
return (
<div className="content">
<div className="settings">
<span style={{fontSize:30}}>Quiz Settings</span>
</div>
<div className="settingsselect">
{error && <ErrorMessage></ErrorMessage>}
<TextField label="Enter Your Name" variant="outlined" onChange={(e)=>setName(e.target.value)} value={name}/>
<TextField id="outlined-select-currency" select label="Select Category" variant="outlined" onChange={(e)=>setCategory(e.target.value)} value={category}>
{
Categories.map((cat)=>{
return(
<MenuItem key={cat.category} value={cat.value}>{cat.category}</MenuItem>);
})
}
</TextField>
<TextField select label="Select Difficulty" onChange={(e)=>setDifficulty(e.target.value)} value={difficulty}>
<MenuItem label="Easy" value="easy">Easy</MenuItem>
<MenuItem label="Medium" value="medium">Medium</MenuItem>
<MenuItem label="Hard" value="hard">Hard</MenuItem>
</TextField>
<Button variant="contained" color="primary" onClick={handlesubmit}>Start Quiz</Button>
</div>
<img src="question.svg" className="banner"></img>
</div>
);
};
export default Home;
App.js file:
const [questions,setQuestions]=useState();
useEffect(()=>{
console.log("Questions have changed");
},[questions]);
const fetchQuestions=async(category,difficulty)=>{
const {data}=await axios(`https://opentdb.com/api.php?amount=10&category=${category}&difficulty=${difficulty}&type=multiple`);
setQuestions(data.results);
}
return (
<BrowserRouter>
<div className="App" style={{backgroundImage: "url(./ques1.png)"}}>
<Header/>
<Routes>
<Route path="/home" exact element={<Home name={name} setName={setName} fetchQuestions={fetchQuestions}/>}></Route>
<Route path="/quiz" exact element={<Quiz name={name} questions={questions} score={score} setScore={setScore} />}></Route>
</Routes>
<Footer></Footer>
</div>
</BrowserRouter>
);
}
export default App;
Quiz.js file:
const Quiz = (props) => {
const {name,questions,score,setScore}=props;
const [options,setOptions]=useState();
const [currentQuestion,setCurrentQuestion]=useState(0);
return (
<div className='quiz'>
<span className="subtitle">Welcome ,{name}</span>
<div className="questionInfo">
<Question questions={questions} currentQuestion={currentQuestion} setCurrentQuestion={setCurrentQuestion} options={options}/>
</div>
</div>
);
};
export default Quiz;
But Im getting undefined when doing console.log(questions) in the <Quiz> component..
please figure out the issue..
When you call fetchQuestions(category,difficulty) in handleSubmit you need to await it there as well.
The await inside fetchQuestions does not extend outside of the function, so making handleSubmit async as well will properly await before navigating
// Home
const handlesubmit = async () => {
if(!category || !name || !difficulty) {
setError(true);
} else {
await fetchQuestions(category,difficulty);
history('/quiz')
}
}
Next, in Quiz you will need useState() and useEffect() to respond to the change of props.questions.
// Quiz
import React, {useState, useEffect} from 'react'
const Quiz = (props) => {
const [questionsDisplay, setQuestionsDisplay] = useState()
useEffect(() => {
const display = props.questions.map((q,idx) => (<div key={idx}>{q.question}</div>))
setQuestionsDisplay(display)
}, [props.questions])
return (
<div className='quiz'>
<span className="subtitle">Welcome ,{name}</span>
<div className="questionInfo">
{questionsDisplay}
</div>
</div>
);
};
export default Quiz;

React scroll nav using useRef in seperate routes

Futher to my last question here, I have been trying to map the refs to other routes. The scroll handler is working but ref.current is null. So I am looking for an answer to this dilema. Using no external dependencies, how can I fix this issue?
App.tsx
import React, { useEffect, useRef } from "react";
import { BrowserRouter, Route, NavLink, useLocation } from "react-router-dom";
import Home from "./pages/Home";
import "./styles.css";
const Header = ({ refs }) => {
const location = useLocation();
useEffect(() => {
console.log("location", location.pathname);
switch (location.pathname) {
case "/about":
scrollSmoothHandler(refs.aboutRef);
break;
case "/contact":
scrollSmoothHandler(refs.contactRef);
break;
case "/hero":
scrollSmoothHandler(refs.heroRef);
break;
default:
scrollSmoothHandler(refs.homeRef);
break;
}
}, [location, refs]);
const scrollSmoothHandler = ref => {
console.log("Triggered.");
console.log(ref.current);
//ref.current.scrollIntoView({ behavior: "smooth" });
};
return (
<>
<NavLink to="/hero" activeClassName="selected">
Hero
</NavLink>
<NavLink to="/about" activeClassName="selected">
About
</NavLink>
<NavLink to="/contact" activeClassName="selected">
Contact
</NavLink>
</>
);
};
function App() {
const homeRef = useRef(null);
const heroRef = useRef(null);
const aboutRef = useRef(null);
const contactRef = useRef(null);
return (
<div ref={homeRef} className="App">
<BrowserRouter>
<Header refs={{ aboutRef, contactRef, heroRef, homeRef }} />
<Route
exact
to="/"
refs={{ aboutRef, contactRef, heroRef, homeRef }}
component={Home}
/>
// More routes here.
</BrowserRouter>
</div>
);
}
export default App;
Home.tsx
import React, { Fragment, forwardRef, useRef } from "react";
import "../styles.css";
const Hero = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>Hero Section</h1>
</section>
);
});
const About = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>About Section</h1>
</section>
);
});
const Contact = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>Contact Section</h1>
</section>
);
});
function Home(refs) {
const heroRef = useRef(refs.heroRef);
const aboutRef = useRef(refs.aboutRef);
const contactRef = useRef(refs.contactRef);
return (
<Fragment>
<Hero ref={heroRef} />
<About ref={aboutRef} />
<Contact ref={contactRef} />
</Fragment>
);
}
export default Home;
You can find a link to my Code Sandbox: here. Forks are much appreciated.
You cannot pass refs as props to other components with the name prop without using forwardRef on the commponent. You need to assign another name to it in order for it to work, For example innerRefs.
Also to pass on refs as prop to the Route component, make use of render prop method
App.tsx
import React, { useEffect, useRef } from "react";
import { BrowserRouter, Route, NavLink, useLocation } from "react-router-dom";
import Home from "./pages/Home";
import "./styles.css";
const Header = ({ innerRefs }) => {
const location = useLocation();
useEffect(() => {
console.log("location", location.pathname);
switch (location.pathname) {
case "/about":
scrollSmoothHandler(innerRefs.aboutRef);
break;
case "/contact":
scrollSmoothHandler(innerRefs.contactRef);
break;
case "/hero":
scrollSmoothHandler(innerRefs.heroRef);
break;
default:
scrollSmoothHandler(innerRefs.homeRef);
break;
}
}, [location, innerRefs]);
const scrollSmoothHandler = innerRef => {
console.log("Triggered.");
console.log(innerRef.current);
innerRef.current.scrollIntoView({ behavior: "smooth" });
};
return (
<>
<NavLink to="/hero" activeClassName="selected">
Hero
</NavLink>
<NavLink to="/about" activeClassName="selected">
About
</NavLink>
<NavLink to="/contact" activeClassName="selected">
Contact
</NavLink>
</>
);
};
function App() {
const homeRef = useRef(null);
const heroRef = useRef(null);
const aboutRef = useRef(null);
const contactRef = useRef(null);
return (
<div ref={homeRef} className="App">
<BrowserRouter>
<Header innerRefs={{ aboutRef, contactRef, heroRef, homeRef }} />
<Route
exact
to="/"
render={routeProps => (
<Home
{...routeProps}
innerRefs={{ aboutRef, contactRef, heroRef, homeRef }}
/>
)}
/>
// More routes here.
</BrowserRouter>
</div>
);
}
export default App;
Home.tsx
import React, { Fragment, forwardRef, useRef } from "react";
import "../styles.css";
const Hero = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>Hero Section</h1>
</section>
);
});
const About = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>About Section</h1>
</section>
);
});
const Contact = forwardRef((props, ref) => {
return (
<section ref={ref}>
<h1>Contact Section</h1>
</section>
);
});
function Home({ innerRefs }) {
return (
<Fragment>
<Hero ref={innerRefs.heroRef} />
<About ref={innerRefs.aboutRef} />
<Contact ref={innerRefs.contactRef} />
</Fragment>
);
}
export default Home;
Working demo here

Categories