ReactJS component only recognizes first parameter I pass - javascript

I'm encountering an issue where a modal component only receives the first parameter I send it but I need it to recognize both parameters or else the modal has no use. Basically, right now I can choose whether I put my TaskProps or a closeModal function as the first parameter. If the TaskProps are the first parameter, the closeModal function gets a TypeError and does not get recognized as a function. If I put closeModal as the first parameter, I'm able to close the modal but I can't work with the TaskProps to show details about certain tasks.
Here's my (relevant) code:
index.js ->
const TaskList = (state: TaskSectionState, task): React$Element<React$FragmentType> => {
const [tasks, setTasks] = useState([])
const [isSortDropdownOpen, setIsSortDropdownOpen] = useState(false);
const [selectedTask, setSelectedTask] = useState(tasks[1]);
const [openModal, setOpenModal] = useState(false)
const selectTask = (task) => {
setSelectedTask(task);
setOpenModal(!openModal)
};
{openModal && <DetailModal {...selectedTask } closeModal={setOpenModal} />}
DetailModal.js ->
type TaskProps = {
id: number,
title: string,
assigned_to: string,
assignee_avatar: string,
due_date: string,
completed: boolean,
priority: string,
stage: string,
checklists: Array<ChecklistsItems>,
description: string,
attachments: Array<AttachmentsItems>,
comments: Array<CommentsItems>,
};
const DetailModal = (task: TaskProps, {closeModal}): React$Element<React$FragmentType> => {
const [completed, setCompleted] = useState(task.completed == true);
return (
<React.Fragment >
<Modal show={true}>
<Card>
<Card.Body>
<h3>Task Details</h3>
<hr className="mt-4 mb-2" />
<Row>
<Col>
<p className="mt-2 mb-1 text-muted">Title</p>
<h4>{task.title}</h4>
<Row>
<Col>
<p className="mt-2 mb-1 text-muted">Deadline</p>
<div className="d-flex">
<i className="uil uil-schedule font-18 text-primary me-1"></i>
<div>
<h5 className="mt-1 font-14">{task.due_date}</h5>
</div>
</div>
</Col>
</Row>
<div className="row mt-3">
<div className="col">
<p className="mt-2 mb-1 text-muted">Description</p>
<div id="taskDesk" dangerouslySetInnerHTML={createMarkup(task.description)}></div>
</div>
</div>
<div className="row mt-3">
<div className="col">
<h5 className="mb-2 font-16">Notes</h5>
<p>Add notes/remarks for your future-self down below.</p>
</div>
</div>
{/* add comments */}
<Row className="mt-2">
<Col>
<div className="border rounded">
<form action="#" className="comment-area-box">
<textarea
rows="3"
className="form-control border-0 resize-none"
placeholder="Your comment..."></textarea>
<div style={{height: "60px"}} className="p-2 bg-light">
<div className="float-end">
<button type="submit" className="btn btn-primary">
<i className="uil uil-message me-1"></i>Add Note
</button>
</div>
<div>
<Link to="#" className="btn btn-sm px-1 btn-light">
</Link>
<Link to="#" className="btn btn-sm px-1 btn-light">
</Link>
</div>
</div>
</form>
</div>
</Col>
<div className="text-end" style={buttonStyle} >
<Button variant="light"type="button" className="me-1"
onClick={() => closeModal(false)}>
Close
</Button>
<Button variant="secondary">
Update Details
</Button>
</div>
</Row>
</Col>
</Row>
</Card.Body>
</Card>
</Modal>
</React.Fragment>
);
};
export default DetailModal;
To make things maybe a bit more clear, if I pass my parameters in the manner which is shown above in DetailModal.js (TaskProps first and closeModal second) the details get rendered in the modal but the following TypeError shows up when clicking the 'Close' button:
And when I pass the parameters the other way around like this I'm able to close the modal perfectly fine but the TaskProps don't get passed and it just leaves blank fields. (See screenshot)
const Task = ({closeModal}, task: TaskProps): React$Element<React$FragmentType> => {
const [completed, setCompleted] = useState(task.completed == true);
I would love to finally get past this headache because I can't seem to figure it out myself. I appreciate any input tremendously!

For react components first argument is an object representing all the props passed to it. So in your case for it to work you have to do it this way:
const DetailModal = ({closeModal, ...task}) => {
This way, you are destructuring closeModal from this object and keeping all the other props grouped together as a task.

Related

How to display different button on different component while mapping through a common component in React?

I have created a component which basically generates a card which includes card title,card description and a button. Now when I map through an array on a different component to generate those cards I want the button to be different on different components. Like on home page the button should say Update, on other page the button should say Delete. How can I acheive that? Here is the component which generates card.
import React from 'react';
import { Card } from 'react-bootstrap';
const InventoryItem = ({ product }) => {
const { productName, productImage, productPrice, productQuantity, productSupplier, productDetails } = product;
return (
<div className='col-12 col-md-6 col-lg-6'>
<Card className='h-100 items-card d-block d-md-block d-lg-flex flex-row align-items-center border border-0'>
<div className='text-center card-image-container'>
<Card.Img variant="top" src={productImage} className='card-image img-fluid' />
</div>
<Card.Body className='card-details'>
<Card.Title>{productName}</Card.Title>
<Card.Text>{productDetails}</Card.Text>
<div className='d-lg-flex align-items-center justify-content-between mb-3'>
<p className='mb-0'>Price: ${productPrice}</p>
<p className='me-2 mb-0'>Stock: {productQuantity}</p>
</div>
<div className='d-flex align-items-center justify-content-between'>
<p className='mb-0'>Supplier:{productSupplier}</p>
<button className='btn btn-dark'>Update</button>
</div>
</Card.Body>
</Card>
</div>
);
};
export default InventoryItem;
Pass the button text as a prop to the component, so consuming code can speficy the text for the button.
Add it as a prop:
const InventoryItem = ({ product, buttonText }) => {
And use it in the button:
<button className='btn btn-dark'>{buttonText}</button>
Then when using the component, pass the prop:
<InventoryItem product={someProductObject} buttonText="Update" />
or with a conditional value:
<InventoryItem product={someProductObject} buttonText={someCondition ? "Update" : "Delete"} />
You can define a condition for the button basaed on the page you are:
window.location.href return the string href.
const MyButton= window.location.href=='Home'
? <button> HOME</button>
: <button> UPDATE</button>
Hope that answer your question,
Mauro

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

Add popup in every item of flatlist in React

I would like to add a popup form in each item in the Flatlist, and the item is actually a card.
My problem is that every time, I clicked the button, the popup shows but, when my mouse moves out of the card, instead of showing the original one, it shows another popup whose parent is the whole page.
I know the problem should be that I need not set the variable setPopup properly. But I don't know how to fix it.
When my mouse is on the card:
enter image description here
When my mouse is out of the card, the popup will move up and its position will be based on the whole page:
enter image description here
Thank you!
This is my code.
const [buttonPopUp, setButtonPopUp] = useState(undefined);
const renderItem = (item, index) => {
return(
<Card key={index} style={{width: '20rem'}}>
<CardImg className='galleryPics' top src={galleryPic.img} alt="..."/>
<CardBody>
<PopUpEditGallery
gallery={item}
index = {index}
trigger={buttonPopUp}
setTrigger={setButtonPopUp}
>
Edit
</PopUpEditGallery>
<CardTitle className='cardTitle'>{item.title}</CardTitle>
<CardText className='cardText'>{item.description}</CardText>
<Button className="btn-round btn-icon" color="primary" size='sm' onClick={()=> setButtonPopUp(index)}>Edit</Button>
</CardBody>
</Card>
);
}
return (
<div>
<div>
<Header/>
</div>
<div className="container">
<div className="row">
<div className='col-7'>
<ul>
<FlatList
list={values.gallery}
renderItem={renderItem}/>
</ul>
</div>
</div>
</div>
</div>
)
code for popup
return (props.trigger != undefined) ? (
props.trigger == props.index &&
<div className='popup'>
<div className='popupInner'>
<form onSubmit={handleSubmit(onSubmit)}>
<FormGroup>
<Label>Title</Label>
<Input
type="text"
placeholder={prev.title}
onChange={val => props.setTitle(val.target.value, prev.idx)}
/>
</FormGroup>
<Button className="btn-round btn-icon" color="primary" size='sm'>
Submit
</Button>
<Button className="btn-round btn-icon" color="default" size='sm'>
Cancel
</Button>
</form>
</div>
</div>
): "";

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

Component is rendered before clicking on button in Reactjs

I am new to Reactjs . I am making a todo app using it . So in developing phase I want the Delete Button to console.log() the "hello" onClick. But whenever I submit a new todo task , even without click the delete button it console.log() the "hello" and if then I click Delete Button nothing happens on console.
I can't understand where's the problem . I surfed the internet but nothing worked.
May anyone tell where's the problem ?
Here's my code :
import './styles/App.css';
import { useState } from 'react';
import { Form, Button, ListGroup } from 'react-bootstrap';
import DropDown from './components/DropDowns';
import DeleteButton from './components/DeleteButton';
function App() {
const [todoList, setTodo] = useState([]);
const [Ttitle, setTitle] = useState('');
const [desc, setDesc] = useState('No Description');
return (
<div className="App d-flex flex-column">
<h1 className="mt-3">Your ToDo App</h1>
<div className="container-fluid mx-0 px-0">
<div className="Todo__Res mx-5">
<h2>Your ToDo List</h2>
<div className="List">
<ul className="Todo__List">
{todoList ? todoList.map((item, index) => <li key={index}>
<ListGroup>
<ListGroup.Item>{item.title}<DropDown desc={item.desc} /><DeleteButton index={index}/></ListGroup.Item>
</ListGroup>
</li>) : 0}
</ul>
</div>
</div>
<div className="Todo__Add mx-5">
<h2>Add Your ToDo</h2>
<Form>
<Form.Group controlId="exampleForm.ControlInput1">
<Form.Label>Title</Form.Label>
<Form.Control type="text" placeholder="Take Jack to School..." onChange={e => setTitle(e.target.value)} />
</Form.Group>
<Form.Group controlId="exampleForm.ControlTextarea1">
<Form.Label>Description</Form.Label>
<Form.Control as="textarea" rows={3} placeholder="Buy him a cake..." onChange={e => setDesc(e.target.value)} onDefault={desc} />
</Form.Group>
<Button variant="primary" type="submit" onClick={(e) => {
e.preventDefault();
const kk = {
title: Ttitle,
desc: desc
}
setTodo([...todoList, kk]);
}}>
Submit
</Button>
</Form>
</div>
</div>
</div>
);
}
export default App;
//Delete Button Component :
import {Button}from 'react-bootstrap'
export default function DeleteButton({index}){
return(
<>
<Button variant="outline-danger" onClick={console.log("hello")}>Delete</Button>
</>
);
}
Thanking You ,
Yours Truly
Rishabh Raghwendra
You are calling the function immediately on render. you should just pass a function to be executed on click
Here you go, this should fix your code issue,
<Button variant="outline-danger" onClick={() => console.log("hello")}>Delete</Button>

Categories

Resources