Why my checkbox doesn't work in my Dialog? - reactjs

I create a component for my Dialog and my Checkbox my issue is when my checkbox is not in the Dialog the update works but when it's inside it doesn't work. I don't understand why.
const Popup = ({ title, handleClose, openned, children }) => {
return (
<Dialog className='react-popup-template' fullWidth={true} maxWidth='sm' open={openned} onClose={handleClose} aria-labelledby="parent-modal-title" aria-describedby="parent-modal-description">
<DialogContent id="modal-description" >
<div>
{title && <div><h4 style={{ textAlign: 'center', fontWeight: 'bold', fontSize : '23px' }}>{title}</h4><br/></div>}
{children}
</div>
</DialogContent>
</Dialog>
);
}
const CheckBox = (value, onChange) => {
return (
<label>
<input type='checkbox' value={value} onChange={onChange} />
</label>)
}
const App = () =>{
const [openPopup, setOpenPopup] = React.useState(false)
const [checked, setChecked] = React.useState(false)
const [title, setTitle] = React.useState('')
const [description, setDescription] = React.useState('')
const showModal = (title) =>{
setTitle(title)
setDescription(<CheckBox value={checked} onChange={() => {setChecked(!checked)}} />)
}
return (
<button onClick={() => {showModal('Title')}}>showModal</button>
<PopupTemplate title={title} handleClose={() => { setOpenPopup(false) }} openned={openPopup}>
{description}
</PopupTemplate>)
}

In your Checkbox you should either destructure your props
const CheckBox = ({ value, onChange }) => {
return (
<label>
<input type="checkbox" value={value} onChange={onChange} />
</label>
);
};
Or use your props via the props value
const CheckBox = (props) => {
return (
<label>
<input type="checkbox" value={props.value} onChange={props.onChange} />
</label>
);
};
EDIT:
The state only updates the first time you click the checkbox. Using the callback in the setChecked method will solve this.
...
setDescription(
<CheckBox
value={checked}
onChange={() => {
setChecked((prevChecked) => !prevChecked);
}}
/>
);
...
PS: I don't now if its just a copy/paste error, but you're missing setOpenPopup(true) in your showModal function.

Try this, as mui uses forwarRef for its components this should work,
setDescription(<CheckBox checked={checked} onChange={e => setChecked(!checked)} />)

Related

Override input value in Child component in React

I have a child input component and parent where I set value.
Parent component
const \[dummyText, setDummyText\] = useState('')
return (
<Input
{...props}
id="first-name"
label="First name"
type="text"
placeholder="Please enter your first name"
value={dummyText}
onChange={(value) => setDummyText(value.target.value)}
/>
)
Child input component
import React, { InputHTMLAttributes, forwardRef } from 'react'
export type Props = {} & InputHTMLAttributes<HTMLInputElement>
const FormInput: React.FC<Props> = forwardRef<HTMLInputElement, Partial<Props>>(
({ ...props }, ref) => <input ref={ref} {...props} />
)
FormInput.displayName = 'FormInput'
export default FormInput
I want to add button in child component where on click I need to clear the value.
The thing is that I want to have clear functionality in child component and I do not want to write extra code for every <Input in parent, and whatever I do in child component, I just can't override props.value.
Does anybody have any experience how to solve this?
If you want to clear the input value you can pass the clearing function as a prop to the child component like this:
const Input = ({ onClearInput, ...otherProps }) => {
return (
<div style={{ display: 'flex', gap: '.5rem' }}>
<input {...otherProps} />
<button onClick={onClearInput}>Clear</button>
</div>
);
};
export default function App() {
const [firstName, setFirstName] = useState('');
const [secondName, setSecondName] = useState('');
const onClearInputHandler = (handler) => handler('');
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '.5rem' }}>
<Input
id="first-name"
label="First name"
type="text"
placeholder="Please enter your first name"
value={firstName}
onChange={(event) => setFirstName(event.target.value)}
onClearInput={() => onClearInputHandler(setFirstName)}
/>
<Input
id="second-name"
label="Second name"
type="text"
placeholder="Please enter your second name"
value={secondName}
onChange={(event) => setSecondName(event.target.value)}
onClearInput={() => onClearInputHandler(setSecondName)}
/>
</div>
);
}
Because you'll have a different state for each input you can pass the set function from useState as a parameter and run it inside the onClearInputHandler to clear the value.
Edit:
Then I think the easiest solutions is to handle the clear function inside the input itself like this:
const Input = (props) => {
const [value, setValue] = useState(props.value || '');
return (
<div style={{ display: 'flex', gap: '.5rem' }}>
<input
{...props}
value={value}
onChange={(event) => setValue(event.target.value)}
/>
<button onClick={() => setValue('')}>Clear</button>
</div>
);
};
export default function App() {
const onSubmitHandler = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const formProps = Object.fromEntries(formData);
console.log(formProps);
};
return (
<form action="submit" onSubmit={onSubmitHandler}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '.5rem' }}>
<Input
id="first-name"
label="First name"
type="text"
placeholder="Please enter your first name"
name="first-name"
/>
<Input
id="second-name"
label="Second name"
type="text"
placeholder="Please enter your second name"
name="second-name"
/>
</div>
<button type="submit" style={{ margin: '.5rem 0' }}>
Submit
</button>
</form>
);
All inputs are inside the form and you can read the values when the form is submitted. Otherwise you'll have to create a new state or ref for every single Input you have in your parent component. This way the input value and the handler to clear it are always inside the Input component.
Here's a demo on stackblitz: https://stackblitz.com/edit/react-gnyvav?file=src%2FApp.js
There are a few solutions for this, I came up with these two
onValueChange
Another solution might be to refactor the onChange to a onValueChange which instead of the event immediately returns the value. Updated the type and removed the Partial since it was giving some errors.
type Props = {
onValueChange: (value: string) => void;
} & InputHTMLAttributes<HTMLInputElement>;
const FormInput: React.FC<Props> = forwardRef<HTMLInputElement, Props>(
({ onValueChange, ...props }, ref) => {
return (
<>
<input
ref={ref}
onChange={(e) => onValueChange(e.target.value)}
{...props}
/>
<button onClick={() => onValueChange("")}>clear</button>
</>
);
}
);
A issue with this is that if a onChange is provided it will override the existing onChange. We can solve this by simply adding it and passing the e (event)
const FormInput: React.FC<Props> = forwardRef<HTMLInputElement, Props>(
({ onValueChange, onChange, ...props }, ref) => {
return (
<>
<input
ref={ref}
onChange={(e) => {
onValueChange(e.target.value);
onChange(e);
}}
{...props}
/>
<button onClick={() => onValueChange("")}>clear</button>
</>
);
}
);
Which you can then use by
<FormInput
id="first-name"
type="text"
placeholder="Please enter your first name"
value={dummyText}
onValueChange={setDummyText}
// onChange={(e) => console.log(e)}
/>
onClear
Now this does not fully move the functionality to the Input component.
We destructure the onClear function and add it to the onClick of the clear button.
type Props = { onClear: () => void } & InputHTMLAttributes<HTMLInputElement>;
const FormInput: React.FC<Props> = forwardRef<HTMLInputElement, Props>(
({ onClear, ...props }, ref) => {
return (
<>
<input ref={ref} {...props} />
<button onClick={onClear}>clear</button>
</>
);
}
);
Now to use it you can go like this
<Input
id="first-name"
onClear={() => setDummyText("")}
type="text"
placeholder="Please enter your first name"
value={dummyText}
onChange={(event) => setDummyText(event.target.value)}
/>

useState not updating data when passing from parent function component using ref

I am trying to send data to child function component where I am binding form fields with that data. It works fine on first call, but when I am calling 2nd time the data never update in state, its always shows the first one.
This is parent which use the ref of child component
export default function Form1() {
const [count, setCount] = useState(0);
const [counter, setCounter] = useState(10);
const AddNewRef = useRef();
const clickMe=() => {
setCount(count+1);
setCounter(counter*2);
AddNewRef.current.showDrawer(counter*2);
}
return (
<div>
<p>You clicked count: {count} & counter: {counter} times</p>
{
count > 10 ?
(
<p className='red'>your count is greater then 10</p>
) :
(
<p className='green'>your count is less then 10</p>
)
}
<button onClick={() => clickMe()}>
Click me
</button>
<AddNew ref={AddNewRef} Count={count} Counter={counter} />
</div>
)
}
This is child component
const AddNew=forwardRef((props, ref) => {
const[objCounter, setobjCounter] = useState(null);
useImperativeHandle(
ref,
() => ({
showDrawer(count) {
setobjCounter(count);
//only shows at first click at parent, Not updating on 2nd, 3rd click from parent and so on....
}
}),
)
return (
<>
<Drawer
title={<span> <i className='fa-solid fa-kaaba' /> Haj Setup Form</span>}
width={window.innerWidth > 900 ? 800 : window.innerWidth - 50}
onClose={onClose}
visible={visible}
bodyStyle={{ paddingBottom: 80 }}
extra={
<Space>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={onClose} type="primary">
Submit
</Button>
</Space>
}
>
<Form
style={{display: formVisible ? 'block' : 'none'}}
form={form}
layout="vertical"
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
hideRequiredMark>
<Row gutter={16}>
<Col xs={24} sm={24} md={24} lg={24}>
<Form.Item
name="packageName"
label="Package Name"
rules={[{ required: true, message: 'Please enter package name' }]}
initialValue={objCounter}
>
<Input style={{width: '100%'}}
maxLength={100} />
</Form.Item>
</Col>
</Row>
</Form>
</Drawer>
</>
)
});
export default AddNew
Since the state updates are working and you are simply wanting to update the form field, you can use the returned form reference from the useForm hook to update the form state. In this case, update the packageName field.
const AddNew = forwardRef((props, ref) => {
const [objCounter, setobjCounter] = useState(13);
const [visible, setVisible] = useState(false);
const [formVisible, setformVisible] = useState(true);
const [form] = Form.useForm();
useImperativeHandle(ref, () => ({
showDrawer(count) {
setobjCounter(count);
setVisible(true);
form.setFieldsValue({
packageName: count // <-- update the specific field
});
}
}));
const onClose = () => {
setVisible(false);
};
return (
...
);
});

How to Use the Same handleChange Event on Different Components

I'm trying to make a function which can disabled textfield when checkbox is checked.
The function is doing what I wanted, but when I trying to make multiple fields with the same function, the components are just bind together.
I've tried to handle them with event.target but I think I messed it up so I deleted the lines.
What should I do so they can separated into two components working individually?
Here's my code:
import React from 'react';
import Checkbox from '#material-ui/core/Checkbox';
import TextField from '#material-ui/core/TextField';
export default function Checkboxes() {
const [required, setRequired] = React.useState(true);
const [checked, setChecked] = React.useState(false);
const [disabled, setDisabled] = React.useState(false);
const handleChange = event => {
setRequired(!required);
setChecked(!checked);
setDisabled(!disabled);
};
return (
<div>
<form>
<TextField
required={required}
autoComplete="off"
id="standard-required"
label="text1"
disabled={disabled}
/>
<Checkbox
label="cb1"
checked={checked}
onChange={handleChange}
color="primary"
/>
</form>
<form>
<TextField
required={required}
autoComplete="off"
id="standard-required"
label="text1"
disabled={disabled}
/>
<Checkbox
label="cb1"
checked={checked}
onChange={handleChange}
color="primary"
/>
</form>
</div>
);
}
Here's my sandbox:
https://stackblitz.com/edit/react-ts-3zvmp5?file=demo.tsx
No need to use state for this purpose. This can be done with manipulating the DOM easily. I have edited your code here in the above link. Just pass the id of the input element to the function and disable it using normal javascript.
I solved my problem by myself.
export default function Checkboxes() {
const [required, setRequired] = React.useState({ setA: true, setB: true });
const [checked, setChecked] = React.useState({ setA: false, setB: false });
const [disabled, setDisabled] = React.useState({ setA: false, setB: false });
return (
<div>
<form>
<TextField
required={required.setA}
autoComplete="off"
id="standard-required"
label="text1"
disabled={disabled.setA}
/>
<Checkbox
label="cb1"
checked={checked.setA}
onChange={() => {
setRequired({ ...required, setA: !required.setA });
setChecked({ ...checked, setA: !checked.setA });
setDisabled({ ...disabled, setA: !disabled.setA });
}}
color="primary"
/>
</form>
<form>
<TextField
required={required.setB}
autoComplete="off"
id="standard-required"
label="text2"
disabled={disabled.setB}
/>
<Checkbox
label="cb2"
checked={checked.setB}
onChange={() => {
setRequired({ ...required, setB: !required.setB });
setChecked({ ...checked, setB: !checked.setB });
setDisabled({ ...disabled, setB: !disabled.setB });
}}
color="primary"
/>
</form>
</div>
);
}
Sandbox here.
However, I decided to separate the hooks.
It's not a large component so I decided to make hooks function individually to avoid any issue might happen in the same hook.
export default function Checkboxes() {
const [requiredA, setRequiredA] = React.useState(true);
const [checkedA, setCheckedA] = React.useState(false);
const [disabledA, setDisabledA] = React.useState(false);
const [requiredB, setRequiredB] = React.useState(true);
const [checkedB, setCheckedB] = React.useState(false);
const [disabledB, setDisabledB] = React.useState(false);
const handleChangeA = () => {
setRequiredA(!requiredA);
setCheckedA(!checkedA);
setDisabledA(!disabledA);
};
const handleChangeB = () => {
setRequiredB(!requiredB);
setCheckedB(!checkedB);
setDisabledB(!disabledB);
};
return (
<div>
<form>
<TextField
required={requiredA}
autoComplete="off"
id="standard-required"
label="text1"
disabled={disabledA}
/>
<Checkbox
label="cb1"
checked={checkedA}
onChange={handleChangeA}
color="primary"
/>
</form>
<form>
<TextField
required={requiredB}
autoComplete="off"
id="standard-required"
label="text2"
disabled={disabledB}
/>
<Checkbox
label="cb2"
checked={checkedB}
onChange={handleChangeB}
color="primary"
/>
</form>
</div>
);
}
sandbox here.

Material UI Autocomplete component issue working with check

I have a Material UI autocomplete component with a checkbox component. How can I get both of them working such that only when a user selects an option from the autocomplete, the checkbox should get checked. Here is the link to my component:
https://codesandbox.io/embed/material-demo-forked-of2cz?codemirror=1
https://codesandbox.io/s/material-demo-forked-of2cz?from-embed=&file=/demo.tsx
i finally got my autocomplete/checkbox to work and be all checked by default (redux & ts)
const HIDDEN_USERS: HiddenUsersProps[] = store.getState().user.hiddenUsers
// array of objects
//{
// uid:"oKW8wDsasYdssdd1wTLggsas02"
// userName:"Matt"
//}
const [value, setValue] = useState<HiddenUsersProps[]>([])
//the only way i found to wait for the values from the redux store
useEffect(() => {
setValue(HIDDEN_USERS)
}, [HIDDEN_USERS])
const handleChange = (newValue: HiddenUsersProps[]) => {
setValue(newValue)
}
return (
<Autocomplete
options={HIDDEN_USERS}
value={value}
onChange={(_, newValue: HiddenUsersProps[]) => handleChange(newValue)}
getOptionLabel={(option: any) => option.userName}
isOptionEqualToValue={(option, value) => option.userName === value.userName}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox icon={icon}
checkedIcon={checkedIcon}
checked={selected} />
{option.userName}
</li>
)}
renderInput={(params) => (
<TextField {...params}
label="Hidden Users"
placeholder="Name"
variant="outlined" />)} />
)
"#mui/material": "^5.0.6",
You should change your component like this :
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import Checkbox from "#material-ui/core/Checkbox";
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState<string | null>("");
const [inputValue, setInputValue] = React.useState("");
const [checked, setChecked] = React.useState<boolean>(false);
const [text1, setText1] = React.useState("");
const [text2, setText2] = React.useState("");
const isTextFieldsNotEmpty = text1.length > 0;
const handleFirstTextChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setChecked(!checked)
setText1(event.target.value);
};
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : "null"}`}</div>
<div>{`inputValue: '${inputValue}' '${checked}' '${text1}'`}</div>
<br />
<Checkbox
checked={checked}
onChange={handleFirstTextChange}
inputProps={{ "aria-label": "primary checkbox" }}
/>
<Autocomplete
value={value}
onChange={(event: any, newValue: string | null) => {
setValue(newValue);
setChecked(!checked)
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
style={{ width: 300 }}
renderInput={(params) => (
<TextField
{...params}
onChange={handleFirstTextChange}
label="Controllable"
variant="outlined"
/>
)}
/>
</div>
);
}
I just have put the setChecked inside the same event listener than your textfield.
That would create the behaviors you search when the user select a field is checking the checkbox.

How to use a function on onClick instead of onChange?

i have an search function for searching in the table. Now. I want to use the search function on the onClick of the icon instead of the onChange of the input field. I don't think i need the throttle for that. I try to use the function setGlobalFilter directly inside the handleClick but it won't work
function GlobalFilter({ globalFilter, setGlobalFilter }) {
const [value, setValue] = React.useState(globalFilter)
const onChange = React.useCallback(
value => {
const throttledSetGlobalFilter = throttle(
value => {
setGlobalFilter(value || undefined)
},
2000
)
throttledSetGlobalFilter(value)
},
[setGlobalFilter]
)
return (
<span className={styles.componentGlobalFilter}>
<input
className={styles.input}
value={value || ''}
onChange={(e) => {
setValue(e.target.value)
onChange(e.target.value)
}}
placeholder={`Zoek in deze tabel`}
/>
<Icon onClick={handleClick} layoutClassName={styles.icon} {...{ icon }} />
</span>
)
function handleClick() {
}
}
If I understand your question, you want the icon click to invoke the setGlobalFilter callback with the input value. Remove the onChange handler and directly update value state. Call setGlobalFilter with the state value when the icon is clicked.
function GlobalFilter({ globalFilter, setGlobalFilter }) {
const [value, setValue] = useState(globalFilter ?? '');
const handleClick = () => setGlobalFilter(value);
return (
<span className={styles.componentGlobalFilter}>
<input
className={styles.input}
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder={`Zoek in deze tabel`}
/>
<Icon onClick={handleClick} layoutClassName={styles.icon} {...{ icon }} />
</span>
);
}
If you need to throttle the setGlobalFilter then the following may help.
function GlobalFilter({ globalFilter, setGlobalFilter }) {
const [value, setValue] = useState(globalFilter ?? '');
const handleClick = throttle(
() => setGlobalFilter(value),
2000,
);
return (
<span className={styles.componentGlobalFilter}>
<input
className={styles.input}
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder={`Zoek in deze tabel`}
/>
<Icon onClick={handleClick} layoutClassName={styles.icon} {...{ icon }} />
</span>
);
}

Resources