I have a component that renders a list of books into bootstrap cards with a checkout button. I have a state for the checkout button called disabled.
I want to enable the button only if the books.quantity>0. How can I successfully change the state of this button?
constructor(props) {
super(props)
this.state = {
books: [],
message: null,
disabled: true
}
}
render() {
return (
<Container fluid>
<Row>
{
this.state.books.map(
books =>
<Col key={books.id} sm="2">
<Card style={{ width: '18rem' }}>
<Card.Img variant="top" src="http://res.freestockphotos.biz/pictures/14/14301-illustration-of-a-book-pv.png" />
<Card.Body>
<Card.Title>{books.title}</Card.Title>
<Card.Subtitle> {books.author.firstName + " " + books.author.lastName} </Card.Subtitle>
<Card.Subtitle > {books.quantity} </Card.Subtitle>
<Button disabled={this.state.disabled} variant="primary" >Checkout</Button>
</Card.Body>
</Card>
</Col>
)
}
</Row>
</Container>
);
}
You can add to the disabled attribute the following:
<Button disabled={books.quantity > 0} variant="primary">Checkout</Button>
This will check the value of books.quantity > 0 and enable the button if the statement is true.
Related
I'm using Formik with chakra UI to create dynamic forms using the Fomrik Field array. and I Open my form in Chakra modal, problem is when I open my form in modal it change field name and id to array last object id , When i remove modal its works as expected, I Recreated my problem here
my code sample
<Box>
{/* start card grid */}
<Box py="12" px={{ base: "6", md: "0" }}>
<FormikProvider value={formik}>
<Box as="section" maxW={{ base: "xs", md: "full" }} mx="auto">
<form onSubmit={formik.handleSubmit}>
<SimpleGrid
columns={{ base: 1, sm: 1, md: 3, xl: 4 }}
spacing="6"
>
<FieldArray name="locations">
{() =>
formik.values?.locations?.map(
(props: any, index: string) => {
const { location, reviews } = props;
return (
<>
<Modal
isOpen={isOpen}
onClose={onClose}
>
<ModalOverlay />
<ModalContent>
<ModalCloseButton />
<ModalBody >
<>
<FormLabel>Page URL</FormLabel>
<InputGroup size="md">
<InputLeftAddon
borderTopEndRadius={0}
borderBottomEndRadius={0}
borderColor="grayExtend.300"
bg="grayExtend.100"
fontSize="0.8rem"
px={2}
>
{modal.urlPrefix}
</InputLeftAddon>
<>
<Input
name={`locations.${index}.facebookUrl`}
value={
formik.values.locations[
index
].facebookUrl
}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
placeholder="facebook name"
type="text"
borderWidth="1.5px"
fontSize="0.8rem"
borderColor="grayExtend.300"
borderTopStartRadius={0}
borderBottomStartRadius={
0
}
/>
<Box
fontSize="0.875rem"
color="red"
mt={2}
>
</Box>
</>
</InputGroup>
</>
</ModalBody>
<ModalFooter mb={4}>
<Button
key={`btn${
Math.random() * 1000
}`}
id={modal.web}
colorScheme="blue"
border="1px"
isFullWidth
type="submit"
_hover={{ bg: "#152aa3" }}
disabled={formik.isSubmitting}
>
Submit
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<Flex
direction="column"
alignItems="center"
rounded="md"
padding="8"
position="relative"
shadow="base"
id={index}
>
<Box
position="absolute"
inset="0"
height="20"
roundedTop="inherit"
/>
<VStack
spacing="4"
mt={3}
>
{buttons.map(
({ title, id, icon }) => (
<Button
key={id+1}
id={id}
colorScheme="grayExtend.100"
borderWidth="1px"
borderColor="grayExtend.300"
variant="outline"
p="10px"
borderRadius="4px"
minH="60px"
minW="300px"
onClick={(e) =>
handleOpenModal(e)
}
className={
formik.values.locations[index]
.facebookUrl &&
formik.errors &&
formik.errors.locations &&
getIn(
formik.errors,
`locations.${index}.facebookUrl`
) &&
"success"
}
display="flex"
justifyContent="start"
bg="#f5faff"
fontSize="0.875rem"
>
{title}
</Button>
)
)}
</VStack>
</Flex>
</>
);
}
)
}
</FieldArray>
</SimpleGrid>
</form>
</Box>
</FormikProvider>
</Box>
{/* end card grid */}
</Box>
as I can see you create modal views for each field. This is not a bad solution but it is better to make one modal and give it data to display (I take out the modal component from the map cycle)
Now the main problem to set special data for each button. I add the state new field id.
In formik inputs change values and names to locations[${modal.id}].facebookUrl
The reason, why are you getting last modal is not controlling it's statement. There is now field which says React not to show 2nd,3nd or other components, so there are 4 modals on each other. You can also use your code but add condition which will hide or show your modal view for example
modal.id === idx && <Modal/>
Also link of this code
import {
Box,
useDisclosure,
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalOverlay,
SimpleGrid,
Flex,
VStack,
FormLabel,
Input,
InputGroup,
InputLeftAddon,
ModalFooter
} from "#chakra-ui/react";
import { FieldArray, getIn, useFormik, FormikProvider } from "formik";
import * as React from "react";
const Form = () => {
const [modal, setModal] = React.useState({
web: "",
urlPrefix: "",
id: 0
});
const { isOpen, onOpen, onClose } = useDisclosure();
const cardData: any = {
locations: [
{
facebookUrl: "fb 1"
},
{
facebookUrl: "fb2"
},
{
facebookUrl: ""
},
{
facebookUrl: ""
}
]
};
const buttons = [
{
id: "facebook",
title: "Facebook",
icon: ""
}
];
// modal func
const handleOpenModal = (e: any, idx: number) => {
const { id } = e.target;
if (id === "facebook") {
setModal({
...modal,
web: "facebook",
urlPrefix: "facebook.com",
id: idx
});
} else return null;
onOpen();
return null;
};
const formik = useFormik({
initialValues: cardData,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
}
});
return (
<Box>
{/* start card grid */}
<Box py="12" px={{ base: "6", md: "0" }}>
<FormikProvider value={formik}>
<Box as="section" maxW={{ base: "xs", md: "full" }} mx="auto">
<form onSubmit={formik.handleSubmit}>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalCloseButton />
<ModalBody>
<>
<FormLabel>Page URL</FormLabel>
<InputGroup size="md">
<InputLeftAddon
borderTopEndRadius={0}
borderBottomEndRadius={0}
borderColor="grayExtend.300"
bg="grayExtend.100"
fontSize="0.8rem"
px={2}
>
{modal.urlPrefix}
</InputLeftAddon>
<>
<Input
name={`locations[${modal.id}].facebookUrl`}
value={
formik.values.locations[modal.id].facebookUrl
}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
placeholder="facebook name"
type="text"
borderWidth="1.5px"
fontSize="0.8rem"
borderColor="grayExtend.300"
borderTopStartRadius={0}
borderBottomStartRadius={0}
/>
<Box fontSize="0.875rem" color="red" mt={2}></Box>
</>
</InputGroup>
</>
</ModalBody>
<ModalFooter mb={4}>
<Button
key={`btn${Math.random() * 1000}`}
id={modal.web}
colorScheme="blue"
border="1px"
isFullWidth
type="submit"
_hover={{ bg: "#152aa3" }}
disabled={formik.isSubmitting}
>
Submit
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<SimpleGrid
columns={{ base: 1, sm: 1, md: 3, xl: 4 }}
spacing="6"
>
<FieldArray name="locations">
{() =>
formik.values?.locations?.map(
(props: any, index: number) => {
const { facebookUrl } = props;
return (
<>
<Flex
direction="column"
alignItems="center"
rounded="md"
padding="8"
position="relative"
shadow="base"
id={index}
>
<Box
position="absolute"
inset="0"
height="20"
roundedTop="inherit"
/>
<VStack spacing="4" mt={3}>
{buttons.map(({ title, id, icon }) => (
<Button
key={id + 1}
id={id}
colorScheme="grayExtend.100"
borderWidth="1px"
borderColor="grayExtend.300"
variant="outline"
p="10px"
borderRadius="4px"
minH="60px"
minW="300px"
onClick={(e) => handleOpenModal(e, index)}
className={
formik.values.locations[index]
.facebookUrl &&
formik.errors &&
formik.errors.locations &&
getIn(
formik.errors,
`locations.${index}.facebookUrl`
) &&
"success"
}
display="flex"
justifyContent="start"
bg="#f5faff"
fontSize="0.875rem"
>
{title}
</Button>
))}
</VStack>
</Flex>
</>
);
}
)
}
</FieldArray>
</SimpleGrid>
</form>
</Box>
</FormikProvider>
</Box>
{/* end card grid */}
</Box>
);
};
export default Form;
My plan is to build a step-by-step from where users can add more sections as needed to a form. This plan is divided into several files to achieve the result, including a parent, a child, and the Quill editor component. I've followed the examples found in the ANT Design website: https://ant.design/components/form/#components-form-demo-dynamic-form-items but failed to replicate the example.
This is the setup for the React-Quill Editor component:
//texteditorsimple.js
import React from 'react';
import ReactQuill, {Quill} from 'react-quill';
import MagicUrl from 'quill-magic-url';
import 'react-quill/dist/quill.snow.css';
//registering magic url module
Quill.register('modules/magicUrl', MagicUrl);
const modules = {
toolbar: [
...
clipboard: {
...
},
magicUrl: true,
};
const formats = [
'list',
'bullet',
'link',
];
const TextEditorSimple = ({ value, onChange, placeholder }) => {
return (
<>
<ReactQuill
theme="snow"
value={value || ''}
modules={modules}
formats={formats}
onChange={onChange}
placeholder={placeholder} >
</ReactQuill>
<div id='counter'></div>
</>
)
}
export default TextEditorSimple;
This is the Parent component:
//parent.js
import React, { useState, useEffect } from 'react';
...
import Child from '../child';
const Parent = () => {
const [ formValue2, setFormValue2 ] = useState([]);
const deleteY = (data) => {
const newArrayFormValue = formValue2.filter(obj => !obj.name.includes(data));
setFormValue2(newArrayFormValue);
}
return (
<Row justify='center' align="top" style={{ padding: 10}}>
<Col>
<Child deleteX={deleteY} fields={formValue2} onChange={(newFields) => {setFormValue2(newFields)}}/>
</Col>
</Row>
)
}
export default Parent;
And this is the Child component:
//child.js
import React from 'react';
...
import TextEditorSimple from './texteditorsimple';
const Child = ({fields, onChange, deleteX}) => {
<Row justify='start' align="top">
<Col xs={24} style={{ padding: 10}}>
<h2>Pengenalan & Langkah-langkah</h2>
<Form
fields={fields}
name="dynamic_form_nest_item"
onFinish={onFinish}
autoComplete="off"
onFieldsChange={(_,allFields) =>{
onChange(allFields);
}} >
<Form.List name="tutorial" style={{ width: '100%'}}>
{(fields, { add, remove }) => (
<>
{fields.map(({ index, key, name, fieldKey, ...restField }) => (
<div key={key}>
<Row>
{
name === 0 ?
<Col xs={24} style={{ padding: 5 }}>
<Form.Item
{...restField}
label='Pengenalan'
name={[name, 'pengenalan']}
fieldKey={[fieldKey, 'pengenalan']}
>
<TextEditorSimple/>
</Form.Item>
</Col>
:
<Col xs={24} md={16} style={{ padding: 5 }}>
<Form.Item
{...restField}
label={'Langkah '+ name}
name={[name, 'langkah'+name]}
fieldKey={[fieldKey, 'langkah'+name]}
>
<TextEditorSimple/>
</Form.Item>
</Col>
}
<Col xs={24} md={8} style={{ padding: 5 }}>
{
name === 0 ?
''
:
<Space align="start">
<Form.Item
{...restField}
name={[name, 'image'+name]}
fieldKey={[fieldKey, 'image'+name]}
valuePropName="fileList"
getValueFromEvent={normFile}
extra="Muat naik satu (1) imej/tangkap layar">
<Upload
name="image"
customRequest={dummyRequest}
listType="picture-card"
maxCount={1} >
{/* <Button icon={<UploadOutlined />}>Muat naik imej/ tangkap layar</Button> */}
{uploadButton}
</Upload>
</Form.Item>
<DeleteOutlined onClick={() => { remove(name); }} style={{ color: 'red'}}/>
</Space>
}
</Col>
</Row>
<Divider/>
</div>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
Tambah Langkah
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form>
</Col>
</Row>
}
export default Child;
I'm trying to catch the changes made on the Form.List 'tutorial' (add, delete) in the Child and uplift the value to the Parent and store them in the state. But the deletion somehow not reflected in the UI, as the element that should have been deleted persisted on page. Tried to manually change the state through poorly coded deleteX and deleteY function, but not successful.
The GIF showing the error can be viewed here: http://www.giphy.com/gifs/HsIjHuckIflqOr3gOO
Here in this code, two card and while clicking the card, it will be marked as selected. Click the card and while clicking the next button it will redirect based on selected card.
In this code, while clicking one card it is selected both it is one issue also. Only clicking button only its redirected to selected card's page.
Used Routing for three pages: CardSelectOptionPage, SuccessPage, FailurePage.
UI Design: Antd Desgin.
Here is code Sandbox link: https://codesandbox.io/s/select-card-moloe?file=/src/index.js
import { Button, Card, Col, Image, Row, Typography } from "antd";
import React, { useState } from "react";
import { icons } from "../assets/icons";
import "./CardSelectOption.scss";
const CardSelectOption = () => {
const [clicked, setClicked] = useState(false);
const chooseOptions = [
{
title: "Yes",
description: "I need to redirect into one Page (Successfull Page)",
icon: clicked ? icons.check : icons.EmptyBox,
},
{
title: "No",
description: "I need to redirect into another Page (UnSuccessfull Page)",
icon: clicked ? icons.check : icons.EmptyBox,
},
];
return (
<Row className="select_page_container">
<Col xl={18} md={18} sm={24} xs={24} className="select_page_header">
<h1>Select One Option</h1>
</Col>
<Row className="select_card_section">
{chooseOptions.map((option, index) => (
<Col xl={8} lg={8} sm={24} key={index} className="select_cards">
<Card onClick={() => setClicked(!clicked)} className="card">
<Row className="card_body">
<Col xl={7} lg={6} md={6} sm={24} xs={24} className="icon_part">
<Image src={option.icon} preview={false} />
</Col>
<Col xl={17} md={18} sm={24} xs={24} className="card_content">
<Col xl={16} lg={16} md={16} className="title">
<Typography.Title level={4}>
{option.title}
</Typography.Title>
</Col>
<Col xl={22} md={22} sm={24} xs={24} className="description">
<Typography.Text>{option.description}</Typography.Text>
</Col>
</Col>
</Row>
</Card>
</Col>
))}
</Row>
<Col xl={12} md={12} sm={24} xs={24} className="submit_button">
<Button size="large" type="primary">
Next
</Button>
</Col>
</Row>
);
};
export default CardSelectOption;
Redirect according to your clicked state
You can navigate like this
const CardSelectOption = (props) => {
const [clicked, setClicked] = useState(false);
const chooseOptions = [
{
title: "Yes",
description: "I need to redirect into one Page (Successfull Page)",
icon: clicked ? icons.check : icons.EmptyBox,
link: "/success"
},
{
title: "No",
description: "I need to redirect into another Page (UnSuccessfull Page)",
icon: !clicked ? icons.check : icons.EmptyBox,
link: "/failure"
}
];
const navigate = () => {
if (clicked === true) {
props.history.push("/Success");
} else {
props.history.push("/Failure");
}
};
return (
<Row className="select_page_container">
<Col xl={12} md={12} sm={24} xs={24} className="select_page_header">
<Typography.Title level={4}>Select One Option</Typography.Title>
</Col>
<Row className="select_card_section">
{chooseOptions.map((option, index) => (
<Col xl={8} lg={8} sm={10} key={index} className="select_cards">
<Card onClick={() => setClicked(!clicked)} className="card">
<Row className="card_body">
<Col xl={7} lg={6} md={6} sm={7} xs={7} className="icon_part">
<Image src={option.icon} preview={false} />
</Col>
<Col xl={17} md={18} sm={17} xs={17} className="card_content">
<Col xl={16} lg={16} md={16} className="title">
<Typography.Title level={4}>
{option.title}
</Typography.Title>
</Col>
<Col xl={22} md={22} sm={24} xs={24} className="description">
<Typography.Text>{option.description}</Typography.Text>
</Col>
</Col>
</Row>
</Card>
</Col>
))}
</Row>
<Col xl={12} md={12} sm={24} xs={24} className="submit_button">
<Button size="large" type="primary" onClick={navigate}>
Next
</Button>
</Col>
</Row>
);
};
call this function from your button!
These are my tabs and tables and I want to call different functions based on tabKey when you click on the tab.
<Paper className={classes.root} style = {{paddingTop:50}}>
<Grid>
<TabSelector
displayType="button"
showTab={"Org Details"}
showCount={false}
highlightTab={true}
onClick={() => this.getTabs.bind(this)}
>
<Tab name="Org Details" tabKey="gridOrgDetails">
<p>
<Grid>
<Paper className={classes.root} style={{ paddingTop: 50 }}>
<a href="#gridOrgDetails" id="gridOrgDetails" />
<Paper style={{ backgroundColor: "#759FEB" }}>
<Typography> ORG Details </Typography>
</Paper>
<EnhancedTable
checkBoxEnabled={false}
Data={{ rows: this.getOrg(), headCells: orgDeatils }}
rowsPerPage={5}
orderBy="Call_Date_vod__c"
order="desc"
/>
</Paper>
</Grid>
</p>
</Tab>
<Tab
name="License Details"
tabKey="gridLicenseDetails"
onClick={() => this.getLicenseDetails()}
>
<p>
<Grid>
<Paper className={classes.root} style={{ paddingTop: 50 }}>
<a href="#gridLicenseDetails" id="gridLicenseDetails" />
<Paper style={{ backgroundColor: "#759FEB" }}>
<Typography> License Details </Typography>
</Paper>
<EnhancedTable
checkBoxEnabled={false}
Data={{ rows: this.getOrg(), headCells: licenseDetails }}
rowsPerPage={5}
orderBy="Call_Date_vod__c"
order="desc"
/>
</Paper>
</Grid>
</p>
</Tab>
</TabSelector>;
This is my function where I am switching keys but this seems to be not working.
Can anyone please help me?
getTabs(f){
console.log(f.tabKey)
switch(f.tabKey) {
case "gridOrgDetails":
return this.getOrgDetails();
break;
case "gridLicenseDetails":
return this.getLicenseDetails();
break;
}
}
Here's a basic representation of what you're trying to achieve jsFiddle
link
const User = (props) => <div onClick = {props.onClick}>I am user
{props.user}</div>
class App extends React.Component {
constructor(props) {
super(props)
}
getTabs(f){
switch(f) {
case "gridOrgDetails":
console.log("gridOrgDetails")
break;
case "gridLicenseDetails":
console.log("gridLicenseDetails")
break;
}
}
render() {
return (
<div>
<div >
<User user="a" onClick={this.getTabs.bind(this,"gridOrgDetails")} />
<User user="b" onClick={this.getTabs.bind(this, "gridLicenseDetails")}/>
</div>
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
I have a "sign up" button. When it is clicked, I would like for it to render a new component, called "SignUp". I would like for the new component to replace the "sign up" button.
Currently, I am using setState so that when the button is clicked, a new component is rendered. However, the button is not being replaced, the "SignUp" component is just rendered beside the button. What may be a good approach for me to replace the button with the new component being rendered?
I have provided my code below:
export default class SignUpSignIn extends Component {
constructor() {
super();
this.state = {
clicked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
clicked: true
});
}
render () {
return (
<div id="SignUpSignInDiv">
<Col md="12" sm="12" xs="12" className="text-center">
<div onClick={this.handleClick}>
{this.state.clicked ? <SignUp /> : null}
<Button id="SignUpButton" color="primary"> Sign Up </Button>
</div>
</Col>
</div>
)
}
}
Well, you're not rendering both components conditionally, only the SignUp. Instead of having null in your ternary, render the sign in button when state.clicked === false:
render () {
return (
<div id="SignUpSignInDiv">
<Col md="12" sm="12" xs="12" className="text-center">
{this.state.clicked ? (
<SignUp />
) : (
<Button id="SignUpButton" color="primary" onClick={this.handleClick}> Sign Up </Button>
)}
</Col>
</div>
)
}
one way to do it is like this , i didnt test it yet but it should work
render () {
let show = <div></div>
if(this.state.clicked){
show = <SignUp />
}
return (
<div id="SignUpSignInDiv">
<Col md="12" sm="12" xs="12" className="text-center">
{show}
<Button id="SignUpButton" color="primary" onClick={this.handleClick}> Sign Up </Button>
</Col>
</div>
)
}