Create search box in the list in react js component - reactjs

Please tell, how to carry out search in the list of components which code is specified below correctly?
The search should be performed by title or full description of the list item.
Component with list of Item components:
const PathItems = () => {
const dispatch = useDispatch();
const pathDescription = useSelector(state => state.firestore.ordered.pathDescription);
const handleClick = (path) => {
dispatch(selectPath(path));
}
if(pathDescription && pathDescription.length !== 0){
return (
<React.Fragment>
<Row>
<Col className="pl-0 border-right border-dark">
{pathDescription && pathDescription.map(item => (
<PathItem
key={item.id}
item={item}
onInfoChange={ handleClick }
/>
))}
</Col>
<Col>
<FullDecript/>
</Col>
</Row>
</React.Fragment>
)
} else {
return (
<h5 className="text-muted text-center text-middle">Add your first route</h5>
)
}
}
export default compose(firestoreConnect(()=> ['pathDescription']))(PathItems);
Item component code:
const PathItem = ({ item, onInfoChange }) => {
const handleClick = () => {
onInfoChange(item);
}
return (
<React.Fragment>
<Card as="a"
style={{cursor: 'pointer'}}
className={'mb-2'}
onClick={ handleClick }>
<Card.Body>
<Row className="align-items-center">
<Col xs={1}>
</Col>
<Col xs={7}>
<h5>{item.title}</h5>
{item.sDescript}
</Col>
<Col xs={4} className="text-right">
<label>{item.length}600 km</label>
</Col>
</Row>
</Card.Body>
</Card>
</React.Fragment>
);
}
export default PathItem;
General view of the described components
Thanks in advance)

...
const [searchQuery, setQuery] = useState("");
const inputEvent = (event) => {
const data = event.target.value;
console.log(pathDescription);
setQuery(data);
}
const filterItems = pathDescription && pathDescription.filter(item => {
return item.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.fDescript.toLowerCase().includes(searchQuery.toLowerCase());
})
...
<Col className="pl-0 border-right border-dark" style={divStyle}>
<InputGroup className="mb-3">
<FormControl
type="text"
placeholder="Type..."
aria-describedby="basic-addon2"
value={ searchQuery }
onChange={ inputEvent }
/>
<InputGroup.Append>
<Button variant="outline-secondary">
<img
alt="Logo"
src="https://cdn1.iconfinder.com/data/icons/app-user-interface-line/64/search_focus_user_interface_app_zoom-256.png"
width="25"
height="25"
className="d-inline-block align-top"/>
</Button>
</InputGroup.Append>
</InputGroup>
{filterItems.sort((a, b) => b.favorite - a.favorite).map(item => (
<PathItem
key={item.id}
item={item}
onInfoChange={ handleClick }
/>
))}
</Col>

Related

Can't add product into Cart

Unable to add product into the cart although, the url is getting changed, it showing two errors in the console, cant understand what is wrong.
http://localhost:3000/cart/636d4264ff92e60977f3bb4a?qt=3
636d4264ff92e60977f3bb4a = productID and qt=3 quantity
enter image description here
here are my files
import React, { useEffect } from 'react'
import { Link, useParams, useLocation, useNavigate } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { Row, Col, ListGroup, Image, Form, Button, Card } from 'react-bootstrap'
import Message from '../components/Message'
import { addToCart, removeFromCart } from '../actions/cartActions'
const CartScreen = ({match, history}) => {
const {id} = useParams();
//const productId = match.params.id
const location = useLocation();
const qty = location.search ? Number (location.search.split('=') [1]) : 1
const dispatch = useDispatch()
const navigate = useNavigate()
const cart = useSelector((state) => state.cart)
const { cartItems } = cart
useEffect(() => {
if(id){
//useEffect working fine
dispatch(addToCart(id,qty))
}else{
//console.log('useEffect error')
}
}, [dispatch, id, qty])
const removeFromCartHandler = (id) => {
dispatch(removeFromCart(id))
}
const chechoutHandler = () => {
navigate('/login?redirect=/shipping')
}
return (
<Row>
<Col md={8}>
<h1>Shopping Cart</h1>
{ cartItems && cartItems.length === 0 ? (
<Message> Your cart is empty <Link to='/'>Go Back</Link></Message>
) : (
<ListGroup variant='flush'>
{cartItems.map(item => (
<ListGroup.Item key={item.product}>
<Row>
<Col md={2}>
<Image src={item.image} alt={item.name} className="cartItemPhoto" flude rounded />
</Col>
<Col md={3}>
<Link to={`/product/${item.product}`}>{item.name}</Link>
</Col>
<Col md={2}>₹{item.price}</Col>
<Col md={2}>
<Form.Control
as='select'
value={item.qty}
onChange={(e)=> dispatch(addToCart(item.product,Number(e.target.value)))}
>
{[...Array(item.countInStock).keys()].map((x) => (
<option key={x+1} value={x + 1}>
{ x + 1 }
</option>
))}
</Form.Control>
</Col>
<Col md={2}>
<Button type='button' varient='ligth' onClick={() => removeFromCartHandler(item.product)} >
<i className='fas fa-trash'></i>
</Button>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</Col>
<Col md={4}>
<Card>
<ListGroup varient = 'flush'>
<ListGroup.Item>
<h2>Subtotal ({cartItems.reduce((acc,item)=>acc+item.qty,0)}) items</h2>
₹{cartItems.reduce((acc,item)=>acc+item.qty * item.price,0).toFixed(2)}
</ListGroup.Item>
<ListGroup>
<Button type='button' className='btn-block' disabled={cartItems.length === 0} onClick={chechoutHandler}>
Proceed To Checkout
</Button>
</ListGroup>
</ListGroup>
</Card>
</Col>
</Row>
)
}
export default CartScreen
import React, { useState, useEffect } from 'react'
import { Link, useParams} from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import {Row, Col, Image, ListGroup, Card, Button, Form } from 'react-bootstrap'
import Rating from '../components/Rating'
import Loader from '../components/Loader'
import Message from '../components/Message'
import { listProductDetails, createProductReview } from '../actions/productActions'
import { useNavigate } from 'react-router-dom'
//import products from '../../../backend/data/products.js'
//import axios from 'axios'
import { PRODUCT_CREATE_REVIEW_RESET } from '../constants/productConstants'
const ProductScreen = ({ match}) => {
const {id} = useParams();
//const product = products.find((p) => p._id === id);
const [qty, setQty] = useState(1)
const [rating, setRating] = useState(0)
const [comment, setComment] = useState('')
const dispatch = useDispatch()
const productDetails = useSelector(state => state.productDetails)
const { loading, error, product } = productDetails
//console.log(product)
// reviews aasche sudhu
const userLogin = useSelector((state) => state.userLogin)
const { userInfo } = userLogin
const productReviewCreate = useSelector((state) => state.productReviewCreate)
const {
success: successProductReview,
loading: loadingProductReview,
error: errorProductReview,
} = productReviewCreate
useEffect(()=>{
if(successProductReview){
alert('Review Submitted!')
setRating(0)
setComment('')
dispatch({type: PRODUCT_CREATE_REVIEW_RESET})
}
dispatch(listProductDetails(id))
}, [dispatch, id, successProductReview])
//if(!product) return null;
// return ( <div>{product.name}</div> );
const history = useNavigate()
const addToCartHandler = () =>{
history(`/cart/${id}?qt=${qty}`)
}
const submitHandler = (e) => {
e.preventDefault()
dispatch(
createProductReview(id, {
rating,
comment,
})
)
}
//console.log(product)
return (
<>
<Link className='btn btn-outline-primary' to='/'>
Go Back
</Link>
{ loading ? (
<Loader/>
): error ? (
<Message variant='danger'> {error} </Message>
) : (
<>
<Row>
<Col md={6}>
{/* src={product.image} changed to -> {`${window.location.origin}/${product.image.name}`} */}
<Image src= {product.image} alt={product.name} fluid/>
</Col>
<Col md={3}>
<ListGroup variant='flust'>
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
{/*
<Rating
value={product.rating.toString()}
text={product.numReviews}
/>
*/}
</ListGroup.Item>
<ListGroup.Item>
Price: ₹{product.price}
</ListGroup.Item>
<ListGroup.Item>
Description: {product.description}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup variant='flush'>
<ListGroup.Item>
<Row>
<Col>
Price:
</Col>
<Col>
<strong>₹{product.price}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Status:
</Col>
<Col>
{product.countInStock>0 ? 'In Stock' : 'Out Of Stock'}
</Col>
</Row>
</ListGroup.Item>
{ product.countInStock > 0 && (
<ListGroup.Item>
<Row>
<Col>Qty</Col>
<Col>
<Form.Control
as='select'
value={qty}
onChange={(e)=>setQty(e.target.value)}
>
{[...Array(product.countInStock).keys()].map((x) => (
<option key={x+1} value={x + 1}>
{ x + 1 }
</option>
))}
</Form.Control>
</Col>
</Row>
</ListGroup.Item>
)}
<ListGroup.Item>
<Button
onClick={addToCartHandler}
className='btn btn-success btn-block btn-wd'
type='button'
disabled={product.countInStock===0}
>
Add To Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
<Col md={6}>
<h2>Reviews</h2>
{product.reviews.length === 0 && <Message>No Reviews</Message>}
<ListGroup variant='flush'>
{product.reviews.map((review) => (
<ListGroup.Item key={review._id}>
<strong>{review.name}</strong>
<Rating value={review.rating} />
<p>{review.createdAt.substring(0, 10)}</p>
<p>{review.comment}</p>
</ListGroup.Item>
))}
<ListGroup.Item>
<h2>Write a Customer Review</h2>
{successProductReview && (
<Message variant='success'>
Review submitted successfully
</Message>
)}
{loadingProductReview && <Loader />}
{errorProductReview && (
<Message variant='danger'>{errorProductReview}</Message>
)}
{userInfo ? (
<Form onSubmit={submitHandler}>
<Form.Group controlId='rating'>
<Form.Label>Rating</Form.Label>
<Form.Control
as='select'
value={rating}
onChange={(e) => setRating(e.target.value)}
>
<option value=''>Select...</option>
<option value='1'>1 - Poor</option>
<option value='2'>2 - Fair</option>
<option value='3'>3 - Good</option>
<option value='4'>4 - Very Good</option>
<option value='5'>5 - Excellent</option>
</Form.Control>
</Form.Group>
<Form.Group controlId='comment'>
<Form.Label>Comment</Form.Label>
<Form.Control
as='textarea'
row='3'
value={comment}
onChange={(e) => setComment(e.target.value)}
></Form.Control>
</Form.Group>
<Button
disabled={loadingProductReview}
type='submit'
variant='primary'
>
Submit
</Button>
</Form>
) : (
<Message>
Please <Link to='/login'>sign in</Link> to write a review{' '}
</Message>
)}
</ListGroup.Item>
</ListGroup>
</Col>
</>
)}
</>
)
}
// Form.Control e add chilo, onChange{(e) => setQty(e.target.value)}
/*
<Form.Control
as='select'
value={qty}
onChange{(e) => setQty(e.target.value)}
>
// lec 32
*/
export default ProductScreen
import axios from 'axios'
import {
CART_ADD_ITEM,
CART_REMOVE_ITEM,
CART_SAVE_SHIPING_ADDRESS,
CART_SAVE_PAYMENT_METHOD
} from '../constants/cartConstants'
import { useParams } from 'react-router-dom'
export const addToCart = (id, qty) => async (dispatch, getState) => {
const {id} = useParams();
//console.log(`id working = ${id}`)
const { data } = await axios.get(`/api/products/${id}`)
//console.log(`data = ${data}`)
dispatch({
type: CART_ADD_ITEM,
payload: {
product: data._id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
qty,
},
})
localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems))
}
export const removeFromCart = (id) => (dispatch, getState) => {
dispatch({
type: CART_REMOVE_ITEM,
payload: id
})
localStorage.setItem('cartItems',JSON.stringify(getState().cart.cartItems))
}
export const saveShippingAdderss = (data) => (dispatch) => {
dispatch({
type: CART_SAVE_SHIPING_ADDRESS,
payload: data,
})
localStorage.setItem('shippingAddress',JSON.stringify(data))
}
export const savePaymentMethod = (data) => (dispatch) => {
dispatch({
type: CART_SAVE_PAYMENT_METHOD,
payload: data,
})
localStorage.setItem('paymentMethod',JSON.stringify(data))
}
import { CART_ADD_ITEM, CART_REMOVE_ITEM, CART_SAVE_PAYMENT_METHOD, CART_SAVE_SHIPING_ADDRESS } from '../constants/cartConstants.js'
import { PRODUCT_DETAILS_REQUEST, PRODUCT_LIST_REQUEST } from '../constants/productConstants.js'
export const cartReducer = (
state = { cartItems: [], shippingAddress: {} }, action) => {
//console.log(`action.type line on 6 = ${action.type}`)
switch(action.type){
case CART_ADD_ITEM:
//console.log(`CART_ADD_ITEM 3333 = ${action.type}`)
const item = action.payload
const existItem = state.cartItems.find( (x) => x.product === item.product)
if(existItem){
return {
...state,
cartItems: state.cartItems.map ((x) =>
x.product === existItem.product ? item : x
),
}
} else {
return {
...state,
cartItems: [...state.cartItems, item],
}
}
case CART_REMOVE_ITEM:
return {
...state,
cartItems: state.cartItems.filter((x) => x.product !==action.payload),
}
case CART_SAVE_SHIPING_ADDRESS:
return {
...state,
shippingAddress: action.payload,
}
case CART_SAVE_PAYMENT_METHOD:
return {
...state,
paymentMethod: action.payload,
}
default:
return state
}
}
I want to resolve the error so that I can add products to my cart. Thank you

hide buttons from interface

I have a modal, and this modal has two interfaces, the first is “QRReader” and the second is “PatientForm”, and the modal has two buttons, the first is “Approve” and the second is “Cancel”.
And I want to hide the two buttons within the interface of the "QRReader"
How can i solve the problem?
And this file contains the entire modal, knowing that the BasicModal tag is modal
import { Button, Col, Row } from "antd";
import {
useState
} from "react";
import { QrReader } from "react-qr-reader";
import patient from "../../../api/nuclearMedicineApi/services/Patient";
import { decrypt } from "../../../utils/decryption";
import PatientForm from "./form";
import { QrcodeOutlined } from '#ant-design/icons';
import BasicModal from "../modal";
import { FormattedMessage } from "react-intl";
import { notify } from "../notification";
const QRScanner = () => {
const [data, setData] = useState<number>(0);
const [patientInfoData, setPatientInfoData] = useState({})
const [modelVisible, setModelVisible] = useState<any>();
console.log('datadatadata: ', data)
const openNotificationWithIcon = () => {
// onSuccess: (data) => {
notify('success', 'ok', 'approve-message');
// },
};
return (
<>
<QrcodeOutlined
className='cursor-pointer'
style={{ fontSize: '2rem' }}
color={'#fff'}
onClick={(e) => {
e.stopPropagation()
setModelVisible(true)
}}
/>
<BasicModal
header={<>
<h2 className='text-primary'><FormattedMessage id="qr-scanner" /></h2>
</>}
content={
<>
{
data !=0 ?
<PatientForm patientInfoData={patientInfoData} data={data} />
:
<Row>
<Col span={18} offset={3}>
<QrReader
onResult={async (result: any, error) => {
if (!!result) {
const t = result?.text;
const d = decrypt(t);
let zz: any = d.match(/(\d+)/)
Math.floor(zz[0])
setData(zz[0]);
const pationInfo = await patient.patientGet({ Id: Number(zz[0]) })
setPatientInfoData(pationInfo)
}
if (!!error) {
console.info(error);
}
}} // style={{ width: '100%' }}
constraints={{ facingMode: 'user' }}
// style={{ width: '100%' }}
/>
</Col>
</Row>
}
<Row>
<Col span={1} offset={3}>
<Button
type='primary'
className='savebtn'
onClick={() => {
patient.switchToPresent(data || 0)
openNotificationWithIcon()
}}
>
<FormattedMessage id={'approve'} />
</Button>
</Col>
<Col span={8} offset={4}>
<Button
type='default'
className='savebtn'
onClick={() => {
setModelVisible(false);
setData(0);
}}
>
<FormattedMessage id={'cancel'} />
</Button>
</Col>
</Row>
</>
}
isOpen={modelVisible}
footer={false}
width='50vw'
handleCancel={() => {
setModelVisible(false);
}}
afterClose={
() => setData(0)
}
/>
</>
)
};
export default QRScanner;
I think you should be able to use a similar condition as you are using to determine if you should render patientForm vs QRReader. You could wrap your buttons with something like this
{ data = 0 && (
<Row>
<Col span={1} offset={3}>
<Button
type='primary'
className='savebtn'
onClick={() => {
patient.switchToPresent(data || 0)
openNotificationWithIcon()
}}
>
<FormattedMessage id={'approve'} />
</Button>
</Col>
<Col span={8} offset={4}>
<Button
type='default'
className='savebtn'
onClick={() => {
setModelVisible(false);
setData(0);
}}
>
<FormattedMessage id={'cancel'} />
</Button>
</Col>
</Row>
)
}
You can have the same condition for showing the buttons which you have for QRScanner and PatientForm
{data != 0 ? (
<Row>
<Col span={1} offset={3}>
<Button
type='primary'
className='savebtn'
onClick={() => {
patient.switchToPresent(data || 0)
openNotificationWithIcon()
}}
>
<FormattedMessage id={'approve'} />
</Button>
</Col>
<Col span={8} offset={4}>
<Button
type='default'
className='savebtn'
onClick={() => {
setModelVisible(false);
setData(0);
}}
>
<FormattedMessage id={'cancel'} />
</Button>
</Col>
</Row>
) : </>}

How do you pass data from one child component to another sibling?

My goal is to display different values in the < h1 > of the GoalsDone component depending on which TeamCard is being hovered over. Both of these components are rendered inside of the TopGroups component, in these code snippets I am attempting to pass through the parent TopGroups.
Child component displaying the number of goals done:
const GoalsDone = ({displayGoals}) => {
return (
<GameifyStyle className="">
<Col className="GPM">
<img className="pt-3 mx-auto pl-0" src="../images/bullseye.png" />
<h1 className="pt-1"> {displayGoals}75 Goals</h1>
<p>DONE</p>
</Col>
</GameifyStyle>
)
}
Child Component that updates the score after being hovered over:
It currently has an unhandled runtime error "setDisplayGoals is not a function"
const TeamCard = ({data}, {setDisplayGoals}) => {
return (
<TeamCardStyle>
{!data && (
<Spinner />
)}
{data && data.getGroupScores && (
data.getGroupScores.slice(0, 4).map((group, index) => {
return (
<Row onMouseEnter={() => {setDisplayGoals(group.totalScore)}}>
<Col className="teamCard mt-2 mb-2">
<Row>
<p>{seed[index]}</p>
</Row>
<Row>
<Col className="hideSmall">
<img className="mouseOn" src="../images/group.png" />
<img className="mouseOff" src="../images/groupSelected.png" />
</Col>
</Row>
<p>{group.name}</p>
</Col>
</Row>
)
})
)}
</TeamCardStyle>
)
}
Parent component:
ATTN lines 38, 48
const GET_GROUP_SCORES = gql`
query GET_GROUP_SCORES($quarter: String, $groupId: String) {
getGroupScores(quarter: $quarter, groupId: $groupId) {
name
id
totalScore
goalsDone
milestonesDone
}
}
`;
const TopGroups = () => {
const {loading, error, data } = useQuery(GET_GROUP_SCORES, {variables: { quarter: "Q2 2021" }})
if (data) {
const sortedGroups = data.getGroupScores.sort((a, b) => {
if (a.totalScore > b.totalScore) {
return -1
}
if (a.totalScore < b.totalScore) {
return 1
} else {
return 0
}
})
}
if (error) {
return <p>An error has occured</p>
}
if (loading) {
<Spinner />
}
const [displayGoals, setDisplayGoals] = useState('0');
return (
<Col className="col-12">
<TeamCardStyle>
<Row>
<TeamCard
data={data}
setDisplayGoals={setDisplayGoals}
/>
</Row>
</TeamCardStyle>
<GameifyStyle>
<Row className="cardContainer mt-3 XsWidthAdjust">
<Col className="SideBorder TopGroupsFonts mx-1">
<GoalsDone
displayGoals={displayGoals} />
</Col>
<Col className="SideBorder TopGroupsFonts mx-1">
<PrizesWon />
</Col>
<Col className="SideBorderPH TopGroupsFonts mx-1">
<MilestonesOnTrack />
</Col>
</Row>
</GameifyStyle>
</Col>
)
}
The error "Child Component that updates the score after being hovered over: It currently has an unhandled runtime error "setDisplayGoals is not a function"" happens because you are destructuring the props wrong in your TeamCard component. Instead of doing
const TeamCard = ({data}, {setDisplayGoals}) => {
You should do:
const TeamCard = ({data, setDisplayGoals}) => {

Removing complex components from an array in ReactJS

I'm trying to make a list of components. I need to remove the items individually but it seems that the state inside the remove function is always outdated.
For example, if I add 10 authors and click in the 10th author remove button, it'll show me 9 elements (which is already wrong) and if I click on the 2nd author, it shows me just 1 element inside the array. Am I missing something here?
const [authorsFields, setAuthorsFields] = useState<Array<JSX.Element>>([]);
const removeAuthorField = () => {
console.log(authorsFields.length);
}
const removeButton = () => {
return (
<Col className={"d-flex justify-content-end py-1"}>
<Button variant={"danger"} onClick={() => removeAuthorField()}>Remove author</Button>
</Col>
);
}
const authorField = (removable: boolean) => {
return (
<>
<Row className={"mb-2"}>
<Form.Group className={"py-1"}>
<Form.Label>Author name</Form.Label>
<Form.Control type={"text"}/>
</Form.Group>
{removable && removeButton()}
</Row>
</>
);
}
const addAuthorField = () => {
if (authorsFields.length !== 0) {
setAuthorsFields((old) => [...old, authorField(true)]);
} else {
setAuthorsFields([authorField(false)]);
}
}
useEffect(() => {
if (authorsFields.length === 0) {
addAuthorField();
}
}, [])
return (
<>
<Col sm={3} style={{maxHeight: "60vh"}} className={"mt-4"}>
<Row>
{authorsFields}
<Row>
<Form.Group className={"py-1"}>
<Button style={{width: "100%"}} onClick={() => addAuthorField()}>
Add Author
</Button>
</Form.Group>
</Row>
</Row>
</Col>
</>
);
Use the following functional component as an example to modify your code on how to use JSX elements seperated from the state management inside the functional components.
import React, { useState } from "react";
import { Button, Row, Col } from "antd";
function App() {
const [authorsCount, setAuthorsCount] = useState(0);
// Use authorsFields to manage authors details in an array of objects
const [authorsFields, setAuthorsFields] = useState([]);
const removeAuthorField = (id) => {
// To remove relevant author filter out the authors without the relevant id
setAuthorsFields((old) =>
old.filter((authorField) => authorField.id !== id)
);
};
const addAuthorField = () => {
setAuthorsFields((old) => [...old, { id: authorsCount, removable: true }]);
setAuthorsCount((old) => old + 1);
};
return (
<div>
<Col sm={3} style={{ maxHeight: "60vh" }} className={"mt-4"}>
<Row>
{authorsFields.map((authorField) => (
<Row className={"mb-2"}>
<div className={"py-1"}>
<div>{`Author name ${authorField.id}`}</div>
</div>
{authorField.removable && (
<>
<Col className={"d-flex justify-content-end py-1"}>
<Button
variant={"danger"}
onClick={() => removeAuthorField(authorField.id)}
>
Remove author
</Button>
</Col>
</>
)}
</Row>
))}
<Row>
<div className={"py-1"}>
<Button
style={{ width: "100%" }}
onClick={() => addAuthorField()}
>
Add Author
</Button>
</div>
</Row>
</Row>
</Col>
</div>
);
}
export default App;
Following is the view.

Utilizing the "Ok" button in ANTd Modal in React

I am creating a movie website that displays Modals of movies when you click on the movie card. I'm able to handle the "onCancel" which turns setActivateModal to false and closes the Modal, but I also want to allow the "Schedule" button to do something. The intended behavior is to have the "Schedule" button generate a different form in which I can fill out to "schedule" a movie with basic form entries that are then sent to my database. I'm not struggling with the form, but I'm struggling with how to handle generating one with the "Schedule" button. I'm unsure if you are allowed to do "nested" Modals, but any way it can be handled is fine.
import React, { useEffect, useState } from 'react';
import {
Layout,
Input,
Row,
Col,
Card,
Tag,
Spin,
Modal,
Typography,
Button,
} from 'antd';
import 'antd/dist/antd.css';
const { Content } = Layout;
const { Search } = Input;
const { Meta } = Card;
const TextTitle = Typography.Title;
const SearchBox = ({ searchHandler }) => {
return (
<Row>
<Col span={12} offset={6}>
<Search
placeholder="Search for movies to schedule!"
enterButton="Search"
size="large"
onSearch={value => searchHandler(value)}
/>
</Col>
</Row>
);
};
const MovieCard = ({
Title,
imdbID,
Poster,
ShowDetails,
DetailRequest,
ActivateModal,
}) => {
const clickHandler = () => {
ActivateModal(true);
DetailRequest(true);
fetch(`http://www.omdbapi.com/?i=${imdbID}&apikey=xxxxxxxx`)
.then(resp => resp)
.then(resp => resp.json())
.then(response => {
DetailRequest(false);
ShowDetails(response);
});
};
return (
<Col style={{ margin: '50px' }} span={3}>
<div>
<Card
style={{ width: 300 }}
cover={
<img
alt={Title}
src={
Poster === 'N/A'
? 'https://placehold.it/198x264&text=Image+Not+Found'
: Poster
}
/>
}
onClick={() => clickHandler()}
>
<Meta title={Title} />
</Card>
</div>
</Col>
);
};
const MovieDetail = ({
Title,
Actors,
Released,
Rated,
Runtime,
Genre,
Poster,
Plot,
}) => {
return (
<Row>
<Col span={11}>
<img
src={
Poster === 'N/A'
? 'https://placehold.it/198x264&text=Image+Not+Found'
: Poster
}
alt={Title}
/>
</Col>
<Col span={13}>
<Row>
<Col>
<TextTitle>{Title}</TextTitle>
</Col>
</Row>
<Row style={{ marginBottom: '.7em' }}>
<Col>{Actors}</Col>
</Row>
<Row style={{ marginBottom: '.7em' }}>
<Col>
<Tag>{Released}</Tag>
<Tag>{Rated}</Tag>
<Tag>{Runtime}</Tag>
<Tag>{Genre}</Tag>
</Col>
</Row>
<Row>
<Col>{Plot}</Col>
</Row>
</Col>
</Row>
);
};
const Loader = () => (
<div>
<Spin />
</div>
);
function Movies() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [query, setQuery] = useState('');
const [activateModal, setActivateModal] = useState(false);
const [details, setShowDetails] = useState(false);
const [detailRequest, setDetailRequest] = useState(false);
useEffect(() => {
setError(null);
setData(null);
fetch(`http://www.omdbapi.com/?s=${query}&apikey=xxxxxxxx`)
.then(resp => resp)
.then(resp => resp.json())
.then(response => {
if (response.Response === 'False') {
setError(response.Error);
} else {
setData(response.Search);
}
})
.catch(({ message }) => {
setError(message);
});
}, [query]);
return (
<div className="Movies">
<Layout className="layout">
<Content>
<div style={{ background: '#4a576e', padding: 60, minHeight: 300 }}>
<SearchBox searchHandler={setQuery} />
<br />
<Row justify="center">
{data !== null &&
data.length > 0 &&
data.map((result, index) => (
<MovieCard
ShowDetails={setShowDetails}
DetailRequest={setDetailRequest}
ActivateModal={setActivateModal}
key={index}
{...result}
/>
))}
</Row>
</div>
<Modal
title="Details"
centered
visible={activateModal}
onCancel={() => setActivateModal(false)}
/* onOk= {() => What do I put here? */
width={800}
footer={[
<Button key="cancel" onClick={() => setActivateModal(false)}>
Cancel
</Button>,
<Button
key="schedule" /* onClick={() => setActivateForm(true)} */
>
Schedule
</Button>,
]}
>
{detailRequest === false ? (
<MovieDetail {...details} />
) : (
<Loader />
)}
</Modal>
</Content>
</Layout>
</div>
);
}
export default Movies;
Assuming all the routes are set up properly in your App.js, add the following changes:
Add this to your import list:
import { Link } from "react-router-dom";
In the Movies function, add the const [activeForm, setActiveForm] = useState(false); as shown below
function Movies() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [query, setQuery] = useState('');
const [activateModal, setActivateModal] = useState(false);
const [details, setShowDetails] = useState(false);
const [detailRequest, setDetailRequest] = useState(false);
const [activateForm, setActivateForm] = useState(false);
In the return for your function, in the div tag right under the "Content" tag, add ActivateForm={setActivateForm} as shown below.
<div style={{ background: '#4a576e', padding: 60, minHeight: 300 }}>
<SearchBox searchHandler={setQuery} />
<br />
<Row justify="center">
{ data !== null && data.length > 0 && data.map((result, index) => (
<MovieCard
ShowDetails={setShowDetails}
DetailRequest={setDetailRequest}
ActivateModal={setActivateModal}
ActivateForm={setActivateForm}
key={index}
{...result}
/>
))}
</Row>
</div>
Finally, in the Modal tag, append to the "onOk" as such, and also in your Modal footer, add the following for "onClick".
<Modal
title='Details'
centered
visible={activateModal}
onCancel={() => setActivateModal(false)}
onOk={() => setActivateForm(true)}
width={800}
footer={[
<Button key="cancel" onClick={() => setActivateModal(false)}>
Cancel
</Button>,
<Button key="schedule" onClick={() =>setActivateForm(true)}><Link to='/ScheduleForm'>Schedule</Link ></Button>
]}
>
{ detailRequest === false ?
(<MovieDetail {...details} />) :
(<Loader />)
}
</Modal>

Resources