I am getting the error shown in the title, and don't know why. Basically I want to get back in the console log a set a values including the height of the links.
TypeError: Cannot read property 'getBoundingClientRect' of null
Why is that. Does it come from the useRef(null) assigned on the linksRef variable?
Here is the component!
import React, { useState, useRef, useEffect } from 'react'
import { FaBars, FaTwitter } from 'react-icons/fa'
import { links, social } from './data'
import logo from './logo.svg'
const Navbar = () => {
const [showLinks, setShowLinks] = useState(false)
const linksContainerRef = useRef(null)
const linksRef = useRef(null)
useEffect(() => {
// We' ll use the height of the links to
// adjust the height of the list container
const linkHeight = linksRef.current.getBoundingClientRect()
}, [showLinks])
return (
<nav>
<div className='nav-center'>
<div className='nav-header'>
<img src={logo} alt='logo' />
<button
className='nav-toggle'
onClick={() => {
setShowLinks(!showLinks)
}}
>
<FaBars />
</button>
</div>
{showLinks && (
<div
className='links-container'
ref={linksContainerRef}
// className={`${
// showLinks ? 'links-container show-container' : 'links-container'
// }`}
>
<ul className='links' ref={linksRef}>
{links.map((link) => {
const { id, url, text } = link
return (
<li key={id}>
<a href={url}>{text}</a>
</li>
)
})}
</ul>
</div>
)}
<ul className='social-icons'>
{social.map((socialIcon) => {
const { id, url, icon } = socialIcon
return (
<li key={id}>
<a href={url}>{icon}</a>
</li>
)
})}
</ul>
</div>
</nav>
)
}
export default Navbar
Thanks,
Theo.
Yes, do a not null check before accessing anything on a ref.current.
Related
I am creating a navigation bar with React that visually highlights the active component when it is scrolled into view by the user. So, they scroll to the 'About' component and the user icon would highlight and the home icon would return to normal. It looks like this:
I am trying to do this in 2 steps: (1) using the useInView hook in my App.jsx component to set the 'activeElement' with a useState hook when the user scrolls to the useInView {ref}, and (2) passing the 'activeElement' as a useState variable to the child Nav component via props and useEffect to update activeNav in the Nav component when the user is scrolling.
Here is my code for the App component, which I have been testing within the paragraph tags. Currently, activeElement is not being affected by scrolling.
const App = () => {
const { ref, inView, entry } = useInView({
/* Optional options */
threshold: 0,
});
const [activeElement, setActiveElement] = useState('#')
return (
<>
<Header ref={ref} setActiveElement={inView ? '#home' : '' }/>
<Nav activeElement={activeElement}/>
<About ref={ref} setActiveElement={inView ? '#about' : '' } />
<p>{activeElement}</p>
<Services ref={ref} setActiveElement={inView ? '#services' : '' } />
<Contact ref={ref} setActiveElement={inView ? '#contact' : '' }/>
<Footer />
<p>{activeElement}</p>
</>
)
}
export default App
And here is the code for my Nav component:
const Nav = ({activeElement}) => {
const [activeNav, setActiveNav] = useState('#');
useEffect(() => {
setActiveNav(activeElement);
})
return (
<nav>
<a href="#" onClick={() => setActiveNav('#')} className={activeNav === '#' ? 'active' : ''}><AiOutlineHome /></a>
<a href="#about" onClick={() => setActiveNav('#about')} className={activeNav === '#about' ? 'active' : ''}><AiOutlineUser /></a>
<a href="#experience" onClick={() => setActiveNav('#experience')} className={activeNav === '#experience' ? 'active' : ''}><HiOutlineBookOpen /></a>
<a href="#services" onClick={() => setActiveNav('#services')} className={activeNav === '#services' ? 'active' : ''}><FaUncharted /></a>
<a href="#contact" onClick={() => setActiveNav('#contact')} className={activeNav === '#contact' ? 'active' : ''}><RiMessage2Line /></a>
</nav>
)
}
export default Nav
What is wrong with my useInView execution? And am I passing the activeElement variable to the Nav component correctly?
Thanks for taking the time to read this through.
** SOLUTION: **
After some digging, and with thanks to the commenter, Ali Mirzaei, for helping to identify where the issue was occuring, we found 2 problems:
I needed a separate useInView hook for each element being observed.
Using 'ref' on a component call was creating an error: "Warning: Function components cannot be given refs. Attempts to access this ref will fail." So, I used the answer from https://stackoverflow.com/a/65756885/13471663 to pass the ref as a prop named innerRef
Working code is as follows:
const App = () => {
const { ref, inView } = useInView();
const { ref: ref1, inView: inView1 } = useInView();
const { ref: ref2, inView: inView2 } = useInView();
const { ref: ref3, inView: inView3 } = useInView();
const { ref: ref4, inView: inView4 } = useInView();
const [activeElement, setActiveElement] = useState('#')
useEffect(() => {
if (inView) {
setActiveElement('#home');
console.log('home');
};
if (inView1) {
setActiveElement('#about')
console.log('about');
};
if (inView2) {
setActiveElement('#experience')
console.log('experience');
};
if (inView3) {
setActiveElement('#services')
console.log('services');
};
if (inView4) {
setActiveElement('#contact')
console.log('contact');
};
}, [inView, inView1, inView2, inView3, inView4])
return (
<>
<Header innerRef={ref} />
<Nav activeElement={activeElement}/>
<About innerRef={ref1} />
<p>{activeElement} {inView.toString()}</p>
<Experience innerRef={ref2} />
<Services innerRef={ref3} />
<Contact innerRef={ref4} />
<Footer />
<p>{activeElement}</p>
</>
)
}
export default App
And for the Nav component:
const Nav = ({activeElement}) => {
const [activeNav, setActiveNav] = useState('#home');
useEffect(() => {
setActiveNav(activeElement);
})
return (
<nav>
<a href="#" onClick={() => setActiveNav('#home')} className={activeNav === '#home' ? 'active' : ''}><AiOutlineHome /></a>
<a href="#about" onClick={() => setActiveNav('#about')} className={activeNav === '#about' ? 'active' : ''}><AiOutlineUser /></a>
<a href="#experience" onClick={() => setActiveNav('#experience')} className={activeNav === '#experience' ? 'active' : ''}><HiOutlineBookOpen /></a>
<a href="#services" onClick={() => setActiveNav('#services')} className={activeNav === '#services' ? 'active' : ''}><FaUncharted /></a>
<a href="#contact" onClick={() => setActiveNav('#contact')} className={activeNav === '#contact' ? 'active' : ''}><RiMessage2Line /></a>
</nav>
)
}
export default Nav
And here is an example of the innerRef use from a component:
const About = ({ innerRef }) => {
return (
<section id='about'>
<div ref={innerRef}>
About
</div>
</section>
)
}
Hope that helps anyone out there experiencing the same issue!
fisrt of all what is setActiveElement={inView ? '#home' : '' } in your components?
you have to pass different refs for each component you want to track if it's in the viewport so form react-intersection-observer documents:
import * as React from "react";
// #ts-ignore Wrong type
import { createRoot } from "react-dom/client";
import { useInView } from "react-intersection-observer";
import ScrollWrapper from "./elements/ScrollWrapper";
import "./styles.css";
function App() {
const { ref, inView } = useInView({
threshold: 0
});
const { ref: ref2, inView: inView2 } = useInView({
threshold: 0
});
return (
<ScrollWrapper inView={inView}>
<div ref={ref} className="inview-block">
<h2>
Element is inside the viewport: <strong>{inView.toString()}</strong>
</h2>
</div>
<div ref={ref2} className="inview-block">
<h2>
Element is inside the viewport: <strong>{inView2.toString()}</strong>
</h2>
</div>
</ScrollWrapper>
);
}
const root = createRoot(document.getElementById("root"));
root.render(<App />);
I have two files Sidebar and UserDataElements.
I want to display data of UserDataElements into Sidebar
I have tried this
This is the main file where i am fetching both the files.
<Sidebar>
<UserDataElements></UserDataElements>
</Sidebar>
Sidebar.js
import React from "react";
import SidebarElements from "./SidebarElements";
const Sidebar = () => { return (
<div className="sideBar">
<SidebarElements></SidebarElements>
</div> ); };
export default Sidebar;
UserDataElements.js
import React from "react";
import userData from "./userData";
const UserDataElements = () => {
return (
<div>
UserData
<ul className="user-ul">
{userData.map((val, key) => {
return (
<li
key={key}
onClick={() => {
window.location.pathname = val.link;
}}
className="userDataList"
>
<div className="d-flex ">
<div id="name">{val.name}</div>
<div id="branch">{val.branch}</div>
</div>
</li>
);
})}
</ul>
</div>
);
};
export default UserDataElements;
You use props for that, which is just like an attribute in HTML, for example if you want to pass data from parent to child you can do it like this
<Sidebar>
<UserDataElements data1={"some_data"} data2={"another_data"}>
</UserDataElements>
</Sidebar>
And in UserDataElements you can access it using props
const UserDataElements = ({ data1, data2 }) => {
// Here data1 and data2 will contain the data you have sent from parent to child
....
}
Or let's say, you want to pass data from child to parent, perhaps on click or something, then you can do it like this
import React from "react";
import userData from "./userData";
const UserDataElements = ({ data1, data2, onItemClick }) => {
return (
<div>
UserData
<ul className="user-ul">
{userData.map((val, key) => {
return (
<li
key={key}
onClick={() => onItemClick && onItemClick(val, key)}
className="userDataList"
>
<div className="d-flex ">
<div id="name">{val.name}</div>
<div id="branch">{val.branch}</div>
</div>
</li>
);
})}
</ul>
</div>
);
};
export default UserDataElements;
Note this specific line
onClick={() => onItemClick && onItemClick(val, key)}
Here we are invoking parent callback method, but before that we check if it exist, and In parent component we can access it like
import React from "react";
import SidebarElements from "./SidebarElements";
const Sidebar = () => {
return (
<div className="sideBar">
<SidebarElements
onItemClick={(val, key) => {
// here you get access to data of clicked element from child to parent
}}
>
</SidebarElements>
</div>
);
};
export default Sidebar;
You should read more about component and props https://reactjs.org/docs/components-and-props.html
I have the following React component:
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const onClickButtonChange = () => {
let cardMore = document.querySelector(".card_more");
let cardMain = document.querySelector(".card_main");
cardMore.style.display = "block";
cardMain.style.display = "none";
};
return (
<div>
{data ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
<button onClick={onClickButtonChange}>More</button>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
My implementation of the More button needs to display additional features (the card_more block). Right now this function only works on the very first element. I understand that in React this can most likely be done more correctly, but I don’t know how, so I use CSS styles.
P.S Edited:
I tried to use React features, maybe someone can tell me or does it make sense?
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
const [show, setShow] = useState(false);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const handleMore = async () => {
if (show === true) {
setShow(false);
} else if (show === false || !data) {
const r = await fetch(url);
const newData = await r.json();
setData(newData);
setShow(true);
}
};
return (
<div>
{data && show ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
<button onClick={handleMore}>More</button>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
Youre right, this isn't the way you should do it in React. But your problem in your onClickButtonChange-Function is that youre only getting one element with document.querySelector(".card_more") and everytime you call it you get the same element back (No matter on which card you call it)
What you need to do is: Identify the single component elements. Thats most likely solved by passing a id/key value down via props and then putting this id on a parent-element (e.g. div.card) and you give it an id:
<div className="card card_main" id={props.keyvalue}>
....
</div>
And then in your onClickButtonChange-Function you call:
let cardMore = document.querySelector(`#${props.keyvalue} .card_more`);
...
This should give you the right element.
I'm pretty new to react, and I am trying to make an Accordion Component with multiple dropdowns. I am trying to load my data from my database. I had thought I got it to work because it showed my data in the correct areas, but when I refreshed the page I got an Uncaught TypeError: Cannot read properties of undefined (reading 'longdescription_title') error. I'm not sure why this is happening, and I would really appreciate any help or advice on how to fix this problem.
Thank you!
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { detailsProduct } from '../actions/productActions';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import '../components/Accordion.css'
import { IconContext } from 'react-icons';
import { FiPlus, FiMinus } from 'react-icons/fi';
export default function ProductScreen(props) {
const dispatch = useDispatch();
const productId = props.match.params.id;
const [accordionItems, setAccordionItems] = useState([]);
const [accordionTitles, setAccordionTitles] = useState([]);
const [clicked, setClicked] = useState(false);
const productDetails = useSelector((state) => state.productDetails);
const { loading, error, product } = productDetails;
const userSignin = useSelector((state) => state.userSignin);
const { userInfo } = userSignin;
useEffect(() => {
if (product) {
const accordionItems = [product.how_to_use];
accordionItems.unshift(product.ingredients);
accordionItems.unshift(product.longdescription);
setAccordionItems(accordionItems);
}
}, [product]);
useEffect(() => {
if (product) {
const accordionTitles = [product.how_to_use_title];
accordionTitles.unshift(product.ingredients_title);
accordionTitles.unshift(product.longdescription_title);
setAccordionTitles(accordionTitles);
}
}, [product]);
const Items = [...accordionItems];
const Titles = [...accordionTitles];
const accordion = [
{title: product.longdescription_title, body: product.longdescription},
{title: product.ingredients_title, body: product.ingredients},
{title: product.how_to_use_title, body: product.how_to_use},
]
const toggle = index => {
if (clicked === index) {
return setClicked(null);
}
setClicked(index);
};
return (
<div>
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<div>
<Link to="/body">Back to result</Link>
<div className="row top">
<div className="col-1">
<ul>
<li>
<h1>{product.name}</h1>
</li>
<li>
<div>
<IconContext.Provider value={{ color: 'black', size: '2vw' }}>
<div className="accordionSection">
<div className = "container">
{accordion && accordion.length ? (
accordion.map((item, index) => {
return (
<>
<div className = "wrap" onClick={() => toggle(index)} key={index}>
<h1>{item.title}</h1>
<span>{clicked === index ? <FiMinus /> : <FiPlus />}</span>
</div>
{clicked === index ? (
<div className="dropdown">
<p>{item.body}</p>
</div>
) :
null}
</>
);
})
) : (
<></>
)}
</div>
</div>
</IconContext.Provider>
</div>
</li>
</ul>
</div>
<li>
<button onClick={addToCartHandler} className="primary block">
Add to Cart
</button>
</li>
</>
)}
</ul>
</div>
</div>
</div>
);
}
Initialize the Items, Titles and accordion variables only when the product has been set (in the useEffect calls).
You should use a separate state to store the accordion array.
Also, no need to use separate useEffect calls:
let Items = [];
let Titles = [];
const [accordion, setAccordion] = useState([]);
useEffect(() => {
if (product) {
const accordionItems = [product.how_to_use];
accordionItems.unshift(product.ingredients);
accordionItems.unshift(product.longdescription);c
setAccordionItems(accordionItems);
Items = [...accordionItems];
const accordionTitles = [product.how_to_use_title];
accordionTitles.unshift(product.ingredients_title);
accordionTitles.unshift(product.longdescription_title);
setAccordionTitles(accordionTitles);
Titles = [...accordionTitles];
setAccordion([
{ title: product.longdescription_title, body: product.longdescription },
{ title: product.ingredients_title, body: product.ingredients },
{ title: product.how_to_use_title, body: product.how_to_use },
]);
}
}, [product]);
This is probably really easy, I want to show the info of a list item when a user clicks on the item, however, the code I have will open the info of all list items when clicked.
https://codesandbox.io/s/friendly-lichterman-j1vpd?file=/src/App.js:0-599
import React, { useState } from "react";
import "./styles.css";
const list = [1, 2, 3];
export default function App() {
const [active, setActive] = useState(false);
return (
<div>
{list.map((item, idx) => {
return (
<>
<li
onClick={() => {
setActive(!active);
}}
>
{item}
<div className={active ? "active" : "info"}>
{" "}
Info {idx + 1}
</div>
</li>
</>
);
})}
</div>
);
}
You are trying to toggle active only which is used by all items. Instead use index position to toggle.
I have explained the rest within the code using comments.
App.js
import React, { useState } from "react";
import "./styles.css";
const list = [1, 2, 3];
export default function App() {
const [active, setActive] = useState();
return (
<div>
{/* You forgot to add ul here */}
<ul>
{list.map((item, idx) => {
return (
// Added key to each child to avoid error. Use <React.Fragment/> instead of <>
<React.Fragment key={idx}>
<li
onClick={() => {
// Condition for toggling the lists.
// If current list is selected
if (active === idx) {
// change active to blank
setActive();
} else {
// change active to current index
setActive(idx);
}
}}
>
{item}
</li>
<div className={active === idx ? "info active" : "info"}>
{" "}
Info {idx + 1}
</div>
</React.Fragment>
);
})}
</ul>
</div>
);
}
Edited this css to avoid applying to another tag
style.css
.info.active {
display: flex;
}
You can try the above code on sandbox
https://codesandbox.io/s/stackoverflow-qno-65730790-tmeyf
Seems like a good use case for useReducer. It's a really useful tool to keep track of the states of multiple components. In your case you would need to track whether a given LI is active (showing information) or not. Here is how to do that with useReducer along with a Sanbdox
import React, { useState } from "react";
import "./styles.css";
const list = [1, 2, 3];
const default_states = list.map((item) => Object({ id: item, action: false }));
export default function App() {
const [li_states, dispatch] = React.useReducer((state, id) => {
return state.map((item) => {
if (item.id === id) return { id: item.id, active: !item.active };
else return item;
});
}, default_states);
return (
<div>
{list.map((item) => {
const cur = li_states.find((s) => s.id === item);
return (
<div key={item}>
<li
onClick={() => {
dispatch(item);
}}
>
{item}
</li>
<div className={cur.active ? "action" : "info"}> Info {item}</div>
</div>
);
})}
</div>
);
}
What's happening here is each time you click any of your LIs, the dispatch calls the reducer function inside React.useReducer with the ID of the LI and toggles the state of the clicked LI.
You can follow this method too
const list = ['Start', 'Installation', 'Text Banners', 'Image Banners'];
const links = ['/start', '/installation', '/textbanners', '/imagebanners'];
const [active, setActive] = useState(null)
const toggleActive = (e) => {
console.log(e)
setActive(e.target.innerText)
}
return (
<div className={style.dashboard_container}>
<div className={style.dashboard}>
<ul>
{list.map((item, index) => {
return (
<li className={active == item ? style.active : ''} key={index} onClick={toggleActive}>{item}</li>
)
})}
</ul>
</div>
{children}
</div>
);