I am building a carousel component in react -- and the client wants there to be an autoscroll option - my concern though is interfering with the user interface -- what kind of logic to build to break/pause the autoscroll without interfering with the user trying to use it in the first place. So hover detection is needed?
//Original Carousel
https://codesandbox.io/s/holy-https-h0yv3
//With current autoscroll option on - but ignoring all UI considerations
https://codesandbox.io/s/nervous-brown-2inki?file=/src/Carousel/Carousel.js
also - the carousel after going through the slides - will just ZIP through the deck - without being able to snap to the slide - so that's another issue
import React, { Component } from 'react';
import Button from '#material-ui/core/Button';
import ChevronLeftIcon from '#material-ui/icons/ChevronLeft';
import ChevronRightIcon from '#material-ui/icons/ChevronRight';
import './Carousel.scss';
class Carousel extends Component {
constructor() {
super();
this.myRef = React.createRef();
this.state = {
currentSlide: 0,
slideWidth: 0,
slideCount: 0
};
this.updateDimensions = this.updateDimensions.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
this.prev = this.prev.bind(this);
this.next = this.next.bind(this);
}
componentDidMount() {
window.addEventListener("resize", this.updateDimensions);
this.updateDimensions();
this.setState({
slideCount: this.props.items.length
});
this.autoSlide();
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateDimensions);
}
handleKeyPress(e) {
if (e.keyCode === 39) {
this.next();
}
if (e.keyCode === 37) {
this.prev();
}
}
updateDimensions(){
//update the dimensions of the slide for responsiveness
this.setState({
slideWidth: this.myRef.current.getBoundingClientRect().width
});
}
prev(){
//if the current slide won't hit negative slide - change current slide
if(this.state.currentSlide > 0){
let prevSlide = this.state.currentSlide-1;
this.setState({
currentSlide: prevSlide
});
}
}
next(){
//if the next slide will not go beyond the items - change current slide
if(this.state.currentSlide < this.props.items.length-1){
let nextSlide = this.state.currentSlide+1;
this.setState({
currentSlide: nextSlide
});
}
}
selected(slide){
console.log(slide)
this.setState({
currentSlide: slide
});
}
autoSlide(){
let that = this;
let count = this.props.items.length;
let i = 0;
setInterval(function(){
that.selected(i);
i++;
if(count === i){
i=0;//resets
}
}, 3000);
}
render() {
return (
<div ref={this.myRef} className="carousel" onKeyDown={this.handleKeyPress} tabIndex={-1}>
<div className="carousel-holder">
<div className="carousel-wrapper">
<div
className="slide-wrapper"
style={{
left: -this.state.slideWidth*this.state.currentSlide,
width: this.state.slideWidth*this.state.slideCount
}}
>
{
this.props.items.map((item, j) => {
return(
<div
key={j}
className="slide"
style={{
width: this.state.slideWidth
}}
>
<div className={"gutter " + (this.props.hasGutter? "has-gutter" : "")}>
{item.slide}
</div>
</div>
)
})
}
</div>
</div>
<Button
className="carousel-arrow prev-arrow"
onClick={this.prev}
disabled={(this.state.currentSlide === 0)}
>
<ChevronLeftIcon />
</Button>
<Button
className="carousel-arrow next-arrow"
onClick={this.next}
disabled={(this.state.currentSlide === this.props.items.length-1)}
>
<ChevronRightIcon />
</Button>
</div>
<div className="carousel-dots">
<ul className="dot-wrapper">
{
this.props.items.map((item, x) => {
return (
<li
key={x}
className={(this.state.currentSlide === x? "selected" : "")}
>
<Button
className="dot"
onClick={() => this.selected(x)}
/>
</li>
)
})
}
</ul>
</div>
</div>
);
}
}
export default Carousel;
You should create a boolean shouldAutoScroll which should be passed as prop to this Carousel component. Initially, it's value would be true, which means this slides should autoscroll, based on a time-interval you can keep changing the active slide.
One the user hovers over the images or once the user clicks the next/previous button, set that shouldAutoScroll to false, which would mean that logic to autoscroll should be stopped.
this.state = {
shouldAutoScroll: true,
...
}
and since you are already calling it on componentDidMount
componentDidMount() {
...
this.state.shouldAutoScroll && this.autoSlide();
}
Toggle it's value, on the next() and prev(), where it is certain that user wants to take control of the scrolling.
next/prev () {
this.setState(p => p.shouldAutoScroll && ({...p, shouldAutoScroll: false}));
... // further logic
}
I'm not sure how to adjust your code in the given scenario according to your expectation. But I also wanted the same thing to be done in my carousel. Then I found this npm package that can be used to display a carousel. I think you also can use this and rectify your problem.
Related
My code generates an input field that allows a user to enter a value to search for. Then when they click the Submit button, it causes displayMap to be true, so that when the MapDisplay component renders, it will trigger an API search via the Map component and return values that are then displayed on the map.
The problem is that this process only works once. When I click the button again, it does do something, I confirmed that it is getting the new value in the input box, but I can't seem to figure out how to get the map to be rendered again.
I've tried setting other variables in the this.setState to try to get it to know that it needs to render the component again, but I guess I'm missing something, because nothing works.
I'm fairly new to React, so any help you can offer would be greatly appreciated.
This is the MainSearchBar.js, where most of the work as described above is happening:
import Map from './newMap.js';
function MapDisplay(props) {
if (props.displayMap) {
return <Map toSearch = {props.searchTerm}></Map>;
} else {
return "";
}
}
class MainSearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
displayMap: false,
value: '',
searchTerm: '',
isOpened: false
};
//this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick = () => {
this.setState({
displayMap: true,
isOpened: !this.state.isOpened,
searchTerm: this.state.value
});
console.log(this.state.value);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
<MapDisplay displayMap={displayMap} searchTerm={this.state.value} />
</div>
)
}
}
export default MainSearchBar;
This is where MainSearchBar is being called from
import Top20Box from '../components/getTop20Comp2.js';
import Header from '../components/Header.js';
import MainIntro from '../components/MainIntro.js';
import MainSearchBar from '../components/MainSearchBar.js';
import MainCTA from '../components/MainCTA.js';
import Footer from '../components/Footer.js';
export default class Home extends Component {
state = {
}
render () {
return (
<React.Fragment>
<Header>
</Header>
<MainIntro />
<MainSearchBar />
<div className="top20-text">
Top 20 trending hashtags
</div>
<Top20Box />
<MainCTA />
<Footer />
</React.Fragment>
)
}
}
And this is the Map component itself, in case you need it:
import React from 'react';
import ReactMapGL, {Marker, Popup} from 'react-map-gl';
import axios from 'axios';
//for the loading animation function
import FadeIn from "react-fade-in";
import Lottie from "react-lottie";
import * as loadingData from "../assets/loading.json";
var locationCoordinates = [];
var locationToSearch = "";
var returnedKeywordSearch = [];
var newArray = [];
const defaultOptions = {
loop: true,
autoplay: true,
animationData: loadingData.default,
rendererSettings: {
preserveAspectRatio: "xMidYMid slice"
}
};
export default class Map extends React.Component {
//sets components for the map, how big the box is and where the map is centered when it opens
state = {
viewport: {
width: "75vw",
height: "50vh",
latitude: 40.4168,
longitude: 3.7038,
zoom: .5
},
tweetSpots: null, //data from the API
selectedSpot: null,
done: undefined, //for loading function
};
async componentDidMount() {
//searches the api for the hashtag that the user entered
await axios.get(`https://laffy.herokuapp.com/search/${this.props.toSearch}`).then(function(response) {
returnedKeywordSearch = response.data;
}) //if the api call returns an error, ignore it
.catch(function(err) {
return null;
});
//goes through the list of locations sent from the api above and finds the latitude/longitude for each
var count = 0;
while (count < returnedKeywordSearch.length) {
locationToSearch = returnedKeywordSearch[count].location;
if (locationToSearch !== undefined) {
var locationList = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${locationToSearch}.json?access_token=pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg`)
.catch(function(err) {
return null;
});
if (locationList !== null) {
if (Array.isArray(locationList.data.features) && locationList.data.features.length)
{
locationCoordinates.push(locationList.data.features[0].center);
if (returnedKeywordSearch[count].location!== null && returnedKeywordSearch[count].location!==""
&& locationList.data.features[0].center !== undefined)
{newArray.push({
id: returnedKeywordSearch[count].id,
createdAt: returnedKeywordSearch[count].createdAt,
text: returnedKeywordSearch[count].text,
name: returnedKeywordSearch[count].name,
location: returnedKeywordSearch[count].location,
coordinates: locationList.data.features[0].center
});
}
}
}
}
count++;
}
this.setState({tweetSpots: newArray});
this.setState({ done: true}); //sets done to true so that loading animation goes away and map displays
}
//is triggered when a marker on the map is hovered over
setSelectedSpot = object => {
this.setState({
selectedSpot: object
});
};
//creates markers that display on the map, using location latitude and longitude
loadMarkers = () => {
return this.state.tweetSpots.map((item,index) => {
return (
<Marker
key={index}
latitude={item.coordinates[1]}
longitude={item.coordinates[0]}
>
<img class="mapMarker"
onMouseOver={() => {
this.setSelectedSpot(item);
}}
src="/images/yellow7_dot.png" alt="" />
</Marker>
);
});
};
//closes popup when close is clicked
closePopup = () => {
this.setState({
selectedSpot: null
});
};
//renders map component and loading animation
render() {
return (
<div className="App">
<div className="map">
{!this.state.done ? (
<FadeIn>
<div class="d-flex justify-content-center align-items-center">
<Lottie options={defaultOptions} width={400} />
</div>
</FadeIn>
) : (
<ReactMapGL {...this.state.viewport} mapStyle="mapbox://styles/mapbox/outdoors-v11"
onViewportChange={(viewport => this.setState({viewport}))}
mapboxApiAccessToken="pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg">
{this.loadMarkers()}
{this.state.selectedSpot !== null ? (
<Popup
key={this.state.selectedSpot.id}
tipSize={5}
latitude={this.state.selectedSpot.coordinates[1]}
longitude={this.state.selectedSpot.coordinates[0]}
closeButton={true}
closeOnClick={false}
onClose={this.closePopup}
>
<div className="mapPopup">
<div className="header"> Tweet </div>
<div className="content">
{" "}
<p>
<b>Name:</b> {this.state.selectedSpot.name}
</p>
<p>
<b>Tweet:</b> {this.state.selectedSpot.text}</p>
<p>View Tweet in Twitter
</p>
</div>
</div>
</Popup>
) : null}
</ReactMapGL>
)}
</div>
</div>
);
}
}
Update: 4/28, per the answer I received, I update the render of the MainSearchBar.js to look like this:
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
{this.state.displayMap && <Map toSearch = {this.searchTerm}></Map>}
</div>
)
}
When you click the button again, the state of MainSearchBar.js updates but the functional component MapDisplay does not and thus the Map does not update as well.
There are many ways to resolve this. Looking at the code, it looks like MapDisplay doesn't do much so you can consider replacing it with conditional rendering.
MainSearchBar.js
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
{this.state.displayMap && <Map toSearch = {props.searchTerm}></Map>}
</div>
)
}
Then in your Map component, add a componentDidUpdate lifecycle method to detect updates to the prop which does the same thing as componentDidMount when the props are updated.
async componentDidMount(prevProps) {
if (props.toSearch != prevProps.toSearch) {
await axios.get(`https://laffy.herokuapp.com/search/${this.props.toSearch}`).then(function(response) {
returnedKeywordSearch = response.data;
}) //if the api call returns an error, ignore it
.catch(function(err) {
return null;
});
//goes through the list of locations sent from the api above and finds the latitude/longitude for each
var count = 0;
while (count < returnedKeywordSearch.length) {
locationToSearch = returnedKeywordSearch[count].location;
if (locationToSearch !== undefined) {
var locationList = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${locationToSearch}.json?access_token=pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg`)
.catch(function(err) {
return null;
});
if (locationList !== null) {
if (Array.isArray(locationList.data.features) && locationList.data.features.length)
{
locationCoordinates.push(locationList.data.features[0].center);
if (returnedKeywordSearch[count].location!== null && returnedKeywordSearch[count].location!==""
&& locationList.data.features[0].center !== undefined)
{newArray.push({
id: returnedKeywordSearch[count].id,
createdAt: returnedKeywordSearch[count].createdAt,
text: returnedKeywordSearch[count].text,
name: returnedKeywordSearch[count].name,
location: returnedKeywordSearch[count].location,
coordinates: locationList.data.features[0].center
});
}
}
}
}
count++;
}
this.setState({tweetSpots: newArray});
this.setState({ done: true}); //sets done to true so that loading animation goes away and map displays
}
}
#wxker Thanks for all your help! You certainly got me pointed in the right direction.
I changed render in MainSearchBar.js back to what it was originally.
And I added a ComponentDidUpdate to the Map component, as follows below:
async componentDidUpdate(prevProps) {
//searches the api for the hashtag that the user entered
if (this.props.toSearch !== prevProps.toSearch) {
and then the rest was the same as the original componentDidMount.
i have a side panel with items listed. when the list item content overflows expand button appears and clicking that expand btn would show the entire content of list item
For this i have created a expandable component. this will show arrow_down when list item content overflows and clicking arrow_down shows up arrow_up.
However with the below code, clicking button 1 just makes the sidpanel disappear instead of arrow_up appearing. could some one help me solve this. thanks.
export default class Expandable extends React.PureComponent{
constructor(props) {
super(props);
this.expandable_ref = React.createRef();
this.state = {
expanded: false,
overflow: false,
};
}
componentDidMount () {
if (this.expandable_ref.current.offsetHeight <
this.expandable_ref.current.scrollHeight) {
this.setState({overflow: true});
}
}
on_expand = () => {
this.setState({expanded: true});
console.log("in expnad");
};
on_collapse = () => {
this.setState({expanded: false});
};
render () {
return (
<div className={(this.state.overflow ?
this.props.container_classname : '')}>
<div className={(this.state.overflow ?
this.props.classname : '')} style={{overflow: 'hidden',
display: 'flex', height: (this.state.expanded ? null :
this.props.base_height)}}
ref={this.expandable_ref}>
{this.props.children}
</div>
{this.state.overflow && this.state.expanded &&
<div className={this.props.expand}>
<button onClick={this.on_collapse}>
{this.props.arrow_up}</button>
</div>}
{this.state.overflow && !this.state.expanded &&
<div className={this.props.expand}>
<button onClick={this.on_expand}>
{this.props.arrow_down}</button>
</div>}
</div>
);
}
}
In the above code i pass the base_height to be 42px.
Edit:
i have realised for the side panel component i add eventlistener click to close the side panel if user clicks anywhere outside sidepanel. When i remove that eventlistener it works fine....
class sidepanel extends React.PureComponent {
constructor(props) {
super(props);
this.sidepanel_ref = React.createRef();
}
handle_click = (event) => {
if (this.sidepanel_ref.current.contains(event.target)) {
return;
} else {
this.props.on_close();
}
};
componentDidMount() {
document.addEventListener('click', this.handle_click, false);
}
componentWillUnmount() {
document.removeEventListener('click', this.handle_click, false);
}
render() {
return (
<div>
<div className="sidepanel" ref=
{this.sidepanel_ref}>
{this.props.children}
</div>
</div>
);
}
}
when i log the event.target and sidepanel_ref.current i see the button element in both of them but svg seems different in both of them.
How can i fix this?
Probably it is because click events bubble up the component tree as they do in the DOM too. If you have an element with an onClick handler inside an element with another onClick handler it will trigger both. Use event.stopPropagation() in the handler of the inner element to stop the event from bubbling up:
export default class Expandable extends React.PureComponent{
constructor(props) {
super(props);
this.expandable_ref = React.createRef();
this.state = {
expanded: false,
overflow: false,
};
}
componentDidMount () {
if (this.expandable_ref.current.offsetHeight <
this.expandable_ref.current.scrollHeight) {
this.setState({overflow: true});
}
}
toggleCollapse = event => {
// use preventDefault here to stop the event from bubbling up
event.stopPropagation();
this.setState(({expanded}) => ({expanded: !expanded}));
};
render () {
const {className, container_classname, base_height, expand, arrow_up, arrow_down} = this.props;
const {overflow, expanded} = this.state;
return (
<div className={overflow ? container_classname : ''}>
<div
className={overflow ? classname : ''}
style={{
overflow: 'hidden',
display: 'flex',
height: expanded ? null : base_height
}}
ref={this.expandable_ref}
>
{this.props.children}
</div>
{overflow && (
<div className={expand}>
<button onClick={this.toggleCollapse}>
{expanded ? arrow_up : arrow_down}
</button>
</div>
)}
</div>
);
}
}
I have a React app in which I render a horizontal list draft picks. At any point in time, one draft pick is active, but at a certain point that pick is made, and then the next one becomes active. When the next one becomes active, I'd like for the list to auto-scroll horizontally so that the active pick is always in view.
I'm sure this has been asked before, but I don't really know how to cause an auto-scroll event to a React component.
Here's the code I have:
DraftOrder.js:
class DraftOrder extends React.Component {
render() {
return (
<React.Fragment>
<div className="d-flex flex-row auto-scroll-x">
{this.props.draftPicks.map(aDraftPick => {
return <ADraftPickInOrder key={v4()} draftPick={aDraftPick} team={this.props.team} />
})}
</div>
</React.Fragment>
);
}
}
ADraftPickInOrder.js:
class ADraftPickInOrder extends React.Component {
constructor(props) {
super(props);
this.state = {
active: this.props.draftPick.active
}
this.renderUserName = this.renderUserName.bind(this);
this.renderDraftedPlayer = this.renderDraftedPlayer.bind(this);
}
renderUserName() {
...
}
renderDraftedPlayer() {
...
}
render() {
return (
<div className="text-center px-3">
<div className={classnames("font-weight-bold no-wrap", { "text-success" : this.props.draftPick.team.id === this.props.team.id } )}>{this.props.draftPick.team.name}</div>
{this.renderUserName()}
{this.renderDraftedPlayer()}
<div><small>{formatDraftPickNumber(this.props.draftPick)}</small></div>
</div>
);
}
}
Any help would be much appreciated!
Use ScrollView and the scrollTo method of it:
import { ScrollView } from 'react-native';
...
setScrollViewRef = ref => {
this.scrollView = ref;
};
handlePress = ({nativeEvent}) => {
const { pageX, pageY } = nativeEvent;
// find out x and y somehow
this.scrollView.scrollTo({x, y, animated: true})
}
render() {
<ScrollView
horizontal
ref={this.setScrollViewRef}
showsHorizontalScrollIndicator={false}>
<DraftPick onPress={this.handlePress} />
<DraftPick onPress={this.handlePress} />
...
</ScrollView>
}
I am new to react - I'm sure this is a simple problem but I am having a lot of trouble figuring out a solution.
So I have two buttons. Onclick on first button it should open fade in and if i click on the same button(button1) or if I click on the button(button2) it should fade out. same for the button2.
so far I managed to figure it out how fade in works but not able to work out on fade out.
import React, { Component } from 'react'
import { Jumbotron, Grid, Row, Col, Image, Button, Carousel,Fade,Well } from 'react-bootstrap';
import './About.css';
export default class About extends Component {
constructor(props, context) {
super(props, context);
this.state = {
buttonPressed: false,
buttonPressed1: false
//open: false
}
}
handleClick = (button) => {
this.setState({ buttonPressed: button })
}
handleClick1 = (button) => {
this.setState({ buttonPressed1: button })
}
render() {
return (
<Grid>
<button className='button1' onClick={() => this.handleClick({ open: !this.state.buttonPressed })}>
BUTTON 1
</button>
<button className='button2' onClick={() => this.handleClick1({ open: !this.state.buttonPressed1 })}>
BUTTON 2
</button>
<Fade class='fade1' in={this.state.buttonPressed}>
<div>
<Well>
first
</Well>
</div>
</Fade>
<Fade class='fade2' in={this.state.buttonPressed1}>
<div>
<Well>
second
</Well>
</div>
</Fade>
</Grid>
)
}
}
Please help with the about code.
Thanks in advance.
First screenshot
Second screnshot
You are passing an object not a boolean value to handleClick method. Here's how it should be:
handleClick = (button) => {
this.setState({ buttonPressed: button.open })
}
handleClick1 = (button) => {
this.setState({ buttonPressed1: button.open })
}
I am using react-slick and I have my own customised arrows. This Slider is NOT an infinite loop and I would like to hide the arrows at the start and end of the loop. So basically at start PrevArrow should be hidden and at the end the NextArrow should be hidden. Which I am trying to set hidden class name depending on state changes. However the class name is not changing although the state is changing correctly. Do you know what's the problem with this code? and how to get it work?
Below is my setting for Slider and the component which renders Slider.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Slider from 'react-slick';
import Arrow from '../slider/Arrow';
export default class CityCarousel extends Component {
constructor(props) {
super(props);
this.state = {
displayLeftArrow: true,
displayRightArrow: true
};
this.slidesToShow = 5;
this.sliderSetting = {
dots: false,
arrows: true,
infinite: false,
initialSlide: 0,
slidesToShow: this.slidesToShow,
slidesToScroll: 1,
speed: 500,
rows: 0,
nextArrow: <Arrow
styleClassName={`city-carousel__right ${
this.state.displayRightArrow ? '' : 'city-carousel__right--hide'
}`}
direction="right"
clickHandler={this.clickHandler}
/>,
prevArrow: <Arrow
styleClassName={`city-carousel__left ${
this.state.displayLeftArrow ? '' : 'city-carousel__left--hide'
}`}
direction="left"
clickHandler={this.clickHandler}
/>,
afterChange: currentSlide => this.setArrowDisplay(currentSlide)
};
}
clickHandler = direction => {
if (direction === 'left') {
this.slider.slickPrev();
} else if (direction === 'right') {
this.slider.slickNext();
}
};
setArrowDisplay = currentSlide => {
const { cityList } = this.props;
const displayLeftArrow = currentSlide !== 0;
const displayRightArrow = currentSlide !== cityList.length - this.slidesToShow;
this.setState({ displayRightArrow, displayLeftArrow });
};
render() {
const { cityList, tours } = this.props;
return (
<div>
<div className="city-selection">
<Slider
ref={c => this.slider = c }
{...this.sliderSetting}
>
{cityList.length > 0 ? this.renderCityList() : null}
</Slider>
</div>
</div>
);
}
}
Here is also code for Arrow component
import React from 'react';
import PropTypes from 'prop-types';
const Arrow = ({ styleClassName, direction, clickHandler }) => {
return(
<div
className={`slick-arrow--${direction} slider-arrow--${direction} ${styleClassName}`}
onClick={e => clickHandler(direction)}
/>
)};
Arrow.propTypes = {
styleClassName: PropTypes.string,
direction: PropTypes.string,
clickHandler: PropTypes.func
};
export default Arrow;
It seems react-slick is not re-rendering <Arrow /> with new props, and it's always with first initialized props setting.
I think the solution is to get <Arrow /> out of slider like this:
<div className="city-selection">
<Arrow
styleClassName={`city-carousel__right ${
this.state.displayRightArrow ? '' : 'city-carousel__right--hide'
}`}
direction="right"
clickHandler={this.clickHandler}
/>
<Arrow
styleClassName={`city-carousel__left ${
this.state.displayLeftArrow ? '' : 'city-carousel__left--hide'
}`}
direction="left"
clickHandler={this.clickHandler}
/>
<Slider
{/* list of items*/}
</Slider>
</div>
you can see how it's working in here
Pass the current slide prop to the component and do a check for the slide you want to trigger hiding the arrow:
function SamplePrevArrow(props) {
const { currentSlide, className, onClick } = props;
if (currentSlide === 0) {
return false;
} else {
return <div className={className} onClick={onClick} />;
}
}
In your this.sliderSettings set nextArrow & prevArrow for your Arrow component do something like this
<Arrow
styleClassName={'YOUR_CLASSES_HERE'}
direction="right"
clickHandler={this.clickHandler}
isHidden={this.state.isHidden}
/>
Where this.state.isHidden is the state variable where you are trying toggle the arrows.
Then in your Arrow component do something like this.
const Arrow = ({ styleClassName, direction, clickHandler, isHidden }) => {
return (
<div
className={`slick-arrow--${direction} slider-arrow--${direction}
${styleClassName}`}
style={{ display: isHidden: 'none': 'block' }}
onClick={e => clickHandler(direction)}
/>
)
};
If you want the best manual controls over the arrow then use a custom arrow.
Create it by using a simple Arrow component.
Set the onClick handler of that component by using a ref in the main Slider component.
const sliderRef = useRef<Slider>(null);
<Slider {...settings} ref={sliderRef}>
Use states to conditionally show or hide arrows.
I wrote a blog post which covers the workflow to achieve similar kind of functionalities you want. You can have a look there.
https://medium.com/#imasharaful/image-slider-with-react-slick-d54a049f043
In 2022, we have boolean arrows, just declare it false in settings.
const settings = {
...,
arrows: false,
};