Handling Multiple Checkboxes with Material UI and Formik - reactjs

I have a form with a checkbox group containing 4 checkboxes within. I need to pass the value to my api on submit of button which is to be handled through Formik.
There are a few conditions on clicking a particular checkbox:
If Height is checked, rest all should be checked as well
If Breadth is checked, except for Height all should be checked
If Length is checked, Both Nos and Length should be checked
Nos can be checked or left unchecked
Currently I'm declaring individual states for all checkboxes and declaring individual functions for each of the conditions mentioned above.
Is there any other way to bind the values from the checkbox to Formik and pass to the api
State and Individual Functions:
const [nos, setNos] = useState(false);
const [length, setLength] = useState(false);
const [breadth, setBreadth] = useState(false);
const [height, setHeight] = useState(false);
const handleHeight = e => {
setNos(!nos);
setLength(!length);
setBreadth(!breadth);
setHeight(!height);
};
const handleBreadth = e => {
setNos(!nos);
setLength(!length);
setBreadth(!breadth);
};
const handleLength = e => {
setNos(!nos);
setLength(!length);
};
const handleNos = e =>{
setNos(!nos);
};
Form with Formik:
const formik = useFormik({
initialValues,
validationSchema,
onSubmit: async (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<>
<form
onKeyDown={onKeyDown}
id="newJobDetails"
name="newJobDetails"
onSubmit={formik.handleSubmit}
className={CommonClasses.formPartStyling}
style={{
height: SetFormSize(
isAdd ? "Add" : textFieldDisable ? "Info" : "Edit",
1
),
marginTop: marginTop || "none",
}}
>
<div
className={CommonClasses.headingTextOfForms}
style={{ marginLeft: "4%", marginTop: "2%" }}
>
Add Jobs
</div>
<Divider
style={{
marginTop: "1%",
marginBottom: "3%",
color: "white",
}}
></Divider>
<div
style={{
marginLeft: "4%",
marginTop: "2%",
}}
>
<Grid container>
<Grid item xs={6}>
<LabelStyling>Job Name</LabelStyling>
<TextField
id="jobName"
name="jobName"
variant="outlined"
placeholder="Enter Job Name"
value={formik.values.jobName}
onChange={formik.handleChange}
size="small"
style={{
width: FORM_PART_VARS.INPUT_BOX_WIDTH,
}}
/>
</Grid>
<Grid item xs={6}>
<LabelStyling>Stage</LabelStyling>
<DropDown
id="stage"
width={FORM_PART_VARS.INPUT_BOX_WIDTH}
value={formik.values.stage}
onChange={formik.handleChange}
error={formik.touched.stage && Boolean(formik.errors.stage)}
helperText={formik.touched.stage && formik.errors.stage}
mapFile={Stage}
placeholder="Select Stage"
/>
</Grid>
</Grid>
</div>
<div
style={{
marginLeft: "4%",
marginTop: "2%",
}}
>
<Grid container>
<Grid item xs={6}>
<LabelStyling>Measurement</LabelStyling>
<TextField
id="measurement"
name="measurement"
variant="outlined"
placeholder="Enter Measurement"
value={formik.values.measurement}
onChange={formik.handleChange}
size="small"
style={{
width: FORM_PART_VARS.INPUT_BOX_WIDTH,
}}
/>
</Grid>
<Grid item xs={6}>
<LabelStyling>Measurement Unit</LabelStyling>
<DropDown
id="measurement_unit"
width={FORM_PART_VARS.INPUT_BOX_WIDTH}
value={formik.values.measurement_unit}
onChange={formik.handleChange}
error={
formik.touched.measurement_unit &&
Boolean(formik.errors.measurement_unit)
}
helperText={
formik.touched.measurement_unit &&
formik.errors.measurement_unit
}
mapFile={MeasurementUnit}
placeholder="Select Unit"
/>
</Grid>
</Grid>
</div>
<div
style={{
marginLeft: "4%",
marginTop: "2%",
}}
>
<Grid container>
<Grid item xs={6}>
<LabelStyling>Rate</LabelStyling>
<TextField
name="jobRate"
id="jobRate"
variant="outlined"
placeholder="Enter Rate"
value={formik.values.jobRate}
onChange={formik.handleChange}
size="small"
style={{
width: FORM_PART_VARS.INPUT_BOX_WIDTH,
}}
/>
</Grid>
<Grid item xs={6}>
<LabelStyling>Service Rate</LabelStyling>
<TextField
name="serviceRate"
id="serviceRate"
variant="outlined"
placeholder="Enter Service Rate"
value={formik.values.serviceRate}
onChange={formik.handleChange}
size="small"
style={{
width: FORM_PART_VARS.INPUT_BOX_WIDTH,
}}
/>
</Grid>
</Grid>
</div>
<div
style={{
marginLeft: "4%",
marginTop: "4%",
}}
>
<LabelStyling>Select Measurement(s)</LabelStyling>
<FormGroup row>
<FormControlLabel control={<CheckBox checked={nos} onChange={handleNos} name="Nos" id="1"/>} label="Nos" />
<FormControlLabel control={<CheckBox checked={length} onChange={handleLength} name="Length" id="2"/>} label="Length" style={{marginLeft: "15px"}}/>
<FormControlLabel control={<CheckBox checked={breadth} onChange={handleBreadth} name="Breadth" id="3"/>} label="Breadth" style={{marginLeft: "15px"}}/>
<FormControlLabel control={<CheckBox checked={height} onChange={handleHeight} name="Height" id="4"/>} label="Height" style={{marginLeft: "15px"}}/>
</FormGroup>
</div>
<div
style={{
marginLeft: "4%",
marginTop: "4%",
}}
>
<LabelStyling>Select Job Type</LabelStyling>
<FormGroup row>
<FormControlLabel control={<CheckBox name="jobType" value="Residential"/>} label="Residential"/>
<FormControlLabel control={<CheckBox name="jobType" value="Commercial"/>} label="Commercial"/>
<FormControlLabel control={<CheckBox name="jobType" value="Infrastructure"/>} label="Infrastructure"/>
</FormGroup>
</div>
</form>
</>
);

When using Formik , it is not needed to maintain additional state to capture the form field values Formik will take care of that .
In this case , since we need to programmatically change the values of other inputs based on a change in one input we can make use of the setFieldValue prop provided by Formik .
Working Sandbox

To combine multiple states and multiple handlers, try something like this:
const [state, setState] = useState({
nos: false,
length: false,
breadth: false,
height: false
});
const handleCheckBox = useCallback((checkbox) => () => setState(state => {
switch (checkbox) {
case 'nos': return {
...state,
nos: !state.nos,
};
case 'length': return {
...state,
nos: !state.nos,
length: !state.length,
};
case 'breadth': return {
...state,
nos: !state.nos,
length: !state.length,
breadth: !state.breadth,
};
case 'height': return {
...state,
nos: !state.nos,
length: !state.length,
breadth: !state.breadth,
height: !state.height,
};
default: {
console.warn('unknown checkbox', checkbox);
return state;
}
}
}), []);
<FormGroup row>
<FormControlLabel control={<CheckBox checked={state.nos} onChange={handleCheckBox('nos')} name={'Nos'} id={'1'}/>} label={'Nos'}/>
<FormControlLabel control={<CheckBox checked={state.length} onChange={handleCheckBox('length')} name={'Length'} id={'2'}/>} label={'Length'} style={{marginLeft: '15px'}}/>
<FormControlLabel control={<CheckBox checked={state.breadth} onChange={handleCheckBox('breadth')} name={'Breadth'} id={'3'}/>} label={'Breadth'} style={{marginLeft: '15px'}}/>
<FormControlLabel control={<CheckBox checked={state.height} onChange={handleCheckBox('height')} name={'Height'} id={'4'}/>} label={'Height'} style={{marginLeft: '15px'}}/>
</FormGroup>

Related

Is there a way to register custom components with useForm?

I am creating a web form using useForm(). This is my main component:
function InsertComponent() {
const [category, setCategory] = useState<number>(-1);
const { register, handleSubmit, control } = useForm({ shouldUnregister: false });
const onSubmit = (data: any, e: any) => {
console.log(control);
console.log(data, e);
}
const changeCategory = (category: number) => {
setCategory(category);
}
return (
<Container component={'main'} maxWidth={'xs'}>
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<Typography component={'h1'} variant={'h5'}>
Nuevo gasto
</Typography>
<Box component={'form'} noValidate sx={{ mt: 3 }} onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<TextField label={'Cantidad'} autoComplete="given-name" id="quantity" required fullWidth autoFocus {...register('quantity')}></TextField>
</Grid>
<Grid item xs={12} sm={6}>
<SelectComponent options={MONTHS} label={'Month'} />
</Grid>
<Button type="submit" style={{ marginTop: '20px' }} variant="outlined">Guardar</Button>
</Box>
</Box>
</Container>
)
}
I can register data with {...register('quantity')} in TextField and that works fine. The problem is I am not finding a way to register the options selected in the SelectComponent component. This is the component:
function SelectComponent(props: SelectCategory) {
const { options, label, onChange, disabled, control } = props;
const { show } = useWatch({ control })
const handleChangeCategory = (event: SelectChangeEvent) => {
onChange && onChange(parseInt(event.target.value))
}
return (
<Box sx={{ minWidth: 150 }}>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">{label}</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
disabled={disabled}
label={label}
onChange={handleChangeCategory}
>
{options.map((option, index) => <MenuItem value={index}>{option}</MenuItem>)}
</Select>
</FormControl>
</Box>
)
}
I've been reading the docs and trying npm libraries like useStateMachine but I haven't gotten it to work. The value of SelectComponent is never registered on the InsertComponent and never appears in the data of the onSubmit function.

React and LocalStorage

I am trying this code where I can send some data and save it in the localstorage. I tied the below code.
function Login () {
const uname=useRef()
const pass = useRef()
const getEmail = localStorage.getItem("emailData")
const getPassword = localStorage.getItem("passwordData")
const handleSubmit=()=>{
if(uname.current.value==="admin"&&pass.current.value==="admin"){
localStorage.setItem("emailData","admin")
localStorage.setItem("passwordData","admin")
}
}
const [values, setValues] = useState({
email: "",
pass: "",
showPass: false,
});
const handlePassVisibilty = () => {
setValues({
...values,
showPass: !values.showPass,
});
};
return (
<div>
{
getEmail&&getPassword?<Home/>:
<Container maxWidth="sm">
<Grid
container
spacing={2}
direction="column"
justifyContent="center"
style={{ minHeight: "100vh" }}
>
<Paper elelvation={2} sx={{ padding: 10 }}>
<h2>Welcome to Employee Management System</h2>
<form onSubmit={handleSubmit}>
<Grid container direction="column" spacing={2}>
<Grid item>
<TextField
type="text"
fullWidth
label="Enter your Username"
placeholder="Username"
variant="outlined"
required
ref={uname}
/>
</Grid>
<Grid item>
<TextField
type={values.showPass ? "text" : "password"}
fullWidth
label="Enter your Password"
placeholder="Password"
variant="outlined"
required
ref={pass}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={handlePassVisibilty}
aria-label="toggle password"
edge="end"
>
{values.showPass ? (
<VisibilityOffIcon />
) : (
<VisibilityIcon />
)}
</IconButton>
</InputAdornment>
),
}}
/>
</Grid>
<Grid item>
<Button type="submit" fullWidth variant="contained" >
Sign In
</Button>
</Grid>
</Grid>
</form>
</Paper>
</Grid>
</Container>
}
</div>
);
};
export default Login;
What I am trying to do is that if the localstorage has the correct username and password the login page will redirect to the home page. The problem I am facing is that the data is not stored in the localstorage. Can someone please explain to me what I am doing wrong? Any help is appreciated Thank you.
you need to give type="submit" to button of your form in order to submit form
<Button fullWidth type="submit" variant="contained">
Sign In
</Button>
if it still not working, use state instead
here what I did with your code :
const getEmail = localStorage.getItem("emailData")
const getPassword = localStorage.getItem("passwordData")
const [values, setValues] = useState({
email: "",
pass: "",
showPass: false,
});
const handleSubmit=()=>{
if(values.email ==="admin" && values.pass ==="admin"){
localStorage.setItem("emailData","admin")
localStorage.setItem("passwordData","admin")
}
}
const handlePassVisibilty = () => {
setValues({
...values,
showPass: !values.showPass,
});
};
return (
<div>
{
getEmail&&getPassword?<Home/>:
<Container maxWidth="sm">
<Grid
container
spacing={2}
direction="column"
justifyContent="center"
style={{ minHeight: "100vh" }}
>
<Paper elelvation={2} sx={{ padding: 10 }}>
<h2>Welcome to Employee Management System</h2>
<form onSubmit={handleSubmit}>
<Grid container direction="column" spacing={2}>
<Grid item>
<TextField
type="text"
fullWidth
label="Enter your Username"
placeholder="Username"
variant="outlined"
required
value={values.email}
onChange={(e)=>{
setValues(prevState=>({...prevState,email:e.target.value}))
}}
/>
</Grid>
<Grid item>
<TextField
type={values.showPass ? "text" : "password"}
fullWidth
label="Enter your Password"
placeholder="Password"
variant="outlined"
required
value={values.pass}
onChange={(e)=>{
setValues(prevState=>({...prevState,pass:e.target.value}))
}}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={handlePassVisibilty}
aria-label="toggle password"
edge="end"
>
{values.showPass ? (
<VisibilityOffIcon />
) : (
<VisibilityIcon />
)}
</IconButton>
</InputAdornment>
),
}}
/>
</Grid>
<Grid item>
<Button type="submit" fullWidth variant="contained" >
Sign In
</Button>
</Grid>
</Grid>
</form>
</Paper>
</Grid>
</Container>
}
</div>
);

React picky not auto closing while selecting one value,?

If using single select, is it possible to close the dropdown after selection? I tried passing keepOpen props but cant able to fix, whiling passing keepOpen the drop down not working as it would, How can fix this issue?
console log while clicking drop down
console log
Parent Component Input Feild
<Grid item xs={12} sm={12} md={12} className={classes.item}>
<Field name='gender' label={I18n.t('gender')} filterable={false} component={renderSelect} >
{
_.get(getGender, 'data', genderArray)
}
</Field>
</Grid>
<Grid item xs={12} sm={12} md={12} className={classes.item}>
<Field name='userTypeResponse' label={I18n.t('user_type')} component={renderSelect} filterable={false} >
{
data?.map(item => ({ id: item.id, name: item.name }))
}
</Field>
</Grid>
Selector Component
const renderSelect = ({ input: { value, name, ...inputProps }, children, selectAll, selectAllText = 'Select All', filterable = true, multiple, label, disabled = false, placeholder = '', showLoader = false, spinnerProps = 'selectTagProp', meta: { touched, error } }) => {
return < LoadingOverlay active={showLoader} spinnerProps={spinnerProps} >
<FormGroup>
{label && <div className='customPicky'>
<InputLabel htmlFor={name}>{label}</InputLabel>
</div>}
<MultiSelect
value={multiple ? value || [] : value || null} {...inputProps}
options={children}
open={false}
multiple={multiple}
keepOpen={false}
includeSelectAll={selectAll}
selectAllText={selectAllText}
includeFilter={filterable}
labelKey='name'
valueKey='id'
placeholder={placeholder}
dropdownHeight={150}
clearFilterOnClose={true}
defaultFocusFilter={true}
disabled={disabled}
className={disabled === true ? 'pickySelect grey' : null}
numberDisplayed={1}
renderList={({ items, selectValue, getIsSelected }) => {
return <div >
{items.length < 1 && <div style={{ textAlign: 'center', padding: '5px' }}>{I18n.t('no_data_available')}</div>}
{items.map(item => (
<li key={item.id} onClick={() => selectValue(item)} style={{ display: 'flex', alignItems: 'center' }}>
<input type={multiple ? 'checkbox' : 'radio'} id={item.id} checked={getIsSelected(item)} />
<div style={{ marginLeft: '5px' }} >
{item.name}
</div>
</li>
))}
</div>;
}
}
/>
{touched && error && <Typography color="error" variant="caption">{error}</Typography>}
</FormGroup>
</LoadingOverlay >;
};

How to align Material ui textfields in React?

I am developing a React js application using Material ui components.
I am a 2 column grid, each column has 4 textField.
Some of those textField are weapped in Autocomplete component.
The problem is that they don't have the same styles thus they are not aligned facing each others.
Here is a screenshot of the result
As you can notice the problem in the second and third row of the left column.
Here is my code
<Grid container style={{ width: "100%", marginTop: 10 }}>
<Grid item style={{ padding: 10 }} xs={12} sm={12} md={6}>
<TextField
className={`${classes.textfield} ${
values.nameError && classes.inputError
}`}
onFocus={(e) => setValues({ ...values, nameError: false })}
onChange={(e) => setValues({ ...values, name: e.target.value })}
error={values.nameError}
helperText={values.nameError ? t("Obligatoire") : ""}
margin="normal"
fullWidth
type="text"
value={values.name}
label={t("nomFR") + " *"}
autoComplete="given-name"
/>
//Same for the rest
</Grid>
<Grid item style={{ padding: 10 }} xs={12} sm={12} md={6}>
<SelectInputWrapper>
<FormControl fullWidth>
<Autocomplete
options={spaces}
getOptionLabel={(option) => option.label}
renderOption={(option, { selected }) => (
<div className={classes.menuItem}>{option.label} </div>
)}
value={values.espace}
disableClearable
renderInput={(params) => (
<TextField
{...params}
error={values.espaceError}
onFocus={(e) =>
setValues({ ...values, espaceError: false })
}
style={{ marginLeft: 3, marginRight: 3 }}
margin="normal"
value={""}
label={t("Espace") + " *"}
/>
)}
/>
{values.espaceError && (
<FormHelperText className={classes.helper} error={true}>
{t("Obligatoire")}
</FormHelperText>
)}
</FormControl>
</SelectInputWrapper>
//Same for the rest
</Grid>
</Grid>
Notice, I am not giving any margins or paddings.
Is there a solution please? I am stuck here for 2 days.

how to preview the selected image upload React js

Have created a button called image upload, need to view the selected image when selected
<GridItem xs={6}>
<FormControl className={classes.formControl}>
<input
accept="image/*"
className={classes.input}
style={{ display: 'none' }}
id="raised-button-file"
multiple
type="file"
/>
<label htmlFor="raised-button-file">
<Button
variant="raised"
component="span"
className={classes.button}
>
Upload Image
</Button>
</label>
</FormControl>
</GridItem>
you can set the file object URL in the state during the onChange and use it in an img tag like this,
const [file, setFile] = useState(undefined);
const handleChange = (event) => {
setFile(URL.createObjectURL(event.target.files[0]));
}
use this handleChange method in input onChange event,
<GridItem xs={6}>
<FormControl className={classes.formControl}>
<input
accept="image/*"
className={classes.input}
style={{ display: 'none' }}
id="raised-button-file"
multiple
type="file"
onChange={handleChange}
/>
{/* preview of file */}
{ file && <img src={this.state.file}/>}
<label htmlFor="raised-button-file">
<Button
variant="raised"
component="span"
className={classes.button}
>
Upload Image
</Button>
</label>
</FormControl>
</GridItem>
also if you want to allow the user to crop the image selected there is this library react-image-crop. I use it and it works pretty good for me.
before select
select image
Typescript
import { Grid, IconButton, makeStyles, Tooltip } from '#material-ui/core'
import { PhotoCamera, Publish } from "#material-ui/icons"
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import { IRequestState } from '../../assets/interfaces'
const FILE = 'ImageUpload.tsx'
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(1),
},
}, right: {
float: "right"
}, w100: {
width: "100%"
}, input: {
display: "none",
},
faceImage: {
color: theme.palette.primary.light,
}, success: {
color: theme.palette.success.main
}
}))
interface IImageUploadProps {
Submit(event: React.MouseEvent<HTMLButtonElement>): void
}
type IImageUploadState = IRequestState & {
imageFile?: any
}
export default function ImageUpload(props: IImageUploadProps) {
const classes = useStyles()
const dispatch = useDispatch()
const initState: IImageUploadState = { inited: false, requesting: false }
const [values, setState] = useState<IImageUploadState>(initState)
const handleCapture = ({ target }: any) => {
if (target && target.files && target.files.length)
setState({ ...values, imageFile: target.files[0] })
}
return (
<>
<Grid
container
direction="row"
justify="space-evenly"
alignItems="center"
spacing={2}
>
<Grid item sm={6} md={3}>
<Grid
justify="center"
alignItems="center"
container
>
<Grid item>
<input
accept="image/jpeg"
className={classes.input}
id="faceImage"
type="file"
onChange={handleCapture}
/>
<Tooltip title="select image">
<label htmlFor="faceImage">
<IconButton
className={classes.faceImage}
color="primary"
aria-label="upload picture"
component="span"
>
<PhotoCamera fontSize="large" />
</IconButton>
</label>
</Tooltip>
</Grid>
</Grid>
</Grid>
<Grid item sm={6} md={3}>
<img className={classes.w100} hidden={!values.imageFile} src={values.imageFile && URL.createObjectURL(values.imageFile)} alt="Logo" />
</Grid>
<Grid item sm={6} md={3}>
<Grid
justify="center"
alignItems="center"
container
>
<Grid item>
<Tooltip title="upload image">
<IconButton
className={classes.success}
aria-label="upload"
onClick={props.Submit}
edge="end"
disabled={!values.imageFile}
>
<Publish />
</IconButton>
</Tooltip>
</Grid>
</Grid>
</Grid>
</Grid>
</>
)
}

Resources