I am trying to populate an MUI select with choices from an API's JSON response. Currently, all Choices are being pushed into one MenuItem within the Select.
The Choices are likely to change in the future so I would like to avoid hard coding them.
How can I apply a .map to get the MenuItemsto display the JSON Choices separately rather than having them display on the same line.
Here is my JSON, and below is how I am displaying all other data.
{
"question_groups": [
{
"GroupName": "DDD",
"questions": [
{
"Question": "1. Do you want a Drink?",
"QuestionType": "Single Choice",
"Response": null,
"Choices": [
"Yes",
"No"
]
}
],
"SizingId": null
},
}
const SelectQuestion = ({ question }) => {
return (
<Box>
<TextField value={question?.Question || ""} />
<Select label="Question" >
<MenuItem value={question?.Choices || ""}>{question?.Choices || ""}</MenuItem>
</Select>
<Divider />
</Box>
);
};
const TextQuestion = ({ question }) => {
return (
<Box>
<TextField />
<TextField />
<Divider />
</Box>
);
};
const questionComps = questions["question_groups"]?.map((group, i) => {
return group["questions"]?.map((question, i) => {
return question["QuestionType"] === "Text" ? (
<TextQuestion key={`${i}${question.Question}`} question={question} />
) : (
<SelectQuestion key={`${i}${question.Question}`} question={question} />
);
});
});
You should change your SelectQuestion component by mapping through your "Choices" options and render MenuItem-s accordingly.
const SelectQuestion = ({ question }) => {
return (
<Box>
<TextField value={question?.Question || ""} />
<Select label="Question">
{question.Choices.map((choice) => (
<MenuItem key={choice} value={choice}>
{choice}
</MenuItem>
))}
</Select>
<Divider />
</Box>
);
};
Demo
Related
I'm trying to use arrays in Grommet DataTable. My data looks like this :
{
customer: [
'BANANA',
'Banana',
'banana',
'republic of banana'
],
somethingelse: ['ABC','123','DEF']
}
In a regular Grommet Table , I'm able to use every cell by defining the first value from the array as title - for example customer[0] - and create an expandable arrow to show the rest of the data in 'customer' :
But I don't get how to do this on a cell basis for a Grommet DataTable ?
Here is the way I'm using it in the regular Grommet Table :
<TableCell scope="row" pad={{ left: '2px', righ: '3px' }}>
<TextInput name="tags" size="xsmall" />
</TableCell>
</TableRow>
{searchResults.length > 0 &&
searchResults.map((searchResult, index) => (
<TableRow key={index}>
<TableCell>
<Box direction="row">
<Text size="xsmall">{searchResult.customer[0]}</Text>
{searchResult.customer.length > 1 && (
<Button
plain
hoverIndicator={false}
icon={
isExpanded[index] ? (
<FormDown size="18px" />
) : (
<FormNext size="18px" />
)
}
onClick={() => toggleOpen(index)}
/>
)}
</Box>
<Box>
{isExpanded[index] && listElements(searchResult.customer)}
</Box>
</TableCell>
Here is my Form , using DataTable :
return (
<Form value={formData} onSubmit={onSubmit} onChange={onChange}>
...
<DataTable
fill
border={{ body: 'bottom' }}
paginate
columns={columns}
data={searchResults}
select={select}
onClickRow={(e) => console.log(e.datum)}
onSelect={() => {}}
step={8}
rowDetails={(row) => { // I'm able to use rowDetails to expand and display some data , but how can I use this to 1. Use the [0] element of the array as title and 2. apply to all cells in the row/table.
for (const cell in row) {
// if (cell.length > 1) {
// return listElements(cell);
// }
console.log(cell);
}
}}
...
/>
...
</Form>
);
I was able to achieve that by using the render function and passing a CellElement to it, in which I have created my rules :
const columns = [
{
property: 'customer',
header: <FormField label="Customer" name="customer" size="xsmall" />,
render: (datum) => <CellElement val={datum.customer} />,
},
CellElement.js
import { Box, Text, Button } from 'grommet';
import { FormNext, FormDown } from 'grommet-icons';
import React, { useState } from 'react';
const CellElement = ({ val }) => {
const title = Array.isArray(val) ? val[0] : val;
const [isExpanded, setIsExpanded] = useState({});
const toggleOpen = (category) => {
setIsExpanded({
...isExpanded,
[category]: !isExpanded[category],
});
};
const listElements = (arr) => {
return arr.slice(1).map((el, index) => (
<Text key={index} size="xsmall">
{el}
</Text>
));
};
return (
<Box>
<Box direction="row">
<Text size="xsmall">{title}</Text>
{Array.isArray(val) && val.length > 1 && (
<Button
plain
hoverIndicator={false}
icon={
isExpanded[title] ? (
<FormDown size="18px" />
) : (
<FormNext size="18px" />
)
}
onClick={() => toggleOpen(title)}
/>
)}
</Box>
<Box>{isExpanded[title] && listElements(val)}</Box>
</Box>
);
};
export default CellElement;
I am adding data into the table after submitting so what my task is I need to check any item that adds to the table mean that is present in the table that that select name object will remove from Select Name I am trying to remove that list using filteredArray function check that code below but it not working properly so if that name is present in the table then that name will not show in select name TextFiled need to remove that already select and added in the table
export default function App() {
const nameList = [
{
selectName: "onee",
selectAge: "18",
location: "nagpur"
},
{
selectName: "two",
selectAge: "20",
location: "pune"
},
{
selectName: "three",
selectAge: "20",
location: "mumbai"
}
];
const [teamdata, setTeamData] = React.useState([]);
const AssignSearchesForm = useFormik({
initialValues: {
selectName: "",
selectAge: "",
location: ""
},
onSubmit: (values) => {
setTeamData([values, ...teamdata]);
}
});
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 || "");
};
let filteredArray = nameList.filter(
(e) => e.selectName !== teamdata.map((data) => data.selectName)
);
console.log("filteredArray", filteredArray);
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}
>
{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}>
<Button
onClick={() => {
AssignSearchesForm.handleSubmit();
}}
variant="contained"
>
Add
</Button>
</Grid>
</Grid>
<Table teamdata={teamdata} />
</div>
);
}
This should do what you're asking:
let filteredArray = nameList.filter(
(e) => !teamdata.some(data => data.selectName === e.selectName)
);
I have the following component for selecting roles:
export const MultipleSelectChip = ({
options,
label,
error,
onRolesUpdate,
}: Props) => {
const theme = useTheme();
const [selectedOptions, setSelectedOptions] = React.useState<string[]>([]);
const handleChipChange = (
event: SelectChangeEvent<typeof selectedOptions>,
) => {
const {
target: { value },
} = event;
setSelectedOptions(
// On autofill we get a the stringified value.
typeof value === 'string' ? value.split(',') : value,
);
};
return (
<div>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="multiple-chip-label">{label}</InputLabel>
<Select
required
labelId="multiple-chip-label"
error={error}
id="demo-multiple-chip"
multiple
value={selectedOptions}
onChange={handleChipChange}
input={<OutlinedInput id="select-multiple-chip" label={label} />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{options.map((propOption) => (
<MenuItem
key={propOption.id}
value={propOption.name}
style={getStyles(propOption, selectedOptions, theme)}
>
{propOption.name}
</MenuItem>
))}
</Select>
<FormHelperText>Here's my helper text</FormHelperText>
</FormControl>
</div>
);
};
For options I have an array of objects with id and name, the thing is that I want to use the names for displaying the chips and the ids to pass them to the parent component for the add request. I don't know how to get de ids, too.
This is the example: https://codesandbox.io/s/6ry5y?file=/demo.tsx but is using an array of strings instead of an array of objects.
This is how 'options' looks like:
const rolesDummy: Role[] = [
{ id: '61fb0f25-34aa-46c6-8683-093254223dcd', name: 'HR' },
{ id: '949b9b1e-d3f8-45cb-a061-08da483bd486', name: 'Interviewer' },
{ id: 'c09ae2d4-1335-4ef0-8d4b-ee9529796b52', name: 'Hiring Manager' },
];
And I need to get back only the selected ids
Thank you!
If you pass the option as an object, you can render each MenuItem with the option.id as a key and the option.name as the label. The MenuItem is identified by an id:
<Select {...}>
{options.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
To display the name in the selected Chip. Use renderValue, but it only provides you the selected values (array of option.id), so you need to find the option to get the name:
renderValue={(selected) => {
return (
<Box>
{selected.map((value) => {
const option = options.find((o) => o.id === value);
return <Chip key={value} label={option.name} />;
})}
</Box>
);
}}
Now you can get an array of selected ids by adding a change handler:
onChange={e => console.log(e.target.value)}
MATERUAL UI Autocomplete component works fine, but I want to get object.id as onSelect event value (event.target.value), not the object.name. In other words, I want to display object.name as select item labels, but I want to get object.id as onSelect event value (event.target.value). Right now, my event.target.value is the same as select item label (object.name). Here is an example (from the Material UI documentation):
The options object is like this:
const options = [
{ id: "01", name: "Peter" },
{ id: "02", name: "Mary },
{ id: "03", name: "John" }
]
And the Autocomplete is the same like in Material UI documentation:
<Autocomplete
id="asynchronous-demo"
fullWidth
open={open}
onOpen={() => {
setOpen(true)
}}
onClose={() => {
setOpen(false)
}}
getOptionLabel={(option) => option.name}
options={options}
loading={loading}
renderInput={(params) => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
onChange={(event) => {
if (event.target.value !== '' || event.target.value !== null) {
onChangeHandle(event.target.value)
}
}}
onSelect={(event) => {
onSelectHandle(event)
}}
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? (
<CircularProgress color="inherit" size={20} />
) : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
With onSelect I always get object.name as event.target.value, but I want to return object.id as event.target.value.
Does anybody knows how??
You are currently grabbing the value from TextField's onSelect, instead of Autocomplete's onChange.
<Autocomplete
...
onChange={(event, newValue) => {
onSelectHandle(newValue)
}}
renderInput={(params) => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
onChange={(event) => {
if (event.target.value !== '' || event.target.value !== null) {
onChangeHandle(event.target.value)
}
}}
...
/>
)}
/>
For more info, check out the controllable states section in the Autocomplete documentation.
I am trying to create an array of objects from a child component form having same fields.
I am using react hooks to store the state and using for loop in JSX repeated the child component.
Flow of the App => Parent --> 2 same Child but different input --> create object array --> return to parent array of objects
UI
Image of the form UI where Delivery Contact is same Child Component
Parent Code
{[...Array(2)].map((e, i) => {
const deliveryContactDetails = deliveryContact => {
// deliveryRef[i].current.sendData()
console.log(deliveryContact);
}
return (
<React.Fragment>
<Grid item xs={12}>
<FormLabel component="legend">
Delivery Contact Person : ({i+1})
</FormLabel>
</Grid>
<DeliveryContact formNo={i+1} ref={deliveryRef[i] = useRef()} saveDelivery={deliveryContactDetails} ></DeliveryContact>
</React.Fragment>
)
})}
</React.Fragment>
Child Code
const formNo = props.formNo;
const [values, setValues] = useState({...contactObj})
const handleInputChange = ({ target }) => {
const { name, value } = target;
setValues({...values, [name]: value})
};
useImperativeHandle(ref,() => ({
sendData() {
props.saveDelivery(values);
}
}));
return(
<React.Fragment>
<Grid item xs={12} sm={6}>
<TextField
select
fullWidth
margin="dense"
id="salutation"
name={"salutation" + formNo}
fullWidth
onChange={handleInputChange}
value={values.salutation}
variant="outlined"
label="Salutation"
>
<MenuItem value={`Mr.`}>Mr.</MenuItem>
<MenuItem value={`Ms.`}>Ms.</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
id="deliveryContactName"
name={"name" + formNo}
label="Delivery Contact Name"
fullWidth
variant="outlined"
margin="dense"
onChange={handleInputChange}
value={values.deliveryContactName}
inputProps={{
maxLength: 40
}}
/>
</Grid>
)
Required Object
"contacts" : [
{
"name": "asdasdas123da",
"phone" : 9898989898,
"email": "test#mailinator.com",
"designation" : "Some Manager" ,
"salutation" : "Mr"
},
{
"name": "asdasdas",
"phone" : 9898989898,
"email": "test#mailinator.com",
"designation" : "Some Manager" ,
"salutation" : "Mr"
}
]