I haven't found in the documentation or on the internet how to call the on() method in the react locomotive scroll. If you know how to do it, please advise.
const ref = useRef(null);
const options = {
smooth: true,
lerp: 0.08,
table: {
smooth: true
},
smartPhone: {
smooth: true
},
}
<LocomotiveScrollProvider options={options} containerRef={ref}>
<div className={demoFour.demo_four} data-scroll-container ref={ref}>
<div className={demoFour.demo_four__container}>
<section className={demoFour.home} data-scroll-section=""></section>
</div>
</div>
</LocomotiveScrollProvider>
Their documentation (https://www.npmjs.com/package/react-locomotive-scroll) shows that onUpdate will trigger on(). So binding a function to onUpdate handler should do what you are looking for.
const onUpdate = () => {
// callback function for on()
}
<LocomotiveScrollProvider
options={options}
containerRef={ref}
onUpdate={onUpdate}
>
<div className={demoFour.demo_four} data-scroll-container ref={ref}>
<div className={demoFour.demo_four__container}>
<section className={demoFour.home} data-scroll-section=""></section>
</div>
</div>
</LocomotiveScrollProvider>
Related
I'm trying to add an event listener for whole document in a component, Since I'm using multiple of those components its duplicating event listeners, Is there a way to optimize this?
Here is my component -
export default function Dropdown(props) {
const [showDropdown, setShowDropdown] = useState(false);
function closeDropdown(event) {
if (!event.target.parentElement.classList.contains('dropdown')) {
setShowDropdown(false);
}
}
useEffect(() => {
document.addEventListener('click', closeDropdown);
return () => {
document.removeEventListener('click', closeDropdown);
};
}, []);
return (
<div className='dropdown-container dropdown'>
// some html here
</div>
);
}
Parent Component -
export default function Preference() {
return (
<div className="container">
<Dropdown
list={Object.values(DIFFICULTY)}
placeholderText="difficulty"
size="size-1"
/>
<Dropdown
list={Object.values(CATEGORIES)}
placeholderText="category"
size="size-2"
/>
<Link to="/quiz">
<button className="btn-large">Take Quiz</button>
</Link>
</div>
);
}
Event listeners as seen in dev tools
I could potentially use custom hook for this but I'm not sure if it will not lead to duplicated listeners.
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>
);
}
I am getting this error while using useRef and useEffect in react js.
**how can i cleanup the useEffect in React js this is main topic of this all question **
Dropdown.js:9
Uncaught TypeError: Cannot read properties of null (reading 'contains')
at HTMLDocument.bodydroptoggler (Dropdown.js:9)
here is screenshot:
I am getting this error when i click on the button named as "drop toggler"
here is code of app.js
import React, { useState } from "react";
import Dropdown from "./components/Dropdown";
const options = [
{
label: "red color is selected",
value: "red",
},
{
label: "blue color is selected",
value: "blue",
},
{
label: "green color is seleted",
value: "green",
},
];
const App = () => {
const [dropactive, setDropactive] = useState(true);
return (
<div className="container ui">
<button
className="button ui"
onClick={() => setDropactive(!dropactive)}
>
drop toggler
</button>
{dropactive ? <Dropdown options={options} /> : null}
</div>
);
};
export default App;
and here is code of dropdown.js
import React, { useState, useRef, useEffect } from "react";
const Dropdown = ({ options }) => {
const [selected, setSelected] = useState(options[0]);
const [open, setOpen] = useState(false);
const ref = useRef();
useEffect(() => {
const bodydroptoggler = (event) => {
if (ref.current.contains(event.target)) {
return;
}
setOpen(false);
};
document.addEventListener("click", bodydroptoggler);
return () => {
document.removeEventListener("click", bodydroptoggler);
console.log("work");
};
}, []);
const RenderedOptions = options.map((option, index) => {
if (selected.value === option.value) {
return null;
} else {
return (
<div
className="item"
key={index}
onClick={() => {
setSelected(option);
}}
>
{option.label}
</div>
);
}
});
return (
<div ref={ref} className="ui form">
<div className="field">
<label className="text label">Select from here:</label>
<div
className={`ui selection dropdown ${
open ? "active visible" : ""
}`}
onClick={() => setOpen(!open)}
>
<i className="dropdown icon"></i>
<div className="text">{selected.label}</div>
<div className={`menu ${open ? "visible transition" : ""}`}>
{RenderedOptions}
</div>
</div>
</div>
</div>
);
};
export default Dropdown;
here is what i want to perform
i just want to hide that form by clicking on the button.
how you can run this project
just create a react app
put code of app.js to app.js of your project
dropdown.js inside the component folder
i hope this all detail will help you i you need anything more just commnet down
thanks in advance
Have you tried using optional chaining since ref.current might sometimes be undefined?
if (ref.current?.contains(event.target))
Here's a codesandbox link with the fix.
Also some additional context from React Ref docs on why sometimes the ref might be null
React will assign the current property with the DOM element when the component mounts, and assign it back to null when it unmounts.
EDIT:
This is whay useLayoutEffect is for. It runs it's contents (and cleanups) synchronously and avoids the race condition. Here's the stackblitz that proves it:
https://stackblitz.com/edit/react-5w7vog
Check out this post from Kent Dodd's as well:
One other situation you might want to use useLayoutEffect instead of useEffect is if you're updating a value (like a ref) and you want to make sure it's up-to-date before any other code runs. For example:
ORIGINAL ANSWER
It's complicated. Your code generally looks good so it took me a minute to understand why. But here's the why - the Dropdown component unmounts before the cleanup from the effect is run. So the click event still finds the handler, this time with a null reference for the ref (because the ref gets updated immediately).
Your code is correct, idomatic React - but this is an edge case that needs deeper understanding.
As the other answerer already mentioned, just add an optional check. But I thought you might like to know why.
This is one of the first times I am actually using React Hooks properly in a project so bear with me if I am not quite there.
In the component below, my aim is to display the <HelperTooltip> on load and when the scrolling div (not the window) scrolls I want to hide after it scrolls X amount of pixels.
My thought process is to create a useRef object on the scrolling <div/> element, which then I can add an event listens with a callback function which then can toggle the state to hide the <HelperTooltip>
I have created a Codesandbox below to try and demonstrate what I am trying to do. As you can see in the demo the node.addEventListener('click') is working fine, however when I try and call the node.addEventListener('scroll') it is not firing.
I'm not sure if I taking the wrong approach or not, any help will greatly be appreciated. In the codesandbox demo it is the react image that I trying to hide on scroll, not the <HelperTooltip>
CodeSandbox link: https://codesandbox.io/s/zxj322ln24
import React, { useRef, useCallback, useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = props => {
const [isLogoActive, toggleLogo] = useState(true);
const scrollElementRef = useCallback(node => {
node.addEventListener("click", event => {
console.log("clicked", event);
});
/*
I want to add the scroll event listener
here and the set the state isLogoActive to
false like the event listener above but the 'scroll' event
is firing --- see below on line 21
*/
// node.addEventListener("scroll", event => {
// console.log("scrolled", event);
// toggle log
// });
});
return (
<div className="scrolling-container">
<div ref={scrollElementRef} className="scrolling-element">
<p>top</p>
{isLogoActive && (
<div className="element-to-hide-after-scroll">
<img
style={{ width: "100px", height: "100px" }}
src="https://arcweb.co/wp-content/uploads/2016/10/react-logo-1000-transparent-768x768.png"
/>
</div>
)}
<p>bottom</p>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("app"));
An easier approach for this particular use case might be to use the onScroll prop and use the scrollTop property from the event target to figure out if you should hide the image or not.
Example
const { useState } = React;
const App = props => {
const [isLogoActive, setLogoActive] = useState(true);
const onScroll = e => {
setLogoActive(e.target.scrollTop < 100);
};
return (
<div onScroll={onScroll} style={{ height: 300, overflowY: "scroll" }}>
<p style={{ marginBottom: 200 }}>top</p>
<img
style={{
width: 100,
height: 100,
visibility: isLogoActive ? "visible" : "hidden"
}}
src="https://arcweb.co/wp-content/uploads/2016/10/react-logo-1000-transparent-768x768.png"
/>
<p style={{ marginTop: 200 }}>bottom</p>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Here is the correct way to bind the addEventListener on div using useRef()
import React, { useState, useEffect, useCallback, useRef } from 'react';
function ScrollingWrapper(props) {
const [hasScrolledDiv, setScrolled] = useState(false);
const scrollContainer = useRef(null);
const onScroll = useCallback((event) => {
if(event.target.scrollTop > 125){
setScrolled(true);
} else if(event.target.scrollTop < 125) {
setScrolled(false);
}
}, []);
useEffect(() => {
scrollContainerWrapper.current.addEventListener('scroll', onScroll);
return () => scrollContainerWrapper.current.removeEventListener('scroll', onScroll);
},[]);
return (
<div ref={scrollContainerWrapper}>
{props.children}
</div>
);
}
export default ScrollingWrapper;
Depending on your use case, it's usually also good to throttle scroll event listeners, so they don't run on every pixel change.
const App = props => {
const [isLogoActive, setLogoActive] = useState(true);
const onScroll = useMemo(() => {
const throttled = throttle(e => setLogoActive(e.target.scrollTop < 100), 300);
return e => {
e.persist();
return throttled(e);
};
}, []);
return (
<div onScroll={onScroll}>
<img
style={{ visibility: isLogoActive ? 'visible' : 'hidden' }}
src="https://arcweb.co/wp-content/uploads/2016/10/react-logo-1000-transparent-768x768.png"
/>
</div>
);
};
The throttle function is available in lodash.
In your example, the scroll is not triggered on the scrolling-element but on the scrolling-container so that's where you want to put your ref : https://codesandbox.io/s/ko4vm93moo :)
But as Throlle said, you could also use the onScroll prop !
I am having a scoping issue with Semantic UI React transitions. My issue is that transitions work fine but when I click one of the divs... ALL of them animate. I only want one of them to animate.
My code:
class App extends React.Component {
state = { animation: 'pulse', duration: 1000, visible: true }
toggleVisibility = () => this.setState({ visible: !this.state.visible })
render() {
const { animation, duration, visible } = this.state
return (
<Container>
<Transition animation={animation} duration={duration} visible={visible}>
<div className="card" content='Run' onClick={this.toggleVisibility}>
Testing
</div>
</Transition>
<Transition animation={animation} duration={duration} visible={visible}>
<div className="card" content='Run' onClick={this.toggleVisibility}>
Testing
</div>
</Transition>
</Container>
)
}
}
Codepen
You're giving the same visible value to both Transition elements at the same time. You need to refactor to something like this:
class App extends React.Component {
//Please don't use visible1 and visible2 as names!
state = { animation: 'pulse', duration: 1000, visible1: true, visible2:
toggleVisibility = (prop) => () => this.setState(state => ({ [prop]: !state[prop] }))
render() {
const { animation, duration, visible1, visible2 } = this.state
return (
<Container>
<Transition animation={animation} duration={duration} visible={visible1}>
<div className="card" content='Run' onClick={this.toggleVisibility("visible1")}>
Testing
</div>
</Transition>
<Transition animation={animation} duration={duration} visible={visible2}>
<div className="card" content='Run' onClick={this.toggleVisibility("visible2")}>
Testing
</div>
</Transition>
</Container>
)
}
}
I changed setState to its functional form because you're using the previous state to calculate the next one.
Since onClick takes a callback, I made toggleVisibility a curried function: it takes a prop name and returns a function which will be called on the click event. I'm also using an ES2015 feature called "computed property names" on [prop]: !state[prop]