react-bootstrap Carousel breaks when changing content during transition - reactjs

video of issue:
When the carousel is transitioning from one slide to another, if another entry from a dataset is choosen, before the slide finishes its transition, the new images are rendered, but controls/timer are broken, so the carousel is not slidable any more (manually or automatically).
activeFish is a useState object with some fish data that gets changed with setActiveFish(newActiveFishDataObject) when a list item is clicked
Anyone have ideas for a solution?
<Carousel>
{activeFish['Image Gallery'].map((imageData, i) => (
<Carousel.Item key={imageData.title + i}>
<div className="image-container">
<img
className="d-block w-100"
src={imageData.src}
alt={imageData.alt}
/>
</div>
<Carousel.Caption>
<p>{imageData.title}</p>
</Carousel.Caption>
</Carousel.Item>
))}
</Carousel>
Replicate-
https://codesandbox.io/s/frosty-night-yckxro (thanks to #IgorGonak)
onload, immediately click the down arrow on the dropdown to expose options, mouse over option b, wait for transition to happen, click b rite in the middle of the transition. observe controls wont work for the 'b' dataset

I've created a dummy data with three entries and three images each entry. Then I built a select to select one and the carousel like yours - it is working on my side: When I choose another entry, the images in carousel are adjusted. The transition is working and is performed after each defined timeout:
https://codesandbox.io/s/frosty-night-yckxro
import React from "react";
import { Carousel, Form, Stack } from "react-bootstrap";
import data from "./fishData";
export default function App() {
const [activeFish, setActiveFish] = React.useState(data[0]);
const onSelect = (event) => {
const choosenFishName = event.target.value;
const choosenFish = data.find((fish) => fish.name === choosenFishName);
setActiveFish(choosenFish);
};
return (
<Stack className="m-3">
<Form.Group id="fish-select" className="mb-3" style={{ width: "18rem" }}>
<Form.Select value={activeFish.name} onChange={onSelect}>
{data.map((entry) => (
<option key={entry.name} value={entry.name}>
{entry.name}
</option>
))}
</Form.Select>
</Form.Group>
<p>Choosen fish is: {activeFish.name}</p>
<Carousel style={{ height: 200, width: 300 }}>
{activeFish["Image Gallery"].map((img) => {
return (
<Carousel.Item key={img.title}>
<img className="d-block w-100" src={img.src} alt={img.alt} />
<Carousel.Caption>{img.title}</Carousel.Caption>
</Carousel.Item>
);
})}
</Carousel>
</Stack>
);
}
Edit:
According to https://github.com/react-bootstrap/react-bootstrap/issues/6352 the problem here is that there is a state called "isSliding" in Carousel component. If it's true, the controls are deactivated. When we change children while sliding "isSliding" is true, but it's not set to false in the end, because the appropriate callback is never fired.
The solution is to reset the component by adding unique key to it:
<Carousel
key={activeFish.name}
interval={2000}
style={{ height: 200, width: 300 }}
>
{activeFish["Image Gallery"].map((img) => {
return (
<Carousel.Item key={img.title}>
<img className="d-block w-100" src={img.src} alt={img.alt} />
<Carousel.Caption>{img.title}</Carousel.Caption>
</Carousel.Item>
);
})}
</Carousel>

Related

React not rendering component with inline styling on IOS

I ran into a problem with my component not re-rendering after a state change, when using props driven inline styling, on IOS/iphone (not tested on ipad).
Here is the code sandbox: https://codesandbox.io/s/confident-meitner-2sju9?file=/src/FlexboxGap.jsx
Edit: adding the code here too as requested:
import { useState } from "react";
import "./FlexboxGap.styles.css";
function FlexboxGap({ visible, containerCat, propertiesCat }) {
const [gap, setGap] = useState(0);
const [rowGap, setRowGap] = useState(0);
const [columnGap, setColumnGap] = useState(0);
function onGapChange(event) {
setGap(event.target.value);
setRowGap(event.target.value);
setColumnGap(event.target.value);
}
function onRowChange(event) {
setRowGap(event.target.value);
}
function onColumnChange(event) {
setColumnGap(event.target.value);
}
return (
<div className="main-container">
<div>
{" "}
<div>gap: {gap}px</div>
<input
type="range"
className="slider-bar"
min={0}
max={20}
value={gap}
onChange={onGapChange}
/>
</div>
<div>
{" "}
<div>row-gap: {rowGap}px</div>
<input
type="range"
className="slider-bar"
min={0}
max={20}
value={rowGap}
onChange={onRowChange}
/>
</div>
<div>
{" "}
<div>column-gap: {columnGap}px</div>
<input
type="range"
className="slider-bar"
min={0}
max={20}
value={columnGap}
onChange={onColumnChange}
/>
</div>
<div
style={{
width: "100vw",
height: "250px",
backgroundColor: "grey",
display: "flex",
flexFlow: "row wrap",
justifyContent: "flex-start",
alignContent: "flex-start",
rowGap: `${rowGap}px`,
columnGap: `${columnGap}px`
}}
>
<div className="graph-item">item</div>
<div className="graph-item">item</div>
<div className="graph-item">item</div>
<div className="graph-item">item</div>
<div className="graph-item">item</div>
<div className="graph-item">item</div>
<div className="graph-item">item</div>
<div className="graph-item">item</div>
<div className="graph-item">item</div>
<div className="graph-item">item</div>
{/* This last invisible div is a hack to "force" react to re-render, by referencing the props rowGap and columnGap.
Otherwise, the rerender fails to happen on iphones. Working on Android
and desktop */}
<div className="hack-ghost">{`${rowGap}${columnGap}`}</div>
</div>
</div>
);
}
export default FlexboxGap;
While the state of rowGap and rowColumn is successfully changed, as well as the style, the div that depends on it does not get updated on iphones (fully updated).
Works on desktop and android devices.
I found a hack by adding a div that references the 2 states I need, and everything now "works".
The problem is:
1- it is hacky and
2-i am not sure i fully understand what is going on.
I thought of this hack by thinking that "i needed react to notice that the state changed inside this element".
Is it an issue with the way that inline css works // an ios implementation particularity?
Kind regards.

How to make the Collapse position to bottom-right after clicking on expand icon in AntD

I am using AntD Collapse for displaying a list of items after expand icon is clicked.
I want the position of expandIcon to go to bottom-right after all the list of the data when expand icon is clicked (just like in google news), but found only two options (left|right) for 'expandIconPosition', no option for top or bottom.
How can we align the expandIcon to bottom-right, when expand icon is clicked?
Few lines from the code for reference:
<Collapse
ghost
style={{ marginTop: "-1vh" }}
expandIcon={({ isActive }) => (
<DownOutlined
style={{ marginTop: "-2vh" }}
rotate={isActive ? 180 : 0}
/>
)}
expandIconPosition="right"
>
<Panel>
<div>
{list()} //list of items
</div>
</Panel>
</Collapse>
Here's one possible solution. Make Collapse a controlled component by specifying activeKey prop and then the value of it will be based on state. Then, by tracking the activeKeys state you can now do a conditional rendering (hide and show) on icons:
const [activePanelKeys, setActivePanelKeys] = useState([]);
const handlePanelIconClick = (panelKey, makeActive) => {
if (makeActive) {
setActivePanelKeys([...activePanelKeys, panelKey]);
} else {
setActivePanelKeys(activePanelKeys.filter((aPK) => aPK !== panelKey));
}
};
<Collapse
activeKey={activePanelKeys}
expandIconPosition="right"
expandIcon={() => <DownOutlined />}
// expandIcon={(panelProps) => (
// <DownOutlined
// onClick={() => handlePanelIconClick(panelProps.panelKey, true)}
// />
// )}
onChange={e => setActivePanelKeys(e)} //if you want to click only icon, comment this and uncomment the above expandedIcon
>
<Panel
header="This is panel header 1"
key="p1"
showArrow={activePanelKeys.indexOf("p1") === -1}
>
<div>{text}</div>
{activePanelKeys.indexOf("p1") !== -1 && (
<div style={{ textAlign: "right", marginTop: 10 }}>
<UpOutlined onClick={() => handlePanelIconClick("p1", false)} />
</div>
)}
</Panel>
{/* <PanelContent header="This is panel header 2" key="p2">
{text}
</PanelContent> */}
</Collapse>;
Here is the complete sample code:
Note: I tried to make a reusable Panel component but it seems that the reveal animation were gone. You can uncomment the commented PanelContent on the code to see the difference.
Hope that I hit what you want.

Display correct components on change with selected value from dropdown?

I have a component called DropDown Single and that has an options param Object and Database. If the Object is selected then I want to show the Autocomplete component for the name Table. If Database is selected I want it to show the autocomplete named database. I have tried to cut down the code with only the components I need to be using. The onclick itself doesn't actually do anything yet but I am assuming I will need it when I want it to show the right component.
<div className='Main UnitTestsAutoCompleteWrapper' style={{display: 'flex', justifyContent: 'space-between'}}>
<div>
<DropdownSingle
options={['Object', 'Database']}
value='Object'
name='Search for unit tests'
onclick={onChange}
/>
</div>
<div style={{marginRIght: '10px'}} >
<AutoCompleteSingle
name='Table'
label='Table'
options={autoComplete.tablesAutoComplete}
onChange={autoComplete.onTableAutoCompleteFieldUpdate}
onSelect={onSelect}
uniqueIdentifier={0}
initialValue={targetObject}
/>
</div>
<div>
<AutoCompleteSingle
name='Database'
label='Database'
options={autoComplete.databasesAutoComplete}
onChange={(e) => onFieldUpdate(e, 'database')}
onSelect={onDatabaseSelect}
uniqueIdentifier={1}
triggerOnSelectOnBackspace
/>
</div>
</div>
Use this as a simple example to guide you, you should really read the documentation. This is all covered under Conditional Rendering:
import React, { useState } from "react";
export default function App() {
const [selectValue, setSelectValue] = useState("cow");
return (
<div>
<select onChange={(e) => setSelectValue(e.target.value)}>
<option value="cow">Cow</option>
<option value="sheep">Sheep</option>
</select>
{selectValue === "cow" ? <p>Cow</p> : <p>Sheep</p>}
</div>
);
}

Put linebreak between components rendered with .map and boostrap

I´m rendering an array, every element of the array is showed with component "Post", but the elements are in the same line, I tried with
return (
<div style={{ height: 'calc(100vh - 65px)' }} className="d-flex justify-content-center align-items-center">
{posts.map((post) => {
return <Post key={post._id} post={post} />
})}
</div>
);
The component "Post" is the next:
I´m using Boostrap por the css styles
return (
{usuario.nombre}
Special title treatment
{texto}
Go somewhere
2 days ago
);
My result (All the elements are in horizontal position and I need them in vertical position):
The father component is root.
If I delete the css classes of the component "Post", teh problem remains.
Have you tried this.
return (
<div>
{posts.map((post) => {
return <><Post key={post._id} post={post} /><br /></>
})}
</div>
);
I only changed the class of div where I put the .map ;| adn the problem was solved.
className="container" instead className="d-flex justify-content-center align-items-center">

React Bootstrap Cards and CardDeck Components - responsive layout

I'm using React Bootstrap and am pulling data from an API using Axios. I'm trying to display the data from that API in bootstrap Card / CarDeck components. I have most of it working using the code below but I'm having problems with the layout.
I'd like to be able to have the card deck be responsive and show a flexible number of cards per row depending on the screen size. At the moment if there are lots of items in the response each card is ridiculously thin and can't be viewed. What is the correct way to do this?
import React, { useState, useEffect } from "react";
import CardDeck from "react-bootstrap/CardDeck";
import Card from "react-bootstrap/Card";
import axios from "axios";
import Container from "react-bootstrap/Container";
const CardColumns = () => {
const [cardInfo, setData] = useState([]);
console.log(cardInfo);
useEffect(() => {
axios
.get(
"https://webhooks.mongodb-realm.com/api/client/v2.0/app/cards-fvyrn/service/Cards/incoming_webhook/getAllCards"
)
.then((response) => {
setData(response.data);
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
}, []);
const renderCard = (card, index) => {
return (
<Card style={{ width: "18rem" }} key={index} className="box">
<Card.Img variant="top" src="holder.js/100px180" src={card.image} />
<Card.Body>
<Card.Title>
{index} - {card.manufacturer}
</Card.Title>
<Card.Text>{card.player}</Card.Text>
</Card.Body>
</Card>
);
};
return <CardDeck>{cardInfo.map(renderCard)}</CardDeck>;
};
export default CardColumns;
In the end this is the solution I went with the below. Key I think were:
Wrapping the cardDeck in <Container> and then wrapping the cards with <Col className="container-fluid mt-4">:
`
const renderCard = (card, index) => {
return (`
<Col className="container-fluid mt-4">
{/* <Card key={index} className="box"> */}
<Card style={{ width: "18rem" }} key={index} className="box">
<Card.Header>
{card.brand} - {card.series}
</Card.Header>
<Card.Img variant="top" src={card.front_image} fluid />
<Card.Body>
<Card.Title>
{card.player} (#
{card.card_number.$numberDouble}) {card.variation}
</Card.Title>
{/* <Card.Text className="d-flex">{card.player}</Card.Text> */}
{/* <Card.Text>{card.player}</Card.Text> */}
</Card.Body>
<ListGroup className="list-group-flush">
<ListGroupItem>
Print Run - {card.print_run.$numberDouble}
</ListGroupItem>
<ListGroupItem>Career Stage - {card.career_stage} </ListGroupItem>
<ListGroupItem>For Trade - {card.forTrade}</ListGroupItem>
</ListGroup>
<Card.Footer className="text-muted">{card.team}</Card.Footer>
</Card>
</Col>
//{/* </Col> */}
);
};
return (
<Container>
<Button variant="primary">Filter By Brand</Button>{" "}
<Button variant="primary">Filter By Player</Button>{" "}
<Button variant="primary">Filter By Team</Button>
<CardDeck>{cardInfo.map(renderCard)}</CardDeck>
</Container>
);`
Here are my suggestions for this issue:
Each card should have a min-width to ensure that they do not shrink below a certain amount. So instead of width: "18rem" try min-width: "18rem". If your CSS is properly set up it should cause other cards to overflow to the next row.
You can make use of media-queries or grid layout as mentioned to determine how many cards you want to show for various screen types based on their varying widths say see this link media-query-breakpoints react-bootstrap-grid
Also you can try using CSS flexbox layout, I have an article on this CSS Flex Box
Try to use a col-'n' to each card instead of a fix width.
...
import { Card, Col } from 'react-bootstrap';
...
<Col md="n"> // n must be your desired width like
<Card key={index} className="box">
...
</Col>
You can see more at: Bootstrap Card Sizing

Resources