I have a component that uses two sliders in combination(a total of 4). One combination is for horizontal carousel and another for vertical. This decision is based upon the screen size.
When it is rendered in smaller devices it is working fine but on larger screens, it renders but as soon as I click on buttons for moving the slides it crashes and presents me with the following error.
I am using react-slick for the carousel.
Here's the component code:
class Carousel extends Component {
constructor(props) {
super(props);
this.state = {
imageSlider: null,
contentSlider: null,
isMobile: true
};
this.getHorizontalSlider = this.getHorizontalSlider.bind(this);
this.getVerticalSlider = this.getVerticalSlider.bind(this);
}
componentDidMount() {
const isMobile = window.screen.width > 768 ? false : true;
this.setState({
imageSlider: this.slider1,
contentSlider: this.slider2,
isMobile: isMobile
});
}
getHorizontalSlider() {
return (
<div className={this.props.className}>
<Slider
asNavFor={this.state.contentSlider}
ref={slider => (this.slider1 = slider)}
className={styles.horizontalImageSlider}
focusOnSelect={false}
arrows={false}
dots={true}
autoplay={true}
autoplaySpeed={7000}
>
{
data.map((item, index) => (
<div key={index}>
<img src={item.imageUrl} className={`${styles.carouselImage} img-fluid mx-auto`} alt="" />
</div>
))
}
</Slider>
<Slider
asNavFor={this.state.imageSlider}
ref={slider => (this.slider2 = slider)}
focusOnSelect={false}
arrows={false}
>
{
data.map((item, index, arr) => (
<div key={index} className='text-center text-secondary'>
<h2 className="text-primary fw-bold mb-4">{item.heading}</h2>
<p className='lead px-2'>{item.description}</p>
</div>
))
}
</Slider>
</div>
)
}
getVerticalSlider() {
return (
<div className={`row ${this.props.className}`}>
<div className="col-4">
<Slider
asNavFor={this.state.contentSlider}
ref={slider => (this.slider1 = slider)}
vertical={true}
arrows={false}
slidesToScroll={1}
verticalSwiping={false}
focusOnSelect={false}
slidesToShow={1}
>
{
data.map((item, index) => (
<div key={index}>
<img src={item.imageUrl} className={`${styles.carouselImage} img-fluid mx-auto`} alt="" />
</div>
))
}
</Slider>
</div>
<div className="col-8">
<div className='d-flex justify-content-center align-items-center h-100'>
<Slider
className={styles.verticalContentSlider}
asNavFor={this.state.imageSlider}
ref={slider => (this.slider2 = slider)}
slidesToScroll={1}
slidesToShow={1}
swipeToSlide={true}
focusOnSelect={true}
vertical={true}
adaptiveHeight={true}
nextArrow={<CustomArrow />}
prevArrow={<CustomArrow />}
>
{
data.map((item, index, arr) => (
<div key={index}>
{getSlideContent(item, index, arr)}
</div>
))
}
</Slider>
</div>
</div>
</div>
)
}
render() {
if (this.state.isMobile === true) {
return this.getHorizontalSlider();
}
return this.getVerticalSlider();
}
}
I know it's not the best code out, but I want to make this work first and then refactor it.
Thanks for reading this, much appreciated.
It looks like your setState() will only be executed once, after the component mounts with the mobile defaults. That means that when your component re-renders to show the vertical sliders, imageSlider and contentSlider still point to the mobile, horizontal sliders. This would cause react-slick to throw an error when trying to resolve your asNavFor settings.
One way to avoid this might be to set your default mobile state in the constructor instead of componentDidMount, so that only one set of components (horizontal or vertical) will ever be rendered.
constructor(props) {
super(props);
this.state = {
imageSlider: null,
contentSlider: null,
isMobile: window.screen.width <= 768
};
...
}
Related
How can I make each product card to change it's bubble background color, also each product cart to have one ? As you can see on picture only first card has bubble with color. The problem is that if I choose a color from the second card it change the color in the first card, also the second card doesn't have bubble inside.
Here is the map function:
{products.map(product => {
return(
<div className="product--Container" key={product._id}>
<div className="product--TopButton--Container">
<button className="about--Button" onClick={() => productData(product._id)}>About</button>
</div>
<div className="color--Background" style={{backgroundColor: color[0] === product._id ? `${color[1]}` : ""}} />
<img src={product.imageUrl} alt="product image" />
<div className="product--Colors--Container">
{product.color.map(color => {
return(
<button name="color" value={color.value} style={{backgroundColor: `${color.value}`}} onClick={() => colorPicker([product._id, color.value])}/>
)
})}
</div>
<div className="product--About--Container">
<h3>{product.name}</h3>
<p>{product.price}$</p>
</div>
Here is the state and the function:
const [color, setColor] = useState([]);
const colorPicker = ([productId, colors]) => {
setColor([productId, colors])
}
You can split this code into a smaller component so it only affects its own state
{products.map(product => (<SmallerComponent product={product} />)}
In the smaller component
function SmallerComponent({product}) {
const [color, setColor] = useState([]); //Have its own state
const colorPicker = ([productId, colors]) => {
setColor([productId, colors])
}
return (<>
<div className="product--Container" key={ product._id }>
< div className="product--TopButton--Container">
< button className="about--Button" onClick={ ()=> productData(product._id) } > About
< /button>
< /div>
< div className="color--Background" style={ { backgroundColor: color[0]===p roduct._id ? `${color[1]}` : "" } } />
< img src={ product.imageUrl } alt="product image" />
< div className="product--Colors--Container"> { product.color.map(color => { return (
< button name="color" value={ color.value } style={ { backgroundColor: `${color.value}` } } onClick={ ()=> colorPicker([product._id, color.value]) } /> ) }) }
</div>
<div className="product--About--Container">
<h3> {product.name} </h3>
<p> { product.price } $ </p>
</div>
</>); // wrap in fragments)
The second card doesn't have a bubble inside maybe because of this condition. They use the same class so I think they should get the same effect
style={{backgroundColor: color[0] === product._id ? `${color[1]}` : ""}}
Another way is you can create a unique id for the element you want to change color then create a function change color by that id
const changeBtnAndBGColor = (idBG,idBtn,color)=>{
// change color by id
document.getElementById(idBG).style.backgroundColor = color;
document.getElementById(idBtn).style.backgroundColor = color;
}
//Make sure you set id element is unique
{
products.map(product => {
return (<div className="product--Container" key={product._id }>
<div className="product--TopButton--Container">
<button className="about--Button" onClick={ ()=> productData(product._id) }> About
</button>
</div>
<div id={'background'+product._id} className="color--Background" />
<img src={product.imageUrl} alt="product image" />
<div className="product--Colors--Container">
{product.color.map(color => {
const idBtn= product._id+color.value;
return(
<button id={product._id+color.value} name="color" value={color.value}
onClick={()=>changeBtnAndBGColor('background'+product._id,idBtn,color.value)}/>
)
})}
I'm trying to create a component that allows a video to autoplay on mouseenter and pauses on mouseleave. However, the current code causes all videos to autoplay when you put the mouseover any single one of the videos. How can I only make the video that you're interacting with update its state in a more isolated way?
I can't seem to find a solution using React hooks anywhere, that I can understand and implement into my own code.
export default function VideoGrid(props) {
const [playing, setPlaying] = useState(false);
return (
<div>
<div className={styles.VideoGrid}>
<div className="container">
<h2 className={styles.title + " text-lg uppercase title"}>{props.title}</h2>
<div className={styles.videos}>
{props.videos ? videos.output.map((video, index) => {
return (
<div className={styles.video} key={index}>
{ video.acf.video_url ? (
<ReactPlayer
controls={false}
playing={playing}
onMouseEnter={() => setPlaying(true)}
onMouseLeave={() => setPlaying(false)}
height={205}
url={video.acf.video_url + '&showinfo=0&controls=0&autohide=1'}
width='100%'
config= {{
youtube: {
playerVars: {showinfo: 0, controls: 0}
}
}}
/>
) : (
<img src={video._embedded ? video._embedded['wp:featuredmedia'][0].media_details.sizes.full.source_url : '/logo.svg'} height={205} />
)}
<p className="mt-2">{video.title.rendered}</p>
{video.acf.description && router.pathname != '/' ? <p className={styles.description + " text-xs"}>{video.acf.description}</p> : ''}
</div>
)
}) : ''}
</div>
</div>
</div>
</div>
)
}
You can create a separate component and deal with the state individually.
const Video = (props) => {
const [playing, setPlaying] = useState(false);
return (
<div className={styles.video} key={index}>
{video.acf.video_url ? (
<ReactPlayer
controls={false}
playing={playing}
onMouseEnter={() => setPlaying(true)}
onMouseLeave={() => setPlaying(false)}
height={205}
url={video.acf.video_url + "&showinfo=0&controls=0&autohide=1"}
width="100%"
config={{
youtube: {
playerVars: { showinfo: 0, controls: 0 },
},
}}
/>
) : (
<img
src={
video._embedded
? video._embedded["wp:featuredmedia"][0].media_details.sizes.full
.source_url
: "/logo.svg"
}
height={205}
/>
)}
<p className="mt-2">{video.title.rendered}</p>
{video.acf.description && router.pathname != "/" ? (
<p className={styles.description + " text-xs"}>
{video.acf.description}
</p>
) : (
""
)}
</div>
);
};
export default Video;
Then render it in your map. You need to do the proper changes to pass your data into the video component.
export default function VideoGrid(props) {
return (
<div>
<div className={styles.VideoGrid}>
<div className="container">
<h2 className={styles.title + " text-lg uppercase title"}>
{props.title}
</h2>
<div className={styles.videos}>
{props.videos
? videos.output.map((video, index) => {
return <Video />;
})
: ""}
</div>
</div>
</div>
</div>
);
}
I have built an accordion but the state opens each one at a time but I only want one open at a time. I have state working but it is just targeting all of the accordions rather than the actual one. Basically, when a user clicks an accordion, it will drop down and show content, the usual stuff but it is showing all the accordions opening but it should just be one at a time.
Where am I going wrong here:
import React, { Component } from 'react';
import axios from 'axios';
import Icon from '../Icon/Icon';
class RestInfo extends Component {
constructor() {
super();
this.toggle = this.toggle.bind(this);
this.state = {
disruptions: [],
activeAcc: false
};
}
componentDidMount() {
axios
.get('https://trasnport-api-isruptions-v2.azure-api.net/Disruption/v2/', {
headers: {
'Ocp-Apim-Subscription-Key': '55060e2bfbf743c5829b9eef583506f7'
}
})
.then(response => {
this.setState({
disruptions: response.data.disruptions
});
});
}
toggle() {
this.setState(prevState => {
return {
activeAcc: !prevState.activeAcc
};
});
}
render() {
const { disruptions, activeAcc } = this.state;
return (
<div>
{disruptions.length > 0 ? (
disruptions.map(post => {
return (
<div key={post.id}>
<div className={`wmnds-accordion ${activeAcc ? 'wmnds-is--open' : ''}`}>
<button
type="button"
aria-controls="accordion-custom-01"
className="wmnds-accordion__summary-wrapper"
aria-expanded="true"
onClick={this.toggle}
>
<div className="wmnds-accordion__summary">
<div className="wmnds-grid wmnds-grid--align-center">
<span className="wmnds-disruption-indicator-small wmnds-col-auto wmnds-m-r-md">
<svg className="wmnds-disruption-indicator-small__icon">
<Icon iconName="modes-isolated-bus" iconClass="modes-isolated-bus" />
</svg>
<svg className="wmnds-disruption-indicator-small__icon">
<Icon iconName="general-warning-circle" iconClass="general-warning-circle" />
</svg>
</span>
<div className="wmnds-col-auto">
<strong>{post.title}</strong>
</div>
</div>
</div>
<svg className="wmnds-accordion__icon">
<Icon iconName="general-expand" iconClass="general-expand" />
</svg>
<svg className="wmnds-accordion__icon wmnds-accordion__icon--minimise">
<Icon iconName="general-minimise" iconClass="general-minimise" />
</svg>
</button>
<div className="wmnds-accordion__content" id="accordion-custom-01">
<h4 className="serviceAffected">Affected Service(s) </h4>
{post.servicesAffected.map(affected => (
<div key={affected.id}>
<span className="wmnds-disruption-indicator-small wmnds-col-auto wmnds-m-r-md">
<svg className="wmnds-disruption-indicator-small__icon">
<Icon iconName="modes-isolated-bus" iconClass="modes-isolated-bus">
{affected.serviceNumber}
</Icon>
</svg>
<svg className="wmnds-disruption-indicator-small__icon">
<Icon iconName="general-warning-circle" iconClass="general-warning-circle" />
</svg>
</span>
<h5>routeDesc:</h5>
{affected.routeDesc}
<h5>serviceNumber:</h5>
{affected.serviceNumber}
<h5>direction</h5>
{affected.direction}
</div>
))}
<p>{post.title}</p>
<p>{post.description}</p>
<p>{post.disruptionSeverity}</p>
<p>{post.mode}</p>
<p>{post.disruptionSeverity}</p>
</div>
</div>
</div>
);
})
) : (
<div>
<div className="wmnds-loader" />
</div>
)}
</div>
);
}
}
export default RestInfo;
Thank you in advance!
The problem is that all accordions have the same status, which is why all open and close at the same time.
The idea here is to play with the identifier of each Item(accordion). We know all the items in a loop have an identifier starting with zero.
The opening of the item will be based on the fact that its identifier is equal to activeAcc and the toggle function
take this identifier as a parameter in order to be able to assign activeAcc the identifier of the corresponding item.
import React, { Component } from 'react';
import axios from 'axios';
import Icon from '../Icon/Icon';
class RestInfo extends Component {
constructor() {
super();
this.toggle = this.toggle.bind(this);
this.state = {
disruptions: [],
activeAcc: 0
};
}
componentDidMount() {
axios
.get('https://trasnport-api-isruptions-v2.azure-api.net/Disruption/v2/', {
headers: {
'Ocp-Apim-Subscription-Key': '55060e2bfbf743c5829b9eef583506f7'
}
})
.then(response => {
this.setState({
disruptions: response.data.disruptions
});
});
}
toggle(key) {
this.setState(prevState => {
return {
activeAcc: prevState.activeAcc===key?false:key
};
});
}
render() {
const { disruptions, activeAcc } = this.state;
return (
<div>
{disruptions.length > 0 ? (
disruptions.map((post,key) => {
return (
<div key={post.id}>
<div className={`wmnds-accordion ${activeAcc===key ? 'wmnds-is--open' : ''}`}>
<button
type="button"
aria-controls="accordion-custom-01"
className="wmnds-accordion__summary-wrapper"
aria-expanded="true"
onClick={()=>this.toggle(key)}
>
<div className="wmnds-accordion__summary">
<div className="wmnds-grid wmnds-grid--align-center">
<span className="wmnds-disruption-indicator-small wmnds-col-auto wmnds-m-r-md">
<svg className="wmnds-disruption-indicator-small__icon">
<Icon iconName="modes-isolated-bus" iconClass="modes-isolated-bus" />
</svg>
<svg className="wmnds-disruption-indicator-small__icon">
<Icon iconName="general-warning-circle" iconClass="general-warning-circle" />
</svg>
</span>
<div className="wmnds-col-auto">
<strong>{post.title}</strong>
</div>
</div>
</div>
<svg className="wmnds-accordion__icon">
<Icon iconName="general-expand" iconClass="general-expand" />
</svg>
<svg className="wmnds-accordion__icon wmnds-accordion__icon--minimise">
<Icon iconName="general-minimise" iconClass="general-minimise" />
</svg>
</button>
<div className="wmnds-accordion__content" id="accordion-custom-01">
<h4 className="serviceAffected">Affected Service(s) </h4>
{post.servicesAffected.map(affected => (
<div key={affected.id}>
<span className="wmnds-disruption-indicator-small wmnds-col-auto wmnds-m-r-md">
<svg className="wmnds-disruption-indicator-small__icon">
<Icon iconName="modes-isolated-bus" iconClass="modes-isolated-bus">
{affected.serviceNumber}
</Icon>
</svg>
<svg className="wmnds-disruption-indicator-small__icon">
<Icon iconName="general-warning-circle" iconClass="general-warning-circle" />
</svg>
</span>
<h5>routeDesc:</h5>
{affected.routeDesc}
<h5>serviceNumber:</h5>
{affected.serviceNumber}
<h5>direction</h5>
{affected.direction}
</div>
))}
<p>{post.title}</p>
<p>{post.description}</p>
<p>{post.disruptionSeverity}</p>
<p>{post.mode}</p>
<p>{post.disruptionSeverity}</p>
</div>
</div>
</div>
);
})
) : (
<div>
<div className="wmnds-loader" />
</div> )}
</div>
);
}
}
export default RestInfo;
class RestInfo extends Component {
constructor() {
super();
this.toggle = this.toggle.bind(this);
this.state = {
disruptions: [],
/** initially all toggles are in false state [false, false, ...] */
activeAccordions: disruptions.map(i => false) /** [false, false, ..... till disruptions.length] */
};
}
componentDidMount() {
axios
.get('https://trasnport-api-isruptions-v2.azure-api.net/Disruption/v2/', {
headers: {
'Ocp-Apim-Subscription-Key': '55060e2bfbf743c5829b9eef583506f7'
}
})
.then(response => {
this.setState({
disruptions: response.data.disruptions
});
});
}
toggle(accordionIndex) {
let activeAccordions = this.state.disruptions.map(i => false) /** close all open accordions */
activeAccordions[accordionIndex] = !activeAccordions[accordionIndex] /** toggle the intended accordion */
this.setState({ activeAccordions: activeAccordions });
}
render() {
const { disruptions, activeAccordions } = this.state;
return (
<div>
{disruptions.length > 0 ? (
disruptions.map((post, postIndex) => {
return (
<div key={post.id}>
<div className={`wmnds-accordion ${activeAccordions[postIndex] ? 'wmnds-is--open' : ''}`}>
I have 3 divs, which I'm displaying in a ReactJS app.
I'm displaying the divs, by looping through an object of classNames stored in state. (Each className has it's own CSS styling, which displays a color - hat1, hat2, hat3).
OnClick, I want to get the div coordinates/position of any of the 3 divs I click on.
I've tried using React.createRef() and getBoundingClientRect(). However, both methods give me the same coordinates, no matter which div I click on.
It looks like it's returning the coordinates of the <section> tag, rather than the target div element I click on...
What am I doing wrong?
class Cylinders extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.state = {
divs: [
{
className: 'hat1'
},
{
className: 'hat2'
},
{
className: 'hat3'
}
]
}
}
componentDidMount = () => {
console.log('mount');
}
handleClick = (item, i) => {
console.log('item', item);
console.log('i', i);
var divCoordinates = ReactDOM.findDOMNode(this).getBoundingClientRect();
console.log(divCoordinates, 'divCoordinates');
// const node = this.myRef.current;
// console.log('node', node);
}
render() {
return (
<section>
<div className="columns is-mobile">
<div className="column">
<h1 className="title has-text-black is-size-2">Cylinders Game</h1>
<button className="has-text-black">Ball container</button>
</div>
</div>
<div className="columns is-mobile">
<div className="colum ballContainer">
<div className="ball"></div>
</div>
</div>
<div className="columns is-mobile">
{this.state.divs.map((item, i) => {
return (
<div className="column">
<div className="columns is-multiline">
<div
onClick={() => this.handleClick(item, i)}
className={item.className}
key={item.name + i}
ref={el => this.containerLine = el}
> {i}
</div>
</div>
</div>
)
})}
</div>
</section>
);
}
}
export default Cylinders;
I got it working!
I simply targeted the item, with item.target.
var divCoordinates = ReactDOM.findDOMNode(item.target).getBoundingClientRect();
I am using nuka carousel in react TypeScript a saleor pwa react ts app
Nuka carousel not showing items cause nuka is passing slideHeight 0 to slider -frame
Code Example:
render() {
const { title } = this.props;
const { products } = this.state;
const productsList = products.map((product: any) => (
<Link to={'/product/' + product.id} key={product.id}>
<ProductListItem product={product} />
</Link>
))
return (
<div className="products">
<div className="container">
<h3>{title}</h3>
<Carousel>
{productsList}
</Carousel>
</div>
</div >
)
}
I solve it by just add if (products.length)
Solution:
render() {
const { title } = this.props;
const { products } = this.state;
if (products.length) {
const productsList = products.map((product: any) => (
<Link
to={'/product/' + product.id} key={product.id}
>
<ProductListItem product={product} />
</Link>
))
return (
<div className="products">
<div className="container">
<h3>{title}</h3>
<Carousel>
{productsList}
</Carousel>
</div>
</div >
)
}
return null;
}
There is no need to override css this is proper way
Here is solution Via Override css. this is for those who is interested in css override