Passing ref as props to get offsetTop - javascript

I have one component which I want to get ref and pass it to other component (fixed to top navbar with opacity 0) to check when the offsetTop of this ref is smaller than window.pageYOffset and if it is, change my state and set opacity of my navbar to 1. The problem is when I pass the ref, the other components gets null causing errors. How do I pass this ref when it's not null?
const galleryRef = useRef(null);
<div className="gallery py-5 border" ref={galleryRef}>
<FixedNavbar ref={galleryRef} />
</div>
FixedNavBar.js
const handleFixedNavbar = () => {
if (window.pageYOffset > ref.current.offsetTop) {
setIsFixed(true);
} else {
setIsFixed(false);
}
};
useEffect(() => {
window.addEventListener('scroll', handleFixedNavbar);
return () => {
window.removeEventListener('scroll', handleFixedNavbar);
};
}, []);
<div ref={navMenusRef} className={`${isFixed ? 'fixed-nav' : ''}`}>
{navMenus.map((item, index) => (
<Link
key={index}
to={item.type}
data-id={item.id}
>
{item.name}
</Link>
))}
</div>

You can use a Callback Ref with useState() to cause a re-render when the ref is updated:
const [galleryRef, setGalleryRef] = useState(null);
<div className="gallery py-5 border" ref={setGalleryRef}>
<FixedNavbar galleryRef={galleryRef} />
</div>
The ref is passed as a standard prop (galleryRef), and the useEffect() is dependent on it, and would be called whenever it changes:
useEffect(() => {
if(!galleryRef) return; // do nothing when ref is not set
const handleFixedNavbar = () => {
setIsFixed(window.pageYOffset > galleryRef.offsetTop); // sets true or false according to condition
};
handleFixedNavbar(); initial set
window.addEventListener('scroll', handleFixedNavbar);
return () => {
window.removeEventListener('scroll', handleFixedNavbar);
};
}, [galleryRef]);

Related

run useState on unmounted component in React Native

I have a state which is used to render some components conditionally, I have the following structure:
const SearchScreen = () => {
const [isSearchBarFocused, setIsSearchBarFocused] = React.useState(false);
return (
<>
<SearchBar onFocus={setIsSearchBarFocused} />
{isSearchBarFocuse ? <SearchSuggestionList isSearchBarFocused={isSearchBarFocused} setIsSearchBarFocused={setIsSearchBarFocused} /> : null}
</>
)
}
As you can see in the code above, the state seter is shared by both components, when the <SearchBar /> is focused the state changes to true and when an option is selected from the <SearchSuggestionList /> the state change to false. The problem with this approach is that I need to run some API calls and other inner state updates in <SearchSuggestionList /> component but this is unmounted due to the conditional rendering then some processes are not reached to be carried out.
then how would I execute code that is inside a component that can be unmounted?
At SearchScreencomponent: You need to add event onBlur for search-input, it will auto trigger when click outside search-input. And add two events onChange and value to get the enter value from search-input. Finally, don't check isSearchBarFocused variable outside SearchSuggestionList component.
export default function SearchScreen() {
const [isSearchBarFocused, setIsSearchBarFocused] = useState(false);
const [searchBarValue, setSearchBarValue] = useState("");
const handleChangeSearchValue = (e) => {
setSearchBarValue(e.target.value);
};
const handleBlurSearch = () => {
setIsSearchBarFocused(false);
};
const handleFocusSearch = () => {
setIsSearchBarFocused(true);
};
return (
<div className="App">
<div className="searchbar-wrapper">
<input
className="search-input"
type="text"
onFocus={handleFocusSearch}
onChange={handleChangeSearchValue}
value={searchBarValue}
onBlur={handleBlurSearch}
/>
<SearchSuggestionList
isSearchBarFocused={isSearchBarFocused}
searchBarValue={searchBarValue}
/>
</div>
</div>
);
}
At SearchSuggestionList component: You need to create isSearchBarFocused and searchBarValue for Props. The display atrribute will change none or block depend on isSearchBarFocused
.search-list {
display: none;
}
.search-list.active {
display: block;
}
and if searchBarValue changes value, useEffect will trigger and re-call API
import { useEffect, useState } from "react";
const MOCKUP_DATA = [
{ id: 1, value: "Clothes" },
{ id: 2, value: "Dress" },
{ id: 3, value: "T-shirt" }
];
const SearchSuggestionList = ({ searchBarValue, isSearchBarFocused }) => {
const [suggestionData, setSuggestionData] = useState([]);
useEffect(() => {
// useEffect will call API first time and re-call every time `searchBarValue` changed value.
console.log("re-call api");
setSuggestionData(MOCKUP_DATA);
}, [searchBarValue]);
const handleClick = (value) => {
console.log(value);
};
return (
<ul className={`search-list ${isSearchBarFocused ? "active" : ""}`}>
{suggestionData.map((item) => (
<li key={item.id} onMouseDown={() => handleClick(item.value)}>
{item.value}
</li>
))}
</ul>
);
};
export default SearchSuggestionList;
Warning: If you use onClick to replace onMouseDown, it will not work. Because onBlur event of search-input will trigger before onClick event of the items in SearchSuggestionList
You can demo it at this link: https://codesandbox.io/s/inspiring-cori-m1bsv0?file=/src/App.jsx

I iterate over an array with map and then try to change a boolen of an object from false to true with an onClick. UI doesn't reflect it but log does

CodeSandbox - https://codesandbox.io/s/distracted-taussig-n7e7q3?file=/src/App.js
As you can see, I iterate over the flaggers array with .map and render <div>true</div> or <div onClick={() => setToTrue(flag)}>false</div>. I assumed that if I were to click the second div, the refer property of that flag would be set to true and the component would re-render, making the div change to <div>true</div> but that doesn't seem to be the case.
In the setToTrue function I console.log the post object, and I can see that the refer property of the second flag has changed to true, but it is not shown in the UI.
import "./styles.css";
export default function App() {
const post = {
flaggers: [
{
refer: false
},
{
refer: false
}
]
}
const setToTrue = (flag) => {
flag.refer = true;
console.log(post)
}
return (
<div className="App">
{post.flaggers.map((flag) => (
<div>
{flag.refer ? <div>true</div> : <div onClick={() => setToTrue(flag)}>false</div>}
</div>
))}
</div>
);
}
Well, that's not how react you have to setState value to trigger the component to rerender which will cause to UI change try the below code it will work fine as I set a value on onClick that causes the component to rerender in short. I would suggest reading the documentation before going into the coding and know-how react works
React Documentation
export default function App() {
const [post, setPost] = React.useState([
{
refer: false,
},
{
refer: false,
},
]);
const setToTrue = (boolFlag, index) => {
const tempPost = [...post];
tempPost[index].refer = boolFlag;
setPost(tempPost);
};
return (
<div className='App'>
{post.map((flag, index) => (
<div key={`${index}flag`}>
{flag.refer ? <div onClick={() => setToTrue(false, index)}>true</div> : <div onClick={() => setToTrue(true, index)}>false</div>}
</div>
))}
</div>
);
}

Cannot read property of undefined how to work around this in the DOM?

I execute a component and this component fills the value profilePicRef once. However, I only want to display the Upload button when profilePicRef.current.preview is also no longer zero. However, I always get the error message TypeError: Cannot read property 'preview' of undefined. My question is, how can I now say if it is undefined, then don't take it into account and if it is not zero show it.
<PhotoFileHandler ref={profilePicRef} />
{
profilePicRef.current.preview !== null &&
<button className="button is-primary is-outlined" type="butto"
onClick={() => { onClickUpload(profilePicRef);
setActiveModal(''); }}>
<i className="fas fa-file-image"></i> Upload</button>
}
PhotoFileHandler
import React, { useState, forwardRef, useImperativeHandle, } from "react";
function PhotoFileHandler(props, ref) {
const [picName, setPicName] = useState(null);
const [preview, setPreview] = useState(null);
const [isPreview, setIsPreview] = useState(true);
const fileSelectedHandler = (event) => {
....
setPicName(event.target.files[0].name);
setPreview(reader.result);
setIsPreview(true);
}
}
catch (err) {
}
};
useImperativeHandle(ref, () => ({
isPreview,
preview,
picName,
checkProfilPicture() {
if (!preview) {
setIsPreview(false);
return false;
}
else {
setIsPreview(true);
return true;
}
},
getImage() {
return preview
},
removePreview() {
setIsPreview(false)
setPreview(null);
setPicName(null);
}
}),
);
return (
<div>
<input class="file-input" type="file" name="resume" accept=".png,.jpg,.jpeg, .jfif"
onChange={fileSelectedHandler} />
</div>
);
};
// eslint-disable-next-line
PhotoFileHandler = forwardRef(PhotoFileHandler);
export default PhotoFileHandler;
Is important to do with ref?
Alternative 0: Without Ref, Parent State
Alternative with state, you can see how it works here: https://codesandbox.io/s/admiring-gates-ctw6m?file=/src/App.js) :
Include one variable "file" const [selectedFile, setSelectedFile] = React.useState(null)
Send the setter function to PhotoFileHandler, I did something like: <PhotoFileHandler onImage={setSelectedFile} />
In PhotoFileHandler I did:
const fileSelectedHandler = (event) => {
props.onImage(event.target.files[0]);
}
Alternative 1: Force update with Parent State
If you need the ref, one workaround can be the trigger it when change it, like: https://codesandbox.io/s/sleepy-butterfly-iqmw3?file=/src/App.js,
Define in your parent component one state: const [wasUpdated, setWasUpdated] = React.useState("");
Include this in your validation to show the preview and upload button:
profilePicRef.current &&
profilePicRef.current.preview !== null &&
wasUpdated && (
<AllHtml />
)
)
Send the setter to the children <PhotoFileHandler ref={profilePicRef} onChange={setWasUpdated} />
In the children component you can trigger the event after updating preview.
useEffect(() => {
props.onChange(preview);
}, [preview]);

How can I lift up the state from Child to Parent in my React app?

I need to lift up the state of my Child component to the Parent to be able to reset this within the resetTotals() function. Each child component within the map has a click counter, so this needs to be reset within the parent component onClick.
How can I pass up the state?
// Parent Functional Component
function Parent() {
const [calorieCount, setCalorieCount] = useState(0);
const [vitaminACount, setVitaminACount] = useState(0);
const [vitaminDCount, setVitaminDCount] = useState(0);
function updateTotals(calories = 0, vitaminA = 0, vitaminD = 0) {
setCalorieCount(prevCalorieCount => Math.round(prevCalorieCount + calories));
setVitaminACount(prevVitaminACount => Math.round((prevVitaminACount + vitaminA) * 100) / 100);
setVitaminDCount(prevVitaminDCount => Math.round((prevVitaminDCount + vitaminD) * 100) / 100);
}
function resetTotals() {
setCalorieCount(0);
setVitaminACount(0);
setVitaminDCount(0);
}
return (
<div className="App">
<main className="products-grid flex flex-wrap">
{FoodCards.map((item, i) => {
return <Child
key={item.id}
name={item.name}
img={item.img}
calories={item.calories}
vitamin_a={item.vitamin_a}
vitamin_d={item.vitamin_d}
updateTotals={updateTotals} />
})}
</main>
<footer>
<div
className="reset"
onClick={() => resetTotals()}
>Reset</div>
</footer>
</div>
);
}
export default App
// Child Functional Component
const Child = (props) => {
const [clickCount, setClickCount] = useState(0);
function handleUpdateTotals(calories, vitamin_a, vitamin_d) {
props.updateTotals(calories, vitamin_a, vitamin_d);
setClickCount(prevClickCount => prevClickCount + 1);
}
return (
<div
className="product"
onClick={() => handleUpdateTotals(props.calories, props.vitamin_a, props.vitamin_d)}
>
<p>{props.name}</p>
<p>{clickCount > 0 ? <p>Selected: {clickCount}</p> : <p>Not Selected</p>}</p>
<img src={props.img} alt="" />
</div>
);
}
You are already updating the parent state from the child in that code.
You are passing in a callback function as a property, then calling it by props.updateTotals(). That will then run the updateTotals function in parent.
Do the same for reset totals: pass the method in as a prop, and call it from the child.

Triggering useState

I have a component and render it conditionally with different props.
{activeNavItem === 'Concept Art' ? (
<Gallary
images={conceptArtImages}
sectionRef={sectionRef}
/>
) : (
<Gallary
images={mattePaintingImages}
sectionRef={sectionRef}
/>
)}
This component has useState(false) and useEffect hooks. useEffect determines when screen position reaches the dom element and it triggers useState to true: elementPosition < screenPosition. Then my state triggers class on dom element: state ? 'animationClass' : ''.
const Gallary = ({ images, sectionRef }) => {
const [isViewed, setIsViewed] = useState(false);
useEffect(() => {
const section = sectionRef.current;
const onScroll = () => {
const screenPosition = window.innerHeight / 2;
const sectionPosition = section.getBoundingClientRect().top;
console.log(screenPosition);
if (sectionPosition < screenPosition) setIsViewed(true);
};
onScroll();
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
};
}, [sectionRef]);
return (
<ul className="section-gallary__list">
{images.map((art, index) => (
<li
key={index}
className={`section-gallary__item ${isViewed ? 'animation--view' : ''}`}>
<img className="section-gallary__img" src={art} alt="concept art" />
</li>
))}
</ul>
);
};
Problem: it works on my first render. But when I toggle component with different props, my state iniatially is true and I haven't animation.
I notice that if I have two components(ComponentA, ComponentB) instead of one(ComponentA) it works fine.
try setting isViewed to false when your component is not in view like this:
if (sectionPosition < screenPosition && !isViewed){
setIsViewed(true);
}
else{
if(isViewed)
setIsViewed(false);
}
and you can do it like this:
if (sectionPosition < screenPosition && !isViewed){
setIsViewed(state=>!state);
}
else{
if(isViewed)
setIsViewed(state=>!state);
}
plus no need to render same component multiple times, you can change props only:
<Gallary
images={activeNavItem === 'ConceptArt'?conceptArtImages:mattePaintingImages}
sectionRef={sectionRef}
/>

Categories