How to pass (this) props to sibling components in React - javascript

I'm mapping a list of data in which each listing has a button which opens a modal to display a full listing's data. Except instead of mapping each entry with a button containing its own modal component mapped per each listing, I'd like to have each button trigger an outside modal component.
handleTaskModal = () =>{
<TaskModal {...state} />
}
render() {
const { tasks } = this.state;
return (
<div>
<Container color="light">
<Row className="flex-row">
{tasks.map(({ name, image, description }) => (
<Col key={name}
xs="11"
sm="6"
lg="4"
className="mx-auto my-sm-2 my-lg-1"
>
<Card className="task-card border-0 rounded shadow-sm my-3">
<CardImg
top
width="100%"
src={image}
style={{
width: "100%",
height: "45vh",
objectFit: "cover"
}}/>
<CardBody>
<CardTitle className="card-title p-0 text-left">{name}</CardTitle>
</CardBody>
<Button
color="primary"
className="w-50 rounded mb-3 mx-auto shadow-sm"
style={{
borderRadius:"20px"
}}
onClick={this.handleTaskModal}
> View Task
</Button>
</Card>
</Col>
))}
</Row>
</Container>
</div>
);
}

I want to share props between each entry to one modal component
You don't do this in react. Data always flows from parents to children, never between siblings. Actions can flow up from a child to the parent by providing a handler. The correct way to solve this is to store the currently viewed task in the parent state and display the modal accordingly:
class Tasks extends Component {
state = {
viewed: null,
};
handleShow = name => this.setState({viewed: name});
handleClose = () => this.setState({viewed: null});
render() {
const {tasks, viewed} = this.state;
return (
<div>
<Container>
<Row>
{/* details omitted for simplicity */}
{tasks.map(({name, image, description}) => (
<Col key={name}>
<Button onClick={() => this.handleShow(name)}>View Task</Button>
</Col>
))}
</Row>
</Container>
{viewed && (
<TaskModal
task={tasks.find(task => task.name === viewed)}
onClose={this.handleClose}
/>
)}
</div>
);
}
}
Also note that handlers should modify the state to trigger a re-render. Returning something from them (e.g. components) doesn't have any effect.

Related

How to set state in Carousel slider from a button click in react

I am using the react-bootstrap carousel component here: https://react-bootstrap.github.io/components/carousel/) and I am trying to set the 'defaultActiveIndex' prop (not mentioned in docs but it allows the carousel to keep autoplaying instead of using 'activeIndex' prop which prevents autoplay).
By setting this property through a button click, I want to set 'defaultActiveIndex', which should change the Carousel current position and then continue autoplaying afterwards.
I have this code in the component that holds the carousel state:
const [carouselIndex, setCarouselIndex] = useState(0);
function handleClick(e, param){
setCarouselIndex(param);
};
and the same component returns this:
<button onClick={(e) => handleClick(e, 3)}>Filter</button>
And the Carousel within the same component has the following props:
<Carousel
className='carousel'
variant='dark'
fade={true}
controls={true}
slide={true}
indicators={false}
interval={1300}
defaultActiveIndex={carouselIndex}
Currently I can see the handleClick function tracking the '3' coming from the button as param, but the 'setCarouselIndex' setter is not working and does not set 'carouselIndex' in the 'defaultActiveIndex' Carousel prop.
Full component code:
import React, { useEffect, useState } from 'react';
import Carousel from 'react-bootstrap/Carousel';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import './../App.css';
function Product(props) {
const [carouselIndex, setCarouselIndex] = useState(0);
function handleClick(e, param){
setCarouselIndex(param);
};
return (
<div>
<Row>
<Col>
</Col>
<Col>2</Col>
<Col>3</Col>
</Row>
<Row>
<Col>
</Col>
<Col>
<Carousel
className='carousel'
variant='dark'
fade={true}
controls={true}
slide={true}
indicators={false}
interval={1300}
defaultActiveIndex={carouselIndex}
>
{props.products.map(product =>
<Carousel.Item
key={product.product_id}
className="carousel-item"
>
<div>
<img
className="d-block w-100"
src={product.image_url}
alt=""
style={{
borderRadius:"5px",
}}
/>
<div className="product-card-info"
style={{
borderRadius:"5px",
}}
>
<p>Name: {product.product_name}</p>
<p>Description: {product.description}</p>
<p>Sizes(s): {product.size}</p>
<p>{product.currency} {product.price}</p>
<p>Delivery cost: {product.delivery_cost}</p>
<p>{product.in_stock}</p>
<p><a href={product.deeplink}>
<span><i></i> Visit Product </span>
</a>
</p>
<p><button onClick={(e) => handleClick(e, 3)}>Filter</button></p>
</div>
</div>
</Carousel.Item>
)}
</Carousel>
</Col>
<Col>
</Col>
</Row>
<Row>
<Col>1</Col>
<Col></Col>
</Row>
</div>
)
}
export default Product;
Any ideas or tips for how I could get this to work would be very much appreciated.
Thanks for your time all.

React Modal returns last value of mapped data

Struggling with this issue with the earlier answers not utilizing map function / functional components. When I click my Card, the modal only shows the data of the last Modal:
export const ModalCard = (props) => {
const productData = props.data;
const [modal, setModal] = React.useState(false);
const toggle = () => setModal(!modal);
return (
<Row>
{productData.map((v, i) => (
<Col className="py-4 btn" key={i} xs={12} md={4} lg={2}>
<div className="pb-4" onClick={toggle}>
<div className="product_card_padding">
<div className="pt-4">
<span className="card_product_subtitle">{v.headline}</span>
</div>
</div>
<Modal isOpen={modal}
toggle={toggle}
centered
>
<ModalBody className="product_modal" onClick={toggle}>
<div className="row pt-3 bg_white">
<Col>
<div>
<span className="card_product_subtitle">{v.headline}</span>
</div>
</Col>
</div>
</ModalBody>
</Modal>
</div>
</Col>
))}
</Row>
);
}
According to your code, multiple modals will be opened and you will see the last modal.
If you have 10 products, then 10 modals will be opened.
My suggestion is that you need to define a global modal outside map function and you need to define a new state variable to represent the selected product to be rendered on the modal.
selectedInd holds the data index to be rendered on modal.
const [selectedInd, setSelectedInd] = React.useState(-1);
Then toggle function would be changed to set -1 to hide modal.
const toggle = () => setSelectedInd(-1);
And move the modal outside map.
Try to use the following code pattern.
export const ModalCard = (props) => {
const productData = props.data;
const [selectedInd, setSelectedInd] = React.useState(-1);
const toggle = () => setSelectedInd(-1);
const modal = selectedInd >= 0 && (productData && productData.length > selectedInd);
return (
<React.Fragment>
<Row>
{productData.map((v, i) => (
<Col className="py-4 btn" key={i} xs={12} md={4} lg={2}>
<div className="pb-4" onClick={()=>setSelectedInd(i)}>
<div className="product_card_padding">
<div className="pt-4">
<span className="card_product_subtitle">{v.headline}</span>
</div>
</div>
</div>
</Col>
))}
</Row>
{modal && <Modal isOpen={modal} toggle={toggle} centered>
<ModalBody className="product_modal" onClick={toggle}>
<div className="row pt-3 bg_white">
<Col>
<div>
<span className="card_product_subtitle">{productData[selectedInd].headline}</span>
</div>
</Col>
</div>
</ModalBody>
</Modal>}
</React.Fragment>
);
}

How to make button color change if button selected and unselected

I need help to add a feature like if the user clicked on the button it changes the color to danger of a button so it would feel like it is selected and if the user clicks the same button it should come back to the original color like primary.
I want to make it a little dynamic as well so it should know which button is being selected in the mapped objects.
I have the following code for now.
<Row xs={3} md={4} className="g-3 py-2 px-2">
{data.map((postData) => {
return (
<Button
className="mx-2"
style={{
width: "10rem",
height: "13rem",
lineHeight: "14px",
fontSize: "12px"
}}
key={postData.id}
>
<Card.Img variant="top" src={postData.image} />
<Card.Body>
<Card.Title className={style.tile}>
{postData.title}
</Card.Title>
</Card.Body>
</Button>
);
})}
</Row>
It's also running in the CodeSandBox
If you can give me a little idea of how to approach this?
I tried using useState for that purpose
onClick={() => setCls((cls) => (cls === "red" ? "green" : "red"))
But that changes the color of all the buttons at one click, you can see this in codesandbox.
try this Code :
import React, { useState } from "react";
//react bootstrap components
import { Container, Row, Card, Button } from "react-bootstrap";
//scss
import style from "./styles.module.scss";
//data for post
import data from "./data.json";
const App = () => {
const [selectedBtnList, setSelectedBtnList] = useState([])
const selectBtnAddHandler = (btnId) => {
const isInList = selectedBtnList.some(item => item === btnId)
if(isInList){
setSelectedBtnList(selectedBtnList.filter(item => item !== btnId))
}else {
setSelectedBtnList([...selectedBtnList,btnId])
}
}
return (
<>
<Container fluid={true}>
<Row xs={3} md={4} className="g-3 py-2 px-2">
{data.map((postData) => {
return (
<Button
className="mx-2"
style={{
width: "10rem",
height: "13rem",
lineHeight: "14px",
fontSize: "12px"
}}
key={postData.id}
variant={selectedBtnList.includes(postData.id) ? "danger" : "primary"}
onClick={()=> selectBtnAddHandler(postData.id)}
>
<Card.Img variant="top" src={postData.image} />
<Card.Body>
<Card.Title className={style.tile}>
{postData.title}
</Card.Title>
</Card.Body>
</Button>
);
})}
</Row>
</Container>
</>
);
};
export default App;

Using Reactstrap: How to toggle only one Collapse at a time?

I am using Reactstrap to open and collapse multiple cards. Once opened, they stay open, and since I plan to use more of them (for articles), this will be a mess. Once I click on a button and open a card, I would like the others to close, so only one card is displayed at a time. How can I achieve this?
const [isOpenInfo, setIsOpenInfo] = useState(false);
const toggleInfo = () => setIsOpenInfo(!isOpenInfo);
const [isOpenArticle1, setIsOpenArticle1] = useState(false);
const toggleArticle1 = () => setIsOpenArticle1(!isOpenArticle1);
const [isOpenArticle2, setIsOpenArticle2] = useState(false);
const toggleArticle2 = () => setIsOpenArticle2(!isOpenArticle2);
In my menu, I have a button "More Info", when clicked, it opens a list of collapsed articles and when clicking on each title, it opens the article (but I just want one article to open at a time). So it's like a collapse inside a collapse...
<Button className="info-button" color="primary" onClick={toggleInfo}>
More Info
</Button>
<Collapse isOpen={isOpenInfo}>
<Card className="card">
<CardBody className="card-body">
<div className="section section-articles">
<div className="articles-buttons">
<Button
className="article2-button"
color="primary"
onClick={toggleArticle2}
>
<h3>Article 2</h3>
</Button>
<Button
className="article1-button"
color="primary"
onClick={toggleArticle1}
>
<h3>Article 1</h3>
</Button>
</div>
<Collapse isOpen={isOpenArticle2}>
<Card className="card">
<CardBody className="card-body">
<Article2 />
</CardBody>
</Card>
</Collapse>
<Collapse isOpen={isOpenArticle1}>
<Card className="card">
<CardBody className="card-body">
<Article1 />
</CardBody>
</Card>
</Collapse>
</div>
</CardBody>
</Card>
</Collapse>
You can create a default variable consisting all your articles and use it to set the state. Create a single state variable for all of your collapsible.
const DEFAULT_ARTICLES = {
article1: false,
article2: false,
};
const [articles, setArticles] = useState(DEFAULT_ARTICLES);
const toggleArticle = (key) => setArticles({
...DEFAULT_ARTICLES,
[key]: true,
});
And on your render function use the key to open the collapse and toggle the collapse.
<Collapse isOpen={isOpenInfo}>
<Card className="card">
<CardBody className="card-body">
<div className="section section-articles">
<div class="articles-buttons">
<Button
className="article2-button"
color="primary"
onClick={() => toggleArticle('article2')}
>
<h3>Article 2</h3>
</Button>
<Button
className="article1-button"
color="primary"
onClick={() => toggleArticle('article1')}
>
<h3>Article 1</h3>
</Button>
</div>
<Collapse isOpen={articles['article1']}>
<Card className="card">
<CardBody className="card-body">
<Article2 />
</CardBody>
</Card>
</Collapse>
<Collapse isOpen={articles['article2']}>
<Card className="card">
<CardBody className="card-body">
<Article1 />
</CardBody>
</Card>
</Collapse>
</div>
</CardBody>
</Card>
</Collapse>
You can use one state to control all the collapses.
const [openedCollapse, setOpenedCollapse] = useState("");
const openCollapse = e => { // this is the button onClick handler
setOpenedCollapse(e.target.dataset.collapse);
};
Then your jsx looks like this:
<Button
className="article1-button"
color="primary"
data-collapse="article1" // A dataset param
onClick={openCollapse}>
<h3>Article 1</h3>
</Button>
<Collapse isOpen={openedCollapse === "article1"}>
<Card className="card">
<CardBody className="card-body">
<Article2 />
</CardBody>
</Card>
</Collapse>
dataset info
You can use an object as state with a callback function when toggling the More Info collapse and then utilize a simple string to determine which article should be opened when the main Collapse is open and a Button was clicked inside of it.
For example, updating whether or not the main collapse is open:
const toggleMoreInfo = () => {
setState(prevState => {
// this gives us access to the current state when setState is executed
// then we can inverse a boolean when the More Info button is clicked
return {
article: "", // resets the open article
moreInfoOpen: !prevState.moreInfoOpen // false => true || true => false
}
})
}
For example, updating which article should be opened:
const handleArticleOpen = (article) => {
setState((prevState) =>
return {
// keep whatever is in state as is by spreading it out (in this case, "moreInfoOpen" stays unchanged)
...prevState, //
// and just override the article with a passed in string
article
}));
};
When dealing with coupled state, I like to use objects over individual states, since it's easier to keep both sets of state in sync. For a demo and the full code, look below...
Working demo:
Code
import * as React from "react";
import { Button, Card, CardBody, Collapse } from "reactstrap";
import "./styles.css";
import "bootstrap/dist/css/bootstrap.min.css";
export default function App() {
const [state, setState] = React.useState({
articleOpen: "",
moreInfoOpen: false
});
const { article, moreInfoOpen } = state;
const toggleMoreInfo = () => {
setState((prevState) => ({
article: "",
moreInfoOpen: !prevState.moreInfoOpen
}));
};
const handleArticleOpen = (article) => {
setState((prevState) => ({
...prevState,
article
}));
};
return (
<div className="app">
<Button className="info-button" color="primary" onClick={toggleMoreInfo}>
More Info
</Button>
<Collapse isOpen={moreInfoOpen}>
<Card className="card">
<CardBody className="card-body">
<div className="section section-articles">
<div className="articles-buttons">
<Button
className="article2-button"
color="primary"
onClick={() => handleArticleOpen("2")}
>
<h3>Article 2</h3>
</Button>
<Button
className="article1-button"
color="primary"
onClick={() => handleArticleOpen("1")}
>
<h3>Article 1</h3>
</Button>
</div>
<Collapse isOpen={article === "2"}>
<Card className="card">
<CardBody className="card-body">
<div>Article 2</div>
</CardBody>
</Card>
</Collapse>
<Collapse isOpen={article === "1"}>
<Card className="card">
<CardBody className="card-body">
<div>Article 1</div>
</CardBody>
</Card>
</Collapse>
</div>
</CardBody>
</Card>
</Collapse>
</div>
);
}

How to open a collapse bar by onClick of an icon in another Component?

I have a component MockTable which shows a list of some items in a table along with actions such as view, delete and update. I need to open a collapse bar in another component AddMock by clicking any of these actions in MockTable. These actions are icons. I'm using semantic-ui react and react-Bootstrap.
actions in Mocktable =>
<Icon link name="eye" />
<Icon link name="edit" />
<Icon link name="delete" />
AddMock =>
const AddMock = () => {
return (
<div className="row">
<Accordion style={{ paddingLeft: "32px" }}>
<Card className="collapseStyle">
<Card.Header>
<Accordion.Toggle as={Button} variant="link" eventKey="0">
Add Mock
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>
<Container className="mockbody">
<Header as="h3">Hierarchy</Header>
<TabContent />
</Container>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
</div>
);
};
How to implement this scenario?
In the component that possess the actions edit, view & delete, you can declare a state variable isActive and pass it through props to the component AddMock.
Assuming Mocktable.js looks something like following.
MockTable.js:
const MockTable = () => {
const [isActive, setIsActive] = useState(false);
return (
<Icon link name="eye" onClick={()=>setIsActive(!isActive)} /> // this will act as toggle (show & hide)
<Icon link name="edit" onClick={()=>setIsActive(!isActive)} />
<Icon link name="delete" onClick={()=>setIsActive(!isActive)} />
<AddMock isActive={isActive} />
)
}
AddMock.js
const AddMock = ({ isActive }) => {
return (
<div className="row">
<Accordion active={isActive} style={{ paddingLeft: "32px" }}> // passing isActive here to show/hide.
<Card className="collapseStyle">
<Card.Header>
<Accordion.Toggle as={Button} variant="link" eventKey="0">
Add Mock
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>
<Container className="mockbody">
<Header as="h3">Hierarchy</Header>
<TabContent />
</Container>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
</div>
);
};

Categories

Resources