I'm trying to disable a submit button on a form if the input text is empty, or if it's greater than 150 characters. For some reason it's working if the input is greater than 150 characters but not when it's empty and I'm not sure why. I've tried everything and I'm still stuck. I'm not sure if it's because I'm using Chakra UI or it's something else. Thank you in advance.
This is what I have:
const AddNewPost = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
const [title, setTitle] = useState("");
const [isSaving, setSaving] = useState(false);
const handleSubmit = async () => {
const date = new Date();
await db.collection("posts").add({
title,
createdAt: date.toUTCString(),
updatedAt: date.toUTCString(),
});
onClose();
setTitle("");
};
return (
<>
<Button onClick={onOpen} colorScheme="blue">
Add new post
</Button>
<Modal onClose={onClose} isOpen={isOpen} isCentered>
<ModalOverlay>
<ModalContent>
<ModalHeader>Add new post</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl id="post-title">
<FormLabel>Post title</FormLabel>
<Textarea
type="post-title"
value={title}
onChange={(e) =>
{
if (e.target.value.length > 150) {
window.alert("Message must not exceed 150 characters")
} else {
setTitle(e.target.value)}
}
}
/>
</FormControl>
</ModalBody>
<ModalFooter>
<HStack spacing={4}>
<Button onClick={onClose}>Close</Button>
<Button
onClick={handleSubmit}
colorScheme="blue"
disabled={
!title || title.length > 150}
isLoading={isSaving}
>
Submit
</Button>
</HStack>
</ModalFooter>
</ModalContent>
</ModalOverlay>
</Modal>
</>
);
}; export default AddNewPost;
According to the Chakra UI Button props documentation the button has no disabled prop.
The correct propname is isDisabled
isDisabled={title === "" || title.length > 150}
Related
In a React app, I am trying to create a custom alert in a separate component as shown below:
employee.ts:*
const [open, setOpen] = useState(false);
const [severity, setSeverity] = useState("");
const [message, setMessage] = useState("");
<CustomAlert open={open} severity={severity} message={message} />
custom-alert.js:
export default function CustomAlert(props) {
const{open, message, severity} = props;
return (
<Snackbar open={open} autoHideDuration={6000} >
{severity !== null && severity !== undefined && severity !== "" ? (
<Alert
variant="filled"
onClick={() => {
setOpen(false);
}}
severity={severity}
sx={{ width: "100%" }}
>
{message}
</Alert>
) : (
<div></div>
)}
</Snackbar>
)
}
Although it works on the first call, it cannot be displayed for the next call from employee component. So, should I define some listener etc? Or can I fix the problem easily by using a smart approach?
I want to set all TextField to the initial state after adding data to the table by clicking ADD button
I am able to set that to the initial state by using AssignSearchesForm.resetForm(); but what happens here if I set this in add button click it will reset data but my table data also get removed because it's updated Formik values setTeamData([values, ...teamdata]);
I am adding Formik value in this state after onClick it will get added but if I reset form and set initial this state value also getting set empty so what I want after adding data into table TextField get reset or initial but table data will stay the same either if we do not update that
This way I am trying
<Button
onClick={() => {
AssignSearchesForm.handleSubmit();
// I tried this two way
//first way
// AssignSearchesForm.resetForm();
//second way
// AssignSearchesForm.setFieldValue("selectName","")
// AssignSearchesForm.setFieldValue("selectAge","")
// AssignSearchesForm.setFieldValue("location","")
}}
variant="contained"
>
Add
</Button>
export default function App() {
const [teamdata, setTeamData] = React.useState([]);
const AssignSearchesForm = useFormik({
initialValues: {
selectName: "",
selectAge: "",
location: ""
},
validationSchema,
onSubmit: (values) => {
setTeamData([values, ...teamdata]);
}
});
let filteredArray = nameList.filter(
(e) => !teamdata.some((data) => data.selectName === e.selectName)
);
const handleChange = (e) => {
const selectedName = e.target.value;
const name = nameList.find((data) => data.selectName === selectedName);
const newOptions = Object.values(name).reduce((optionList, key) => {
optionList.push({ value: key, label: key });
return optionList;
}, []);
AssignSearchesForm.setFieldValue("selectName", selectedName);
AssignSearchesForm.setFieldValue("selectAge", newOptions[1]?.value || "");
AssignSearchesForm.setFieldValue("location", newOptions[2]?.value || "");
};
return (
<div className="App">
<Grid container direction="row" spacing={1}>
<Grid item xs={4}>
<TextField
sx={{ minWidth: 150 }}
select
id="outlined-basic"
label="Select Name"
name="selectName"
size="small"
onChange={handleChange}
value={AssignSearchesForm.values.selectName}
error={
AssignSearchesForm.errors.selectName &&
AssignSearchesForm.touched.selectName
}
>
{filteredArray?.map((option) => (
<MenuItem key={option.selectName} value={option.selectName}>
{option.selectName}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item xs={4}>
<TextField
id="outlined-basic"
label="location"
name="location"
size="small"
{...AssignSearchesForm.getFieldProps("location")}
/>
</Grid>
<Grid item xs={4}>
<TextField
id="outlined-basic"
label="Select Age"
name="selectAge"
size="small"
{...AssignSearchesForm.getFieldProps("selectAge")}
/>
</Grid>
<Grid item xs={4}>
<Button
onClick={() => {
AssignSearchesForm.handleSubmit();
// AssignSearchesForm.resetForm();
// AssignSearchesForm.setFieldValue("selectName","")
// AssignSearchesForm.setFieldValue("selectAge","")
// AssignSearchesForm.setFieldValue("location","")
}}
variant="contained"
>
Add
</Button>
</Grid>
</Grid>
<Table teamdata={teamdata} />
</div>
);
}
You can leverage the second parameter formikHelper in onSubmit function of formik.
Better approach pointed out by dev-developer in comments:
onSubmit: (values, formikHelper) => {
setTeamData([values, ...teamdata]);
formikHelper.resetForm();
}
Another alternative approach if resetForm() is not useful.
onSubmit: (values, formikHelper) => {
setTeamData([values, ...teamdata]);
formikHelper.setFieldValue("selectName", "");
formikHelper.setFieldValue("selectAge", "");
formikHelper.setFieldValue("location", "");
}
Here is the modified code sandbox
I am trying to figure out why any event triggers a visible page refresh when re-rendering. The following is the parent component :
export default function FullScreenDialog({purchaseList={"testtest":"900","heyzi":"90"}}) {
const [open, setOpen] = React.useState(false);
const [pieChartData,setPieChartData]=React.useState([])
const [formset,setFormset]=React.useState([
{
id: uuidv4(),
product:"",
price: 0,
quantity: 0,
productSubtotal: 0,
}
])
const singleForm= {
id: uuidv4(),
product:"",
price: 0,
quantity: 0,
productSubtotal: 0,
}
const handleProduct = (e,id) => {
const newFormset=formset.map(item=>
(item.id !== id? item : {
...item , product: e.target.value , price:purchaseList[e.target.value]
})
)
setFormset(newFormset)
}
const handleQuantity = (e,id) => {
const newFormset=formset.map((item)=>
{
if (item.id===id){
const newItem={
...item, quantity: e.target.value
}
return newItem
}
return item
})
setFormset(newFormset)
}
const handleAdd=()=>{
setFormset([...formset,singleForm])
}
const handleDelete=(id)=>{
const newFormset=formset.filter(item=>
item.id !==id
)
setFormset(newFormset)
}
//below is solely from this component, up is the state from children
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
handlePieChartData()
};
const handlePieChartData=()=>{
setPieChartData(
[...pieChartData,{id:uuidv4(), data:formset }])
}
console.log(formset)
console.log(pieChartData)
return (
<div>
<ListOfPieChartsPresenter
open={open}
handleClickOpen={handleClickOpen}
handleClose={handleClose}
handleProduct={handleProduct}
handleQuantity={handleQuantity}
handleDelete={handleDelete}
handleAdd={handleAdd}
purchaseList={purchaseList}
formset={formset}/>
</div>
);
}
Here is the ListOfPieChartsPresenter component:
const ListOfPieChartsPresenter=({handleClickOpen,open,handleClose,handleProduct,handleQuantity,handleDelete,handleAdd,purchaseList,formset})=>{
const classes = useStyles();
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction="up" ref={ref} {...props} />;
});
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open full-screen dialog
</Button>
<Dialog fullScreen open={open} onClose={handleClose} TransitionComponent={Transition}>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton edge="start" color="inherit" onClick={handleClose} aria-label="close">
{/* <CloseIcon /> */}
</IconButton>
<Typography variant="h6" className={classes.title}>
Sound
</Typography>
<Button autoFocus color="inherit" onClick={handleClose}>
save
</Button>
</Toolbar>
</AppBar>
<form >
<PieChartGroupForm
handleProduct={handleProduct}
handleQuantity={handleQuantity}
handleDelete={handleDelete}
handleAdd={handleAdd}
purchaseList={purchaseList}
formset={formset} />
</form>
</Dialog>
</div>
)
}
Lastly, this is the PieChartGroupFormPresenter:
const PieChartGroupFormPresenter=({handleProduct,handleQuantity,handleDelete,product,quantity,item,purchaseList,id})=>{
return (
<div>
<FormControl>
<Select onChange={(e)=>handleProduct(e,id)} value={product}>
{Object.keys(purchaseList).map((item,index) =>
<MenuItem value={item} key={index}>{item}</MenuItem>
)}
</Select>
<TextField onChange={(e)=>handleQuantity(e,id)} value={quantity} />
<TextField value={item.price} disabled>{item.price}</TextField>
<button onClick={()=>handleDelete(id)}>Delete</button>
</FormControl>
<br>
</br>
</div>
)
}
I have been reviewing this for approx an hour now and I still have no idea why this is happening. Could you please share your opinion? Thank you !
The first issue is that, with function components, every time the parent rerenders, the child will also rerender. You can fix this via React.memo:
const PieChartGroupFormPresenter = React.memo(({handleProduct,handleQuantity,handleDelete,product,quantity,item,purchaseList,id})=>{
//...
});
The second issue is that every time you execute code like const handleAdd=()=>{...} you get a different function (closure). So each time FullScreenDialog's state changes, it's going to change all the props passed to ListOfPieChartsPresenter.
You can use the useCallback hook to improve this: then the function/closure will change only when the specified dependencies change. For example:
const handleAdd = React.useCallback(()=>{
setFormset([...formset,singleForm])
}, [formset, singleForm]);
However, note that this callback function will still change whenever formset or singleForm change, so it will still cause a rerender when this state changes.
So hopefully it's at least clear why rerenders are happening, and why they are difficult to prevent. Unfortunately, I'm not familiar enough with React transitions to figure out why they're triggering for each rerender.
I want to implement two buttons Select All and Select None inside Autocomplete React Material UI along with checkbox for each option.When Select All button is clicked all the options must be checked and when I click Select None all the options must be unchecked.
How do I implement that ?
<Autocomplete
id={id }
size={size}
multiple={multiple}
value={value}
disabled={disabled}
options={items}
onChange={handleChange}
getOptionLabel={option => option.label}
renderOption={(option, { selected }) => (
<React.Fragment >
{isCheckBox(check, selected)}
{option.label}
</React.Fragment>
)}
renderInput={params => (
<TextField id="dropdown_input"
{...params} label="controlled" variant={variant} label={label} placeholder={placeholder} />
)}
/>
export function isCheckBox(check, selected) {
if (check) {
const CheckBox = <Checkbox
id="dropdown_check"
icon={icon}
checkedIcon={checkedIcon}
checked={selected}
/>
return CheckBox;
}
return null;
}
I stumbled into the same issue earlier today.
The trick is to use local state to manage what has been selected, and change the renderOption to select * checkboxes if the local state has the 'all' key in it.
NB: At the time of writing React 16 is what I'm working with
I'm on a deadline, so I'll leave a codesandbox solution for you instead of a rushed explanation. Hope it helps :
Select All AutoComplete Sandbox
Updated
for React version 16.13.1 and later. codesandbox
const [open, setOpen] = useState(false);
const timer = useRef(-1);
const setOpenByTimer = (isOpen) => {
clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
setOpen(isOpen);
}, 200);
}
const MyPopper = function (props) {
const addAllClick = (e) => {
clearTimeout(timer.current);
console.log('Add All');
}
const clearClick = (e) => {
clearTimeout(timer.current);
console.log('Clear');
}
return (
<Popper {...props}>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button color="primary" onClick={addAllClick}>
Add All
</Button>
<Button color="primary" onClick={clearClick}>
Clear
</Button>
</ButtonGroup>
{props.children}
</Popper>
);
};
return (
<Autocomplete
PopperComponent={MyPopper}
onOpen={(e) => {
console.log('onOpen');
setOpenByTimer(true);
}}
onClose={(obj,reason) => {
console.log('onClose', reason);
setOpenByTimer(false);
}}
open={open}
.....
....
/>
);
Old Answer
Just customise PopperComponent and do whatever you want.
Autocomplete API
const addAllClick = (e: any) => {
setValue(items);
};
const clearClick = (e: any) => {
setValue([]);
};
const MyPopper = function (props: any) {
return (
<Popper {...props}>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button color="primary" onClick={addAllClick}>
Add All
</Button>
<Button color="primary" onClick={clearClick}>
Clear
</Button>
</ButtonGroup>
{props.children}
</Popper>
);
};
<Autocomplete
PopperComponent={MyPopper}
...
/>
If you want to make an autocomplete with select all option using react material ui and react hook form, you can implement to Autocomplete like so
multiple: To allow multiple selection
disableCloseOnSelect: To disable the close of the box after each selection
options: Array of items of selection
value: Selected options.
getOptionLabel: The string value of the option in our case is name
filterOptions: A function that determines the filtered options to be rendered on search, in our case we used it to add selectAll checkbox.
renderOption: Render the option, use getOptionLabel by default.
renderInput: To render the input,
onChange: Callback fired when the value changes
Now you can play with selected values using handleChange so once the select is fired check if the selected option is select all if yes then set the newest selectedOptions
<Autocomplete
multiple
disableCloseOnSelect
options={items}
value={selectedOptions}
getOptionLabel={(option) => option.name}
filterOptions={(options, params) => {
const filtered = filter(options, params)
return [{ id: 0, name: selectAllLabel }, ...filtered]
}}
renderOption={(props, option, { selected }) => {
// To control the state of 'select-all' checkbox
const selectAllProps =
option.name === 'Sélectionner Tous' ? { checked: allSelected } : {}
return (
<li {...props}>
<Checkbox checked={selected} {...selectAllProps} />
{option.name}
</li>
)
}}
renderInput={(params) => (
<TextField {...params} label={label} placeholder={label} />
)}
onChange={handleChange}
/>
you can refer to the Autocomplete API to get detailed definition of each item
You can refer to this codeSendBox to check a demo of react material Autocomplete with select all using react material ui version 5 and react hook form verion 7
I have a project on ReactJS which you can find here (see develop-branch) or check it out on our web site.
As you can see, I use formik to handle forms.
Now I have only one submit button which handles all forms however it does not link with forms by form attribute. It was OK.
Unfortunately, I've faced a problem when having a go to implement form validation. I still prefer using formik validation, but the thing is that it demands a direct connection between form and submit button like this:
export function GenerateButton(props) {
return (
<Button id="genButton"
form="form1"
type="submit"
onClick={props.onClick}>
Generate
</Button>
);
}
Any ideas how I can link all forms with submit button?
Or I have to just use fictitious buttons in every form (position: absolute; left: -9999px;) and imitate their click after pushing generate button?
P.S. now there is id="forms" in html form tag, it is just stupid mistake, must be class attribute. I can generate unique id this way: id={"form"+(props.index + 1)}.
P.S.S. I am so sorry for my English.
I think you can handle this with fieldarray of Formik very easily.
you will have an array and a list of forms in it then you can simply add and remove forms.
you won't have any problem with using validation of Formik too.
here is an example that exactly does what you want:
import React from "react";
import { Formik, Form, Field, FieldArray } from "formik";
const formInitialValues = { name: "", lastName: "" };
const FormList = () => (
<Formik
initialValues={{ forms: [formInitialValues] }}
onSubmit={values =>
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
}, 500)
}
render={({ values }) => (
<Form>
<FieldArray
name="forms"
render={arrayHelpers => (
<div>
{values.forms.map((formItem, index) => (
<div key={index}>
<Field name={`forms.${index}.name`} />
<Field name={`forms.${index}.lastName`} />
<button
type="button"
onClick={() => arrayHelpers.remove(index)} // remove a form from the list of forms
>
-
</button>
<button
type="button"
onClick={() =>
arrayHelpers.insert(index, formInitialValues)
} // insert an empty string at a position
>
+
</button>
</div>
))}
<div>
<button type="submit">Submit</button>
</div>
</div>
)}
/>
</Form>
)}
/>
);
export default FormList;
I have provided a code sandbox version for you too
please let me know if you still have any problem
more reading:
https://jaredpalmer.com/formik/docs/api/fieldarray#fieldarray-array-of-objects
The fieldArray solution looks quite complex and I think useFormik is a better option here.
For example:
import React from 'react'
import { Button, Grid, TextField } from '#mui/material'
import clsx from 'clsx'
import { useFormik } from 'formik'
export function MyFormComponent(props: any) {
const formA = useFormik({
initialValues: {
age: 23
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const formB = useFormik({
initialValues: {
name: 'bar'
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const formC = useFormik({
initialValues: {
description: 'A flat object'
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const submitForm = () => {
//get your values here
const values1 = formA.values
const values2 = formB.values
const values3 = formC.values
//data access for storing the values
}
return (
<div>
<form onSubmit={formA.handleSubmit}>
<TextField
style={{ marginBottom: 20 }}
fullWidth
type='number'
label='Age'
id='age'
name='age'
InputProps={{
inputProps: {
min: 0
}
}}
onChange={formA.handleChange}
value={formA.values.age}
/>
</form>
<form onSubmit={formB.handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
onChange={formB.handleChange}
value={formB.values.name}
style={{ marginRight: 20 }}
fullWidth
name='name'
type='text'
label='Name'
InputProps={{
inputProps: {
min: 0
}
}}
/>
</Grid>
</Grid>
</form>
<form onSubmit={formC.handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
onChange={formC.handleChange}
value={formC.values.description}
style={{ marginRight: 20 }}
fullWidth
name='description'
type='text'
label='Description'
InputProps={{
inputProps: {
min: 0
}
}}
/>
</Grid>
</Grid>
</form>
<Button color='secondary' variant='contained' onClick={submitForm}>
Submit
</Button>
</div>
)
}
I'm created a special utility library to work with multiple forms (including repeatable forms) from one place. I see that you are using Formik component (instead of useFormik hook), but maybe it still be useful.
Check this example
https://stackblitz.com/edit/multi-formik-hook-basic-example?file=App.tsx
You can find the library here