Create multiple buttons React JS - javascript

I have the ability to render my single button with all my options inside of the one button like this:
What I want to do is separate these into individual buttons
My code is as follows (the first button is the one in question, how do I make them individual?)
function Filters({ filter, setFilter, location }) {
return (
<div className="bg-transparent outline-none text-sm text-gray-600 placeholder-grey-400">
<div className="pl-2 pr-2 gap-3">
<Button
colorScheme="teal"
size="xs"
onClick={(e) => {
setFilter({
...filter,
search: e.target.value,
});
}}
>
{locations.map((item, index) => (
<text>
<option key={index} value={item.location}>
{item.location}
</option>
</text>
))}
</Button>
<Button
colorScheme="teal"
size="xs"
onClick={(e) => {
setFilter({
...filter,
search: e.target.value,
});
}}
>
ALL
</Button>
</div>
</div>
);
}
export default Filters;

Related

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>
);
}

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>
);
}

React trigger only one element in array

I am in the process of making a comment system like the one on youtube. In my implementation, when I click on modify, all comments are now inputs but only the value of the selected input will be modified. how to trigger only the element i clicked.
as you can see it triggers all the array elements
function App() {
const [open, setOpen] = useState(false);
return (
<div className="container mt-5">
<MDBRow>
{data &&
data.map((item) => (
<MDBCol md="7" lg="7" key={item.id} className="mb-4">
{!open && (
<>
<div className="font-weight-bolder float-left pr-2">
{item.name}
</div>
<div className="float-right pr-2">
<button
onClick={() => {
setOpen(true);
}}
>
Modifier
</button>
</div>
</>
)}
{open && (
<UpdateData
id={item.id}
name={item.name}
onAbort={() => setOpen(false)}
submit={() => setOpen(false)}
/>
)}
</MDBCol>
))}
</MDBRow>
</div>
);
}
export const UpdateData = ({ name, id, onAbort, submit }) => {
const formik = useFormik({
initialValues: {
id: id,
name: name,
},
onSubmit: async (values) => {
console.log(values);
submit();
},
});
return (
<form onSubmit={formik.handleSubmit}>
<MDBInput
value={formik.values.name}
name="name"
onChange={formik.handleChange}
/>
<div className="float-right">
<span onClick={onAbort} className="text-capitalize grey-text">
Cancel
</span>
<button type="submit">confirm</button>
</div>
</form>
);
};
And this is the sandbox
that i have created
To trigger only one element to be clicked you have to pass the index
function App() {
const [open, setOpen] = useState(false);
const [selectedRow, setSelectedRow] = useState(undefined);
const onSelectedRow = (index) => {
setSelectedRow(index);
setOpen(true);
}
return (
<div className="container mt-5">
<MDBRow>
{data &&
// here you will get the index
data.map((item,index) => (
<MDBCol md="7" lg="7" key={item.id} className="mb-4">
{!open && (
<>
<div className="font-weight-bolder float-left pr-2">
{item.name}
</div>
<div className="float-right pr-2">
// Now onClick pass the index of selected row to onSelectedRow
<button
onClick={() =>onSelectedRow(index)}
>
Modifier
</button>
</div>
</>
)}
// here add condition to open selected row
{ (open === true && selectedRow === index) ? (
<UpdateData
id={item.id}
name={item.name}
onAbort={() => setOpen(false)}
submit={() => setOpen(false)}
/>
) : null
}
</MDBCol>
))}
</MDBRow>
</div>
);
}
Sandbox code https://codesandbox.io/s/youthful-wave-k4eih?file=/src/App.js
If you have any queries comment below!
instead of having a false default value in your hook you should have a unique key for each element. By default, it applies to all elements.

Delete a <div> onClick in React

I would like to delete a search result by clicking on the X icon on the individual card.
The search returns 10 recipes from the API, generating 10 divs. How would I go about removing individual divs onClick of the icon whilst keeping the other divs? Essentially just a remove search result button.
return (
<div className='App'>
<form onSubmit={getSearch} className="search-form">
<InputGroup>
<InputGroupAddon addonType="prepend">
<InputGroupText><FontAwesomeIcon icon={faSearch} /></InputGroupText>
</InputGroupAddon>
<Input className="search-bar" type="text" placeholder="Search for recipe..." value={search} onChange={updateSearch} />
</InputGroup>
<Button color="primary" size="sm" className="search-button" type="submit">Search</Button>
</form>
<div className="recipes">
{recipes.map(recipe => (
<Recipe
key={recipe.recipe.label}
title={recipe.recipe.label}
theUrl={recipe.recipe.url}
image={recipe.recipe.image}
ingredients={recipe.recipe.ingredients}
source={recipe.recipe.source}
healthLabels={recipe.recipe.healthLabels}
servings={recipe.recipe.yield} />
))}
</div>
</div>
const Recipe = ({ title, theUrl, image, ingredients, source, healthLabels, servings }) => {
return (
<div className={style.recipe}>
<FontAwesomeIcon className={style.delete} icon={faTimes} />
<h3 >{title}</h3>
<Badge className={style.badge} color="primary">{source}</Badge>
<p>Serves: <Badge color="primary" pill>{servings}</Badge></p>
<img src={image} alt='food' />
<ol className={style.allergens}>
{healthLabels.map(healthLabel => (
<li>{healthLabel}</li>
))}
</ol>
<div className={style.ingr}>
<ol>
{ingredients.map(ingredient => (
<li>{ingredient.text}</li>
))}
</ol>
<Button className={style.button} outline color="primary" size="sm" href={theUrl} target="_blank">Method</Button>
</div>
<div className={style.info}>
<div className={style.share}>
<WhatsappShareButton url={theUrl}><WhatsappIcon round={true} size={20} /></WhatsappShareButton>
<FacebookShareButton url={theUrl}><FacebookIcon round={true} size={20} /></FacebookShareButton>
<EmailShareButton url={theUrl}><EmailIcon round={true} size={20} /></EmailShareButton>
</div>
</div>
</div>
);
}
Simply update the recipes array and React will update the HTML.
I'm not sure where recipes comes from, but if you set an onClick on, say, <Recipe label="x"> that deletes the corresponding recipe element from recipes, then React should no longer render that recipe.
That should be easy. Here's one way:
add onClick to this
<FontAwesomeIcon onClick={deleteRecipe} className={style.delete} icon={faTimes} />
pass a reference of the function that deletes the recipe.
deleteRecipeHandler = (id) => {
// filter your recipes here such that the new recipes array doesn't contain the recipe
// with the id you're getting here.
// Change the below code how you need
const newRecipes = oldRecipes.filter(recipe => {
return recipe.id !== id;
});
}
{recipes.map(recipe => (
<Recipe
key={recipe.recipe.label}
deleteRecipe={this.deleteRecipeHandler.bind(this,recipe.recipe.id)}
title={recipe.recipe.label}
theUrl={recipe.recipe.url}
image={recipe.recipe.image}
ingredients={recipe.recipe.ingredients}
source={recipe.recipe.source}
healthLabels={recipe.recipe.healthLabels}
servings={recipe.recipe.yield} />
))}
since you're destructuring your props you can use
const Recipe = ({ title, theUrl, image, ingredients, source, healthLabels, servings, deleteRecipe }) => {
return (
<div className={style.recipe}>
<FontAwesomeIcon onClick={deleteRecipe} className={style.delete} icon={faTimes} />

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

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.

Categories

Resources