I have written the following code to animate three Lottie icons in a Gatsby project. The code works as expected: the icon starts moving when the user hovers on the element and stops playing when the element is not hovered anymore. Is there a way though to reuse the same function for onMouseLeave and onMouseEnter to animate all three icons (not all at once but separately)? Right now I have specified different functions for each icon but it feels like the code could be shorter.
import React, { createRef, useEffect } from "react"
import lottie from "lottie-web"
import heart from "../assets/data/heart.json"
import wine from "../assets/data/wine.json"
import party from "../assets/data/party.json"
import { useStaticQuery, graphql } from "gatsby"
import styled from "styled-components"
import { StaticImage } from "gatsby-plugin-image"
export const query = graphql`
{
contentfulMatrimonio {
titolo
sottotitolo
}
}
`
const Wedding = () => {
const data = useStaticQuery(query)
let animationContainer1 = createRef()
let animationContainer2 = createRef()
let animationContainer3 = createRef()
let heartPlay = null
let winePlay = null
let partyPlay = null
useEffect(() => {
let heartIcon = {
container: animationContainer1.current,
animationData: heart, //animation file
renderer: "svg",
loop: true,
autoplay: false,
}
let wineIcon = {
container: animationContainer2.current,
animationData: wine, //animation file
renderer: "svg",
loop: true,
autoplay: false,
}
let partyIcon = {
container: animationContainer3.current,
animationData: party, //animation file
renderer: "svg",
loop: true,
autoplay: false,
}
heartPlay = lottie.loadAnimation(heartIcon)
winePlay = lottie.loadAnimation(wineIcon)
partyPlay = lottie.loadAnimation(partyIcon)
}, [])
function startHeartAnimation() {
heartPlay.play()
}
function stopHeartAnimation() {
heartPlay.pause()
}
function startWineAnimation() {
winePlay.play()
}
function stopWineAnimation() {
winePlay.pause()
}
function startPartyAnimation() {
partyPlay.play()
}
function stopPartyAnimation() {
partyPlay.pause()
}
return (
<Wrapper>
<div className="title-container">
<h3>{data.contentfulMatrimonio.titolo}</h3>
<p>{data.contentfulMatrimonio.sottotitolo}</p>
</div>
<div className="program-container">
<StaticImage
src="../assets/images/spots.png"
placeholder="tracedSVG"
layout="constrained"
className="background-style"
/>
<div
className="program-card"
onMouseEnter={startHeartAnimation}
onMouseLeave={stopHeartAnimation}
>
<div className="animation-container" ref={animationContainer1}></div>
<p className="time-style">ore 17:00</p>
<p>cerimonia</p>
</div>
<div
className="program-card"
onMouseEnter={startWineAnimation}
onMouseLeave={stopWineAnimation}
>
<div className="animation-container" ref={animationContainer2}></div>
<p className="time-style">ore 19:00</p>
<p>cena</p>
</div>
<div
className="program-card"
onMouseEnter={startPartyAnimation}
onMouseLeave={stopPartyAnimation}
>
<div className="animation-container" ref={animationContainer3}></div>
<p className="time-style">ore 22:00</p>
<p>tutti si balla</p>
</div>
</div>
</Wrapper>
)
}
Handling Events,you can bind event with param by using arrow function, like this.
const Hello = () => {
const startWineAnimation = (param) => {
switch (param) {
case 1:
//do div1
break;
case 2:
//do div2
break;
case 3:
//do div3
break;
default:
break;
}
};
return (
<div>
<div className="div1" onMouseEnter={() => startWineAnimation(1)}>
123
</div>
<div className="div2" onMouseEnter={() => startWineAnimation(2)}>
456
</div>
<div className="div3" onMouseEnter={() => startWineAnimation(3)}>
789
</div>
</div>
);
};
Related
I have a componenet that wraps its children and slides them in and out based on the stage prop, which represents the active child's index.
As this uses a .map() to wrap each child in a div for styling, I need to give each child a key prop. I want to assign a random key as the children could be anything.
I thought I could just do this
key={`pageSlide-${uuid()}`}
but it causes an infinite loop/React to freeze and I can't figure out why
I have tried
Mapping the children before render and adding a uuid key there, calling it via key={child.uuid}
Creating an array of uuids and assigning them via key={uuids[i]}
Using a custom hook to store the children in a state and assign a uuid prop there
All result in the same issue
Currently I'm just using the child's index as a key key={pageSlide-${i}} which works but is not best practice and I want to learn why this is happening.
I can also assign the key directly to the child in the parent component and then use child.key but this kinda defeats the point of generating the key
(uuid is a function from react-uuid, but the same issue happens with any function including Math.random())
Here is the full component:
import {
Children,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import PropTypes from "prop-types";
import uuid from "react-uuid";
import ProgressBarWithTicks from "./ProgressBarWithTicks";
import { childrenPropType } from "../../../propTypes/childrenPropTypes";
const calculateTranslateX = (i = 0, stage = 0) => {
let translateX = stage === i ? 0 : 100;
if (i < stage) {
translateX = -100;
}
return translateX;
};
const ComponentSlider = ({ stage, children, stageCounter }) => {
const childComponents = Children.toArray(children);
const containerRef = useRef(null);
const [lastResize, setLastResize] = useState(null);
const [currentMaxHeight, setCurrentMaxHeight] = useState(
containerRef.current?.childNodes?.[stage]?.clientHeight
);
const updateMaxHeight = useCallback(
(scrollToTop = true) => {
if (scrollToTop) {
window.scrollTo(0, 0);
}
setCurrentMaxHeight(
Math.max(
containerRef.current?.childNodes?.[stage]?.clientHeight,
window.innerHeight -
(containerRef?.current?.offsetTop || 0) -
48
)
);
},
[stage]
);
useEffect(updateMaxHeight, [stage, updateMaxHeight]);
useEffect(() => updateMaxHeight(false), [lastResize, updateMaxHeight]);
const resizeListener = useMemo(
() => new MutationObserver(() => setLastResize(Date.now())),
[]
);
useEffect(() => {
if (containerRef.current) {
resizeListener.observe(containerRef.current, {
childList: true,
subtree: true,
});
}
}, [resizeListener]);
return (
<div className="w-100">
{stageCounter && (
<ProgressBarWithTicks
currentStage={stage}
stages={childComponents.length}
/>
)}
<div
className="position-relative divSlider align-items-start"
ref={containerRef}
style={{
maxHeight: currentMaxHeight || null,
}}>
{Children.map(childComponents, (child, i) => (
<div
key={`pageSlide-${uuid()}`}
className={`w-100 ${
stage === i ? "opacity-100" : "opacity-0"
} justify-content-center d-flex`}
style={{
zIndex: childComponents.length - i,
transform: `translateX(${calculateTranslateX(
i,
stage
)}%)`,
pointerEvents: stage === i ? null : "none",
cursor: stage === i ? null : "none",
}}>
{child}
</div>
))}
</div>
</div>
);
};
ComponentSlider.propTypes = {
children: childrenPropType.isRequired,
stage: PropTypes.number,
stageCounter: PropTypes.bool,
};
ComponentSlider.defaultProps = {
stage: 0,
stageCounter: false,
};
export default ComponentSlider;
It is only called in this component (twice, happens in both instances)
import { useEffect, useReducer, useState } from "react";
import { useParams } from "react-router-dom";
import {
FaCalendarCheck,
FaCalendarPlus,
FaHandHoldingHeart,
} from "react-icons/fa";
import { IoIosCart } from "react-icons/io";
import { mockMatches } from "../../../templates/mockData";
import { initialSwapFormState } from "../../../templates/initalStates";
import swapReducer from "../../../reducers/swapReducer";
import useFetch from "../../../hooks/useFetch";
import useValidateFields from "../../../hooks/useValidateFields";
import IconWrap from "../../common/IconWrap";
import ComponentSlider from "../../common/transitions/ComponentSlider";
import ConfirmNewSwap from "./ConfirmSwap";
import SwapFormWrapper from "./SwapFormWrapper";
import MatchSwap from "../Matches/MatchSwap";
import SwapOffers from "./SwapOffers";
import CreateNewSwap from "./CreateNewSwap";
import smallNumberToWord from "../../../functions/utils/numberToWord";
import ComponentFader from "../../common/transitions/ComponentFader";
const formStageHeaders = [
"What shift do you want to swap?",
"What shifts can you do instead?",
"Pick a matching shift",
"Good to go!",
];
const NewSwap = () => {
const { swapIdParam } = useParams();
const [formStage, setFormStage] = useState(0);
const [swapId, setSwapId] = useState(swapIdParam || null);
const [newSwap, dispatchNewSwap] = useReducer(swapReducer, {
...initialSwapFormState,
});
const [matches, setMatches] = useState(mockMatches);
const [selectedMatch, setSelectedMatch] = useState(null);
const [validateHook, newSwapValidationErrors] = useValidateFields(newSwap);
const fetchHook = useFetch();
const setStage = (stageIndex) => {
if (!swapId && stageIndex > 1) {
setSwapId(Math.round(Math.random() * 100));
}
if (stageIndex === "reset") {
setSwapId(null);
dispatchNewSwap({ type: "reset" });
}
setFormStage(stageIndex === "reset" ? 0 : stageIndex);
};
const saveMatch = async () => {
const matchResponse = await fetchHook({
type: "addSwap",
options: { body: newSwap },
});
if (matchResponse.success) {
setStage(3);
} else {
setMatches([]);
dispatchNewSwap({ type: "setSwapMatch" });
setStage(1);
}
};
useEffect(() => {
// set matchId of new selected swap
dispatchNewSwap({ type: "setSwapMatch", payload: selectedMatch });
}, [selectedMatch]);
return (
<div>
<div className="my-3">
<div className="d-flex justify-content-center w-100 my-3">
<ComponentSlider stage={formStage}>
<IconWrap colour="primary">
<FaCalendarPlus />
</IconWrap>
<IconWrap colour="danger">
<FaHandHoldingHeart />
</IconWrap>
<IconWrap colour="warning">
<IoIosCart />
</IconWrap>
<IconWrap colour="success">
<FaCalendarCheck />
</IconWrap>
</ComponentSlider>
</div>
<ComponentFader stage={formStage}>
{formStageHeaders.map((x) => (
<h3
key={`stageHeading-${x.id}`}
className="text-center my-3">
{x}
</h3>
))}
</ComponentFader>
</div>
<div className="mx-auto" style={{ maxWidth: "400px" }}>
<ComponentSlider stage={formStage} stageCounter>
<SwapFormWrapper heading="Shift details">
<CreateNewSwap
setSwapId={setSwapId}
newSwap={newSwap}
newSwapValidationErrors={newSwapValidationErrors}
dispatchNewSwap={dispatchNewSwap}
validateFunction={validateHook}
setStage={setStage}
/>
</SwapFormWrapper>
<SwapFormWrapper heading="Swap in return offers">
<p>
You can add up to{" "}
{smallNumberToWord(5).toLowerCase()} offers, and
must have at least one
</p>
<SwapOffers
swapId={swapId}
setStage={setStage}
newSwap={newSwap}
dispatchNewSwap={dispatchNewSwap}
setMatches={setMatches}
/>
</SwapFormWrapper>
<SwapFormWrapper>
<MatchSwap
swapId={swapId}
setStage={setStage}
matches={matches}
selectedMatch={selectedMatch}
setSelectedMatch={setSelectedMatch}
dispatchNewSwap={dispatchNewSwap}
saveMatch={saveMatch}
/>
</SwapFormWrapper>
<SwapFormWrapper>
<ConfirmNewSwap
swapId={swapId}
setStage={setStage}
selectedSwap={selectedMatch}
newSwap={newSwap}
/>
</SwapFormWrapper>
</ComponentSlider>
</div>
</div>
);
};
NewSwap.propTypes = {};
export default NewSwap;
One solution
#Nick Parsons has pointed out I don't even need a key if using React.Children.map(), so this is a non issue
I'd still really like to understand what was causing this problem, aas far as I can tell updateMaxHeight is involved, but I can't quite see the chain that leads to an constant re-rendering
Interstingly if I use useMemo for an array of uuids it works
const uuids = useMemo(
() => Array.from({ length: childComponents.length }).map(() => uuid()),
[childComponents.length]
);
/*...*/
key={uuids[i]}
I am trying to build a component that will display a progress bar at the top of a component that is being wrapped by it and I can not figure out why it is not active until I click one of the buttons rendered by Pagination component. The code is:
export class IntersectionHandler {
element: HTMLElement;
observer: IntersectionObserver;
listener: void;
actualState: {
progress: 0;
isIntersecting: false;
};
constructor(element: HTMLElement) {
this.element = element;
this.observer = new IntersectionObserver(this.handleObserver);
this.listener = document.addEventListener('scroll', this.onScroll);
this.actualState = {
progress: 0,
isIntersecting: false,
};
}
private handleObserver(entries: IntersectionObserverEntry[]) {
console.log(entries);
}
private startListeningScroll() {}
onScroll() {
console.log('ev', window.screenY);
console.log('ev', window.scrollY);
}
private onIntersect() {}
}
const WithPogressBar: React.FC = ({ children }) => {
const triggerEl = useRef<HTMLDivElement | null>(null);
if (triggerEl.current) {
const ih = new IntersectionHandler(triggerEl.current);
ih.onScroll();
}
return (
<div>
<div className="progress_bar" />
<div ref={triggerEl}>{children}</div>
</div>
);
};
const App: React.FC = () => {
const [state, actions] = usePagination(mockData);
const { paginatedChunk } = state;
return (
<div className="App">
<Pagination state={state} actions={actions} />
<div style={{ height: '1000px' }}>dfsdf</div>
<WithPogressBar>
<PaginatedTable {...paginatedChunk} />
</WithPogressBar>
</div>
);
};
export default App;
As you can see I have PaginatedTable wrapped by WithProgressBar and I log to the console some data in WithProgressBar component, but nothing is being logged until I click on the Pagination component's button, and when I click it, the first value is being logged three times.
Could someone explain me why it is happening and what should I do to have my progress bar working straight a way on page load without user clicking anything?
Thanks
I am trying to make a simple 'Nonogram'/'Picross' game using React to learn UseContext and UseReducer, but am puzzled as to why my top component (App) is not re-rendering when a value it uses changes. Perhaps I am missing something basic, but I've read through documentation and examples online and can't see why it is not re-rendering.
Expectation: User goes on the application, clicks on the squares to change their value (draw a cross by clicking on the squares), and the text underneath the board reads "Congratulations!", as it is based on the value of 'isComplete'
Problem: As above, but 'Keep trying' remains.
I added a button to see the boardState as defined in the UseReducer function, too.
Code is as follows:
App.js
import './App.css';
import { useReducer } from 'react';
import Table from './Table';
import BoardContext from './BoardContext';
import boardReducer from './BoardReducer';
function App() {
//Puzzle layout
const puzzleArray = [
[true, false, true],
[false, true, false],
[true, false, true]
];
//Creating a set of blank arrays to start the game as the userSelection
const generateUserSelection = () => {
const userSelection = [];
puzzleArray.forEach(row => {
let blankRow = [];
row.forEach(square => {
blankRow.push(false)
});
userSelection.push(blankRow);
})
return userSelection;
};
//Initial Context value
const boardInfo = {
puzzleName: "My Puzzle",
puzzleArray: puzzleArray,
userSelection: generateUserSelection(),
isComplete: false
};
const [ boardState, dispatch ] = useReducer(boardReducer, boardInfo)
return (
<BoardContext.Provider value={{board: boardState, dispatch}}>
<div className="App">
<header className="App-header">
<p>
Picross
</p>
<Table />
</header>
<div>
{boardState.isComplete ?
<div>Congratulations!</div>
: <div>Keep trying</div>
}
</div>
<button onClick={() => console.log(boardState)}>boardState</button>
</div>
</BoardContext.Provider>
);
}
export default App;
Table.jsx:
import { useContext, useEffect } from 'react';
import './App.css';
import Square from './Square';
import BoardContext from './BoardContext';
function Table() {
useEffect(() => {console.log('table useEffect')})
const { board } = useContext(BoardContext);
const generateTable = solution => {
const squareLayout = []
for (let i = 0; i < solution.length; i++) {
const squares = []
for (let j = 0; j < solution[i].length; j++) {
squares.push(
<Square
position={{row: i, column: j}}
/>
);
};
squareLayout.push(
<div className="table-row">
{squares}
</div>
);
};
return squareLayout;
};
return (
<div className="grid-container">
{generateTable(board.puzzleArray)}
</div>
);
}
export default Table;
Square.jsx
import { useContext, useState } from 'react';
import './App.css';
import BoardContext from './BoardContext';
function Square(props) {
const { board, dispatch } = useContext(BoardContext)
const [ isSelected, setIsSelected ] = useState(false);
const { position } = props;
const handleToggle = () => {
console.log(board)
board.userSelection[position.row][position.column] = !board.userSelection[position.row][position.column]
dispatch(board);
setIsSelected(!isSelected);
}
return (
<div className={`square ${isSelected ? " selected" : ""}`}
onClick={handleToggle}
>
{position.row}, {position.column}
</div>
);
}
export default Square;
Thanks
Edit: I know for a simple application like this it would be very easy to pass down state through props, but the idea is to practice other hooks, so wanting to avoid it. The ideas I am practicing in this would ideally be extensible to bigger projects in the future.
Edit 2: As requested, here's my BoardReducer.js file:
const boardReducer = (state, updateInfo) => {
let isComplete = false;
if (JSON.stringify(updateInfo.userSelection) === JSON.stringify(state.puzzleArray)) {
isComplete = true;
}
updateInfo.isComplete = isComplete;
return updateInfo;
}
export default boardReducer;
(using JSON.stringify as a cheap way to check matching arrays as it's only a small one for now!)
Issue
You are mutating your state object in a couple places:
const handleToggle = () => {
console.log(board);
board.userSelection[position.row][position.column] = !board.userSelection[position.row][position.column]; // <-- mutation!
dispatch(board);
setIsSelected(!isSelected);
}
And in reducer
const boardReducer = (state, updateInfo) => {
let isComplete = false;
if (JSON.stringify(updateInfo.userSelection) === JSON.stringify(state.puzzleArray)) {
isComplete = true;
}
updateInfo.isComplete = isComplete; // <-- mutation!
return updateInfo; // <-- returning mutated state object
}
Since no new state object is created React doesn't see a state change and doesn't rerender your UI.
Solution
useReducer will typically employ a "redux" pattern where the reducer function consumes the current state and an action to operate on that state, and returns a new state object.
You should dispatch an action that toggles the user selection and checks for a complete board.
Board Reducer
When updating state you should shallow copy any state objects that you are updating into new object references, starting with the entire state object.
const boardReducer = (state, action) => {
if (action.type === "TOGGLE") {
const { position } = action;
const nextState = {
...state,
userSelection: state.userSelection.map((rowEl, row) =>
row === position.row
? rowEl.map((colEl, col) =>
col === position.column ? !colEl : colEl
)
: rowEl
)
};
nextState.isComplete =
JSON.stringify(nextState.userSelection) ===
JSON.stringify(state.puzzleArray);
return nextState;
}
return state;
};
Create an action creator, which is really just a function that returns an action object.
const togglePosition = position => ({
type: "TOGGLE",
position
});
Then the handleToggle should consume/pass the row and column position in a dispatched action.
const handleToggle = () => dispatch(togglePosition(position));
Simple Demo
Demo Code:
const puzzleArray = [
[true, false, true],
[false, true, false],
[true, false, true]
];
const userSelection = Array(3).fill(Array(3).fill(false));
const togglePosition = (row, column) => ({
type: "TOGGLE",
position: { row, column }
});
const boardReducer = (state, action) => {
if (action.type === "TOGGLE") {
const { position } = action;
const nextState = {
...state,
userSelection: state.userSelection.map((rowEl, row) =>
row === position.row
? rowEl.map((colEl, col) =>
col === position.column ? !colEl : colEl
)
: rowEl
)
};
nextState.isComplete =
JSON.stringify(nextState.userSelection) ===
JSON.stringify(state.puzzleArray);
return nextState;
}
return state;
};
export default function App() {
const [boardState, dispatch] = React.useReducer(boardReducer, {
puzzleArray,
userSelection,
isComplete: false
});
const handleClick = (row, column) => () =>
dispatch(togglePosition(row, column));
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div>{boardState.isComplete ? "Congratulations!" : "Keep Trying"}</div>
<div>
{boardState.userSelection.map((row, r) => (
<div key={r}>
{row.map((col, c) => (
<span
key={c}
className={classnames("square", { active: col })}
onClick={handleClick(r, c)}
/>
))}
</div>
))}
</div>
</div>
);
}
I'm a bit lost in my component organisation and I have a problem to animate a component display.
I use the handleCart function to toggle the display state, then through the props, I attend to apply a gsap animation to show or hide the cart component, but it doesn't work.
In the react dev tools I see that the state in the cart component changes when I clic on the cart button, but nothing happens ...
The cart button is the the header :
const Header = ({ history }) => {
const [displayCart, setDisplayCart] = useState(false);
const handleCart = () => {
if (displayCart === false) {
setDisplayCart(!displayCart);
} else if (displayCart === true) {
setDisplayCart(false);
};
}
return (
<header>
<Cart display={displayCart} />
</header>
);
};
And the cart component should display with the gsap animation :
const Cart = (props) => {
let cartAnim = useRef(null);
useEffect(() => {
if (props.display === true) {
gsap.to(cartAnim, {
duration: 1,
width: '30vw',
ease: 'power3.inOut',
});
} else if (props.display === false) {
gsap.to(cartAnim, {
duration: 1,
ease: 'power3.inOut',
width: '0vw',
});
}
}, [])
return (
<div ref={el => (cartAnim = el)} className="cart">
<div className="menu">
<button>Close</button>
</div>
<h1 id="panier">Panier</h1>
I find the solution by myself. Sorry.
I need to add props in the array at the end of the useEffect function :
useEffect(() => {
if (props.display === true) {
gsap.to(cartAnim, {
duration: 1,
width: '30vw',
ease: 'power3.inOut',
});
} else if (props.display === false) {
gsap.to(cartAnim, {
duration: 1,
ease: 'power3.inOut',
width: '0vw',
});
}
}, [props])
Here is the carousel I am using: react-slick
I want to be able to scroll through each slide using the mouse scroll up or down event.
Scroll up to increment, scroll down to decrement.
Found an example online of exactly what I need - just unsure of how to convert this into a react solution.
Example: https://codepen.io/Grawl/pen/mMLQQb
What would be the best way to achieve this in a "react" component based approach?
Here is my react component:
import React from 'react';
import PropTypes from 'prop-types';
import styles from './styles.css';
import ReactSVG from 'react-svg';
import Slider from 'react-slick';
import MobileSVG from '../../../assets/svg/icons/Mobile_Icon_Option2.svg';
import TabletSVG from '../../../assets/svg/icons/Tablet_Icon_Option2.svg';
import DesktopSVG from '../../../assets/svg/icons/Desktop_Icon_Option2.svg';
const deviceIcons = {'mobile': MobileSVG, 'tablet': TabletSVG, 'desktop': DesktopSVG};
import BackToTopButton from '../BackToTopButton';
export default class ProductComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
const {productData} = this.props
//Slider settings
const settings = {
dots: true,
infinite: false,
speed: 500,
fade: true,
arrows: false,
centerMode: true,
slidesToShow: 1,
slidesToScroll: 1
}
//Slider items
const sliderItems = productData.map((obj, i) => {
return (
<div className="product-component row" key={i}>
<div className="product-component-image-wrap col-xs-12 col-sm-8">
<span className="product-heading">{obj.category}</span>
<div className="product-detail-wrap">
<img className="product-component-image" src={`${process.env.DB_URL}${obj.image}`} />
<ul className="list-device-support">
{obj.categoryDeviceSupport.map((obj, i) => {
return (<li key={i}>
<span className="svg-icon">
<ReactSVG path={deviceIcons[obj.value]} />
</span>
<span className="product-label">{obj.label}</span>
</li>)
})}
</ul>
</div>
</div>
<div className="product-component-info col-xs-12 col-sm-3">
<span className="align-bottom">{obj.title}</span>
<p className="align-bottom">{obj.categoryBrief}</p>
</div>
</div>
)
});
return (
<div className="product-component-wrap col-xs-12">
<Slider {...settings}>
{sliderItems}
</Slider>
<BackToTopButton scrollStepInPx="50" delayInMs="7" />
</div>
)
}
}
ProductComponent.propTypes = {
productData: PropTypes.array
};
ProductComponent.defaultProps = {
productData: []
};
You'd wanna do something like this:
constructor(props){
super(props);
this.slide = this.slide.bind(this);
}
slide(y){
y > 0 ? (
this.slider.slickNext()
) : (
this.slider.slickPrev()
)
}
componentWillMount(){
window.addEventListener('wheel', (e) => {
this.slide(e.wheelDelta);
})
}
render(){...
and add a ref to your slider:
<Slider ref={slider => this.slider = slider }>
So when the y value of the wheel event is greater than 0 i.e. scroll up then show next slide, when scrolling down show previous.
The following should work fine for you:
componentDidMount(){
let slickListDiv = document.getElementsByClassName('slick-list')[0]
slickListDiv.addEventListener('wheel', event => {
event.preventDefault()
event.deltaY > 0 ? this.slider.slickNext() : this.slider.slickPrev()
})
}
You should initialize the component like this:
<Slider {...settings} ref={slider => this.slider = slider.innerSlider}>
...
</Slider>
I use the following code in my CustomSlider component:
constructor(props) {
super(props);
this.handleWheel = this.handleWheel.bind(this);
}
componentDidMount() {
ReactDOM.findDOMNode(this).addEventListener('wheel', this.handleWheel);
}
componentWillUnmount() {
ReactDOM.findDOMNode(this).removeEventListener('wheel', this.handleWheel);
}
handleWheel(e) {
e.preventDefault();
e.deltaY > 0 ? this.slider.slickNext() : this.slider.slickPrev();
}
Component initialization
<Slider ref={slider => this.slider = slider}>
...
</Slider>
With hooks
const sliderRef = createRef();
const scroll = useCallback(
y => {
if (y > 0) {
return sliderRef?.current?.slickNext(); /// ? <- using description below
} else {
return sliderRef?.current?.slickPrev();
}
},
[sliderRef]
);
useEffect(() => {
window.addEventListener("wheel", e => {
scroll(e.deltaY);
});
}, [scroll]);
I used optional chaining from typescript connected with babel plugins, but you can use verification like: sliderRef.current && sliderRef.current.slickNext()
I was able to get scrolling to work in a function component that reference the Slider component (react-slick JS library) using hooks (useRef to obtain a reference to the Slider component and useEffect to add and remove a listener (scroll function) to the wheel event).
const myComponent () => {
const settings = {
dots: true,
slidesToShow: 1,
slidesToScroll: 1,};
const slider = useRef(null);
function scroll(e){
if (slider === null)
return 0;
e.wheelDelta > 0 ? (
slider.current.slickNext()
) : (
slider.current.slickPrev()
);
};
useEffect(() => {
window.addEventListener("wheel", scroll,true);
return () => {
window.removeEventListener("wheel", scroll, true);
};
}, []);
return (
<Slider {...settings} ref={slider}>
</Slider>
);
}
export default myComponent;