The following component should always only have at most one of the two checkboxes checked. Although the state checkedList always only contains at most one value the UI doesn't reflect this state.
What am I missing?
interface TeilnehmerProps {
rolle: Teilnehmerrolle,
teilnehmer: Teilnehmer,
readonly: boolean
}
export const TeilnehmerCard = (props: TeilnehmerProps) => {
const { rolle, teilnehmer, readonly } = props;
const [name, setName] = useState<string>(teilnehmer?.name);
const [checkedList, setCheckedList] = useState<CheckboxValueType[]>([teilnehmer?.abwesenheit]);
const handleChecked = (list: CheckboxValueType[]) => {
const set = new Set(list);
if (set.size === 2) {
set.delete(checkedList[0]);
setCheckedList(Array.from(set));
} else if (set.size === 1) {
setCheckedList(Array.from(set));
} else {
setCheckedList([]);
}
};
useEffect(() => {
console.log(checkedList);
}, [checkedList]);
return <Card title="" size="small" className={styles.teilnehmerCard}>
<Row key={teilnehmer?.rolle}>
<Col lg={4}>
<Form.Item name={`${teilnehmer?.rolle}.abwesenheit`}
initialValue={[teilnehmer?.abwesenheit]}>
<Checkbox.Group value={checkedList}
disabled={readonly}
options={[{ value: 'ENTSCHULDIGT', label: '' }, { value: 'UNENTSCHULDIGT', label: '' }]}
onChange={handleChecked}>
</Checkbox.Group>
</Form.Item>
</Col>
</Row>
</Card>;
};
Once wrapped in <Form.Item/> one cannot and shouldn't control the form values anymore, but use form.setFieldsValue().
Related
RankPermission.value in switchPermission function is changing from false to true, but MUI Switch is not updating in the browser. I don't know why isn't it updating, and I didn't try much. I don't have any ideas how can I fix it.
const [activeRank, setActiveRank] = useState<FactionRanks>();
export type FactionRanks = {
id: number;
name: string;
rankPermissions: FactionRanksPermissions[];
};
export type FactionRanksPermissions = {
label: string;
value: boolean;
};
const ActionMenu = () => {
const { activeRank } = useContext(FactionPanelContext);
const switchPermission = (rankPermission: FactionRanksPermissions) => {
rankPermission.value = !rankPermission.value;
console.log(rankPermission);
};
return (
<Wrapper>
<Buttons>
{activeRank?.rankPermissions.map(
(rankPermission: FactionRanksPermissions, index: number) => (
<Row key={index}>
<OptionDetails>{rankPermission.label}</OptionDetails>
<IOSSwitch
checked={rankPermission.value}
inputProps={{ 'aria-label': 'secondary checkbox' }}
onClick={() => switchPermission(rankPermission)}
/>
</Row>
)
)}
</Buttons>
</Wrapper>
);
};
You are changing rankPermission in place here
rankPermission.value = !rankPermission.value;
You should be setting state, with a new object of rankPermission, where it is defined instead.
I want the users to be able to select multiple tags while also allowing them to add a tag if it does not exist, the examples on the material UI documentation work on the freeSolo option which works on string / object values as options whereas when we use multiple, that changes to an array
How do I implement a multiple creatable with material-ui?
My code:
// Fetch Adding tag list
const [listOpen, setListOpen] = useState(false);
const [options, setOptions] = useState<Tag[]>([]);
const loadingTags = listOpen && options.length === 0;
useEffect(() => {
let active = true;
if (!loadingTags) {
return undefined;
}
(async () => {
try {
const response = await getAllTagsForUser();
if (active) {
setOptions(response.data);
}
} catch (error) {
console.log(error);
}
})();
return () => {
active = false;
};
}, [loadingTags]);
useEffect(() => {
if (!listOpen) {
setOptions([]);
}
}, [listOpen]);
<Autocomplete
multiple
id="tags"
open={listOpen}
onOpen={() => {
setListOpen(true);
}}
onClose={() => {
setListOpen(false);
}}
options={options}
disableCloseOnSelect
getOptionLabel={(option) => option?.name || ""}
defaultValue={
contact?.tags?.map((element) => {
return { name: element };
}) || undefined
}
renderOption={(option, { selected }) => (
<React.Fragment>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.name}
</React.Fragment>
)}
style={{ width: 500 }}
renderInput={(params) => (
<TextField {...params} variant="outlined" label="Tags" />
)}
/>;
This is just fetching tags from the server and showing them as options, I understand that to be able to allow adding more, I would need to add filterOptions and onChange but, can someone please provide an example on how to deal with array there?
I know this isn't an quick answer but may someone else could use it. Found this Question buy searching an solution. Didn't find one so I tryed myself and this is what I Created and seems it works.
Based on the original Docs https://mui.com/components/autocomplete/#creatable
Complete example:
import React, { useEffect, useState } from "react";
//Components
import TextField from "#mui/material/TextField";
import Autocomplete, { createFilterOptions } from "#mui/material/Autocomplete";
//Icons
const filter = createFilterOptions();
export default function AutocompleteTagsCreate() {
const [selected, setSelected] = useState([])
const [options, setOptions] = useState([]);
useEffect(() => {
setOptions(data);
}, [])
return (
<Autocomplete
value={selected}
multiple
onChange={(event, newValue, reason, details) => {
let valueList = selected;
if (details.option.create && reason !== 'removeOption') {
valueList.push({ id: undefined, name: details.option.name, create: details.option.create });
setSelected(valueList);
}
else {
setSelected(newValue);
}
}}
filterSelectedOptions
filterOptions={(options, params) => {
const filtered = filter(options, params);
const { inputValue } = params;
// Suggest the creation of a new value
const isExisting = options.some((option) => inputValue === option.name);
if (inputValue !== '' && !isExisting) {
filtered.push({
name: inputValue,
label: `Add "${inputValue}"`,
create: true
});
}
return filtered;
}}
selectOnFocus
clearOnBlur
handleHomeEndKeys
id="tags-Create"
options={options}
getOptionLabel={(option) => {
// Value selected with enter, right from the input
if (typeof option === 'string') {
return option;
}
// Add "xxx" option created dynamically
if (option.label) {
return option.name;
}
// Regular option
return option.name;
}}
renderOption={(props, option) => <li {...props}>{option.create ? option.label : option.name}</li>}
freeSolo
renderInput={(params) => (
<TextField {...params} label="Tags" />
)}
/>
);
}
const data = [
{
id: 1,
name: 'Tag1'
},
{
id: 2,
name: 'Tag2'
},
{
id: 3,
name: 'Tag3'
},
{
id: 4,
name: 'Tag4'
},
]
I am a beginner with javscript So i will be thankful for explanation.
{isolate_list.map((row) => {
return (
<FormControlLabel
control={
<Checkbox
color="primary"
checked={!!checked}
onChange={toggleCheckbox}
name="checkedA"
>
{" "}
</Checkbox>
}
label={row.isolatename}
>
{""}
</FormControlLabel>
);
})}
and i have this button
<Button
onClick={selectall}
style={{ margin: 50 }}
variant="outlined"
label="SELECT ALL ISOLATES"
>
SELECT ALL ISOLATES
</Button>
Can anyone help how can i use the button to select all checkboxes and in the same time i can select every checkbox alone by clicking on it?
I beginn with this part but i am not sure
const [checked, setChecked] = React.useState(true);
const toggleCheckbox = (event) => {
setChecked(event.target.checked);
};
You should hold checkbox value's in the and give the state value as a property to each. For example
<Checkbox
color="primary"
onChange={toggleCheckbox}
name="checkedA"
value={checked}
>
And then in the onClick function
setChecked();
The simplest implementations(without any form manager):
Declare state to store our checked ids array.
const [checkedIds, setCheckedIds] = useState([]);
implement handler.
const handleCheck = useCallback((id) => {
return () => {
setCheckedIds(prevIds => prevIds.includes(id) ? prevIds.filter(item => item !== id) : [...prevIds, id]);
};
}, []);
render our checkboxes and apply handler.
list.map(({ id, isolatename }) => (
<FormControlLabel
key={id}
control={
<Checkbox
color="primary"
checked={checkedIds.includes(id)}
onChange={handleCheck(id)}
name={`checkbox_${id}`}
/>
}
label={isolatename}
/>)
))
ps. in case if <Checkbox/> props 'onChange' returns callback like this (isChecked: boolean) => {} we can simplify (2) step.
const handleCheck = useCallback(id => {
return isChecked => {
setCheckedIds(prevIds => isChecked ? prevIds.filter(item => item == id) : [...prevIds, id]);
};
}, []);
You may remember that it is React JS and not only JS that we are talking about.
In React you want to control data in the way of a state. There are a lot of ways to do so with check boxes, I'm contributing with one that you can see in the code snippet below:
import React, {useState} from "react";
export default function CheckBoxesControllers() {
const [checkboxes, setCheckboxes] = useState(() => [
{ id: "0", checked: false },
{ id: "1", checked: false },
{ id: "2", checked: false },
]);
const handleUpdate = (event) => {
const { target: {id, checked} } = event;
setCheckboxes(currentState => {
const notToBeUpdated = currentState.filter(input => input.id !== id);
return [
...notToBeUpdated,
{ id, checked }
]
});
}
function toggleSelectAll() {
setCheckboxes(currentState => currentState.map(checkbox => ({...checkbox, checked: !checkbox.checked})));
}
return (
<>
{checkboxes?.length ? (
checkboxes.map((checkbox, index) => {
return (
<input
checked={checkbox.checked}
id={checkbox.id}
key={index}
type="checkbox"
onChange={event => handleUpdate(event)}
/>
);
})
) : <></>}
<button onClick={toggleSelectAll}>Toggle Select All</button>
</>
)
}
This code is meant to serve you as an example of how to work properly with react state in the hook way, but there are other way, as you can see in the Documentation
Lately i discovered the react-hook-from plugin which on the first sight seem to be perfect to start using and replace other plugins due to its great performance.
After using the plugin for some really simple forms i came across a complicated form that i wish to handle with the plugin. My form is based on a nested object which has the following structure. (Typescript definition)
type model = {
tag: string;
visible: boolean;
columns?: (modelColumn | model)[];
}
type modelColumn = {
property: string;
}
So in order to handle to handle a n-level nested form i created the following components.
const initialData = {
...
datasource: {
tag: "tag1",
visible: true,
columns: [
{
property: "property",
},
{
property: "property1",
},
{
tag: "tag2",
visible: true,
columns: [
{
property: "property",
},
{
tag: "tag3",
visible: false,
columns: [
{
property: "property",
}
]
}
]
},
{
entity: "tag4",
visible: false,
}
],
},
...
}
export const EditorContent: React.FunctionComponent<EditorContentProps> = (props: any) => {
const form = useForm({
mode: 'all',
defaultValues: initialData,
});
return (
<FormProvider {...form}>
<form>
<Handler
path="datasource"
/>
</form>
</FormProvider>
);
}
In the above component the main form is created loaded with initial data. (I provided an example value). The Handler component has the login of recursion with the path property to call nested logic of the form data type. Here is the sample implementation.
...
import { get, isNil } from "lodash";
import { useFieldArray, useForm, useFormContext, useWatch } from "react-hook-form";
...
export type HandlerProps = {
path: string;
index?: number;
...
}
export const Handler: React.FunctionComponent<HandlerProps> = (props) => {
const { path, index, onDelete, ...rest } = props;
const { control } = useFormContext();
const name = isNil(index)? `${path}` :`${path}[${index}]`;
const { fields, append, insert, remove } = useFieldArray({
control: control,
name: `${name}.columns`
});
...
const value = useWatch({
control,
name: `${name}`,
});
...
const addHandler = () => {
append({ property: null });
};
console.log(`Render path ${name}`);
return (
<React.Fragment>
<Row>
<Col>
<FormField name={`${name}.tag`} defaultValue={value.tag}>
<Input
label={`Tag`}
/>
</FormField>
</Col>
<Col>
<FormField defaultValue={value.visible} name={`${name}.visible`} >
<Switch />
</FormField>
</Col>
<Col>
<button onClick={addHandler}>Add Column Property</button>
</Col>
</Row>
{
fields && (
fields.map((field: any, _index: number) => {
if (field.property !== undefined) {
return (
<Column
path={`${name}.columns`}
index={_index}
control={control}
fields={fields}
onDelete={() => remove(_index) }
/>
)
} else {
return (
<Handler
path={`${name}.columns`}
index={_index}
/>
)
}
})
)
}
</React.Fragment>
);
}
Essentially the Handler Component uses the form's context and call itself if it should render a nested object of the form like datasource.columns[x] which the register to useFieldArray to get it's columns. Everything so far works fine. I render the complete tree (if i can say) like object form correctly.
For reference here is the code of the Column Component as also for the formField helper component FormField.
export const Column: React.FunctionComponent<ColumnProps> = (props) => {
const { fields, control, path, index, onDelete } = props;
const value = useWatch({
control,
name: `${path}[${index}]`,
defaultValue: !isNil(fields[index])? fields[index]: {
property: null,
}
});
console.log(`Render of Column ${path} ${value.property}`);
return (
<Row>
<Col>
<button onClick={onDelete}>Remove Property</button>
</Col>
<Col>
<FormField name={`${path}[${index}].property`} defaultValue={value.property}>
<Input
label={`Column Property name`}
/>
</FormField>
</Col>
</Row>
);
}
export type FormFieldProps = {
name: string;
disabled?: boolean;
validation?: any;
defaultValue?: any;
children?: any;
};
export const FormField: React.FunctionComponent<FormFieldProps> = (props) => {
const {
children,
name,
validation,
defaultValue,
disabled,
} = props;
const { errors, control, setValue } = useFormContext();
return (
<Controller
name={name}
control={control}
defaultValue={defaultValue? defaultValue: null}
rules={{ required: true }}
render={props => {
return (
React.cloneElement(children, {
...children.props,
...{
disabled: disabled || children.props.disabled,
value: props.value,
onChange: (v:any) => {
props.onChange(v);
setValue(name, v, { shouldValidate: true });
},
}
})
);
}}
/>
);
}
The problem is when i remove a field from the array. The Handler component re-renders having correct values on the fields data. But the values of useWatch have the initial data which leads to a wrong render with wrong fields being displayed an the forms starts to mesh up.
Is there any advice on how to render such a nested form the correct way. I guess this is not an issue from the react-hook-form but the implementation has an error which seems to cause the problem.
How can I toggle a checkbox in a map when I click on it?
So only set to true or false which one was clicked.
Then I would like to either pack the value of the checkbox, in this case the index, into an array or delete it from it.
const handleChange = (event) => {
const copy = teilen;
setChecked(event.target.checked);
if (event.target.checked)
{
setTeilen(teilen, event.target.value);
} else {
copy.splice(event.target.value, 1);
setTeilen(copy);
}
console.log(teilen);
};
[...]
pdfs.map((p, index) => (
[...]
<Checkbox
value={p._id}
index={p._id}
checked={checked}
onChange={handleChange}
inputProps={{ "aria-label": "primary checkbox" }}
/>
[...]
))}
in your case you can try something like this:
const checkboxes = [
{
name:"a",
value:"a",
checked:false
},
{
name:"b",
value:"b",
checked:false
},
{
name:"c",
value:"c",
checked:false
},
];
const checkedArray = [];
const handleChange = (e) => {
if(e.target.checked){
checkedArray.push(item.value);
checkboxes[i].checked = true;
} else {
if(checkedArray.contains(item.value)){
checkedArray.filter(arrItem => arrItem === item.value);
}
checkboxes[i].checked = false;
}
const render = () => {
return checkboxes.map((item,i) => (
<input type="checkbox"
value={item.value}
name={item.value}
checked={item.checked}
onChange={handleChange}
/>
))
}
and you must keep the data in state to make component re-render.