I've got this .map to map a list of checkboxes.
I'd like to map the setState as well, and make in sort it dynamically points out the value binded to each column object:
const [activeColumns, setActiveColumns] = useState<any>({
creationDate: false,
destination: false,
})
const columns = [
{ label: 'Creation_date', value: "creationDate" },
{ label: "Destination", value: "destination" }
]
return (
// ...
{
columns.map((column: any, index: number) => (
<MenuItem onClick={() => setActiveColumns({...activeColumns, [column.value]: !activeColumns[column.value]})}>
<FormGroup>
<FormControlLabel control={<Checkbox checked={activeColumns[column.value]}/>} label={column.label} />
</FormGroup>
</MenuItem>
))
})
I don't know how to dynamically point to activeColmumns keys from columns values.
I'm using React TS and MUI
Your code is working as intended, just wrap your expression with JSX fragments or any parent element
import React, { useState } from "react";
import FormGroup from "#mui/material/FormGroup";
import FormControlLabel from "#mui/material/FormControlLabel";
import Checkbox from "#mui/material/Checkbox";
import { MenuItem } from "#mui/material";
export default function CheckboxLabels() {
const [activeColumns, setActiveColumns] = useState<any>({
creationDate: false,
destination: false
});
const columns = [
{ label: "Creation_date", value: "creationDate" },
{ label: "Destination", value: "destination" }
];
return (
// ...
<>
{columns.map((column: any, index: number) => (
<MenuItem
key={column.value}
onClick={() =>
setActiveColumns({
...activeColumns,
[column.value]: !activeColumns[column.value]
})
}
>
<FormGroup>
<FormControlLabel
control={<Checkbox checked={activeColumns[column.value]} />}
label={column.label}
/>
</FormGroup>
</MenuItem>
))}
</>
);
}
Related
what i am creating here is a sorter with 3 inputs like shown here . This sorter will get some data from a table. Now i'm setting state with initSelect and i'm passing it the fields array but when i console.log(select) it gives me the object shown in the image which is incorrect from the behaviours i want {sorterParam1: 'Date', sorterParam2: '', sorterParam3: ''}
The first input has to have a default value of 'Date' always , but it can change to other values in the dropdown list like name , username ect . When i console log the select state it is messed up as it's always selecting the last one on the fields array , how can i change the initSelects function to correctly build the object i want.
Also the tricky thing which i can't seem to do is , if this Date value is selcted , in the second input, the date value should not be there. And if in the second input we select another value like Earth , Earth and Date should not be in the 3rd input and so on. So basically it means filtering out values . I need serious help as this is for the company i work on
Excepted Behaviour: Dynamically update value every time i select one input element like
{sorterParam1: 'Date', sorterParam2: '', sorterParam3: ''}
When selectin 'Date' for example , it shouldn't not be included in the dropdownlist on sorterParam2, sorterParam3.
/*eslint-disable*/
import React, { useState, useMemo } from 'react';
import TextField from '#mui/material/TextField';
import Button from '#mui/material/Button';
import { GridSortModel } from '#mui/x-data-grid';
import SorterField from './SorterField';
const initSelects = (fields) => {
let object = {};
fields.map((item) => {
console.log(item, 'item');
object = {
...item,
[item.name]: item.defaultValue ? item.defaultValue : '',
};
});
return object;
};
const Sorter = ({ menuItemsValue, setSortData }: SortProps) => {
const fields: SorterProps[] = [
{
name: 'sorterParam1',
title: 'Sort by',
optional: false,
defaultValue: 'Date',
},
{
name: 'sorterParam2',
title: 'Then by',
optional: true,
},
{
name: 'sorterParam3',
title: 'Then by',
optional: true,
},
];
const [select, setSelect] = useState<any>(() => initSelects(fields));
const getMenuItems = useMemo(() => {
return menuItemsValue.filter((item) => select.sorterParam1 !== item);
}, [menuItemsValue, select]);
const handleSelectChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
console.log(111, name, value);
setSelect({ ...select, [name]: value });
setSortData(sortOptions);
};
const handleClearAllInputs = () => {
setSelect({
sorterParam1: '',
sorterParam2: '',
sorterParam3: '',
});
};
const handleConfirm = () => {};
return (
<TextField
label="Advanced Sorting"
className={styles.sorter__inputs}
id="sorter-parameter-1"
variant="standard"
InputProps={{
disableUnderline: true,
}}
select
SelectProps={{
IconComponent: (props) => <NewIcon {...props} />,
}}
sx={{
fontSize: '12px',
width: '100%',
'& .MuiInputBase-input:focus': {
backgroundColor: 'transparent !important',
},
'& .MuiInputLabel-root': {
color: '#9E9E9E',
},
'& .MuiTextField-root': {
fontSize: '13px',
},
'& .MuiOutlinedInput-root': {
backgroundColor: '#fff',
},
}}
>
{fields.map((option, index) => (
<SorterField
key={option.name}
menuItemsValue={getMenuItems}
name={option.name}
option={option}
count={fields.length}
handleChange={handleSelectChange}
index={index + 1} // setData={setData}
/>
))}
<div className={styles.sorter__inputControllers}>
<Button
className={styles.sorter__clearAllInput}
onClick={() => handleClearAllInputs()}
>
Clear All
</Button>
<Button
onClick={() => handleConfirm()}
className={styles.sorter__confirmInput}
>
Confirm
</Button>
</div>
</TextField>
);
};
export default Sorter;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This is the SorterField Component code if that might be helpful
/*eslint-disable*/
import React, { useState } from 'react';
import TextField from '#mui/material/TextField';
import { MenuItem } from '#mui/material';
import { SorterProps } from '../../types/Sorter';
import { ReactComponent as SorterLine } from '../../assets/img/sortLine.svg';
import styles from '../../assets/components/Sorter/sorter.module.scss';
type SorterFieldProps = {
menuItemsValue: string[];
option: SorterProps;
count: number;
name: string;
handleChange: (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => void;
index: number;
};
function SorterField({
option,
count,
menuItemsValue,
handleChange,
index,
}: SorterFieldProps) {
const handleSorting = () => {};
return (
<div className={styles.sorter__container}>
<div className={styles.sorter__header}>
<p className={styles.sorter__label}>
{option.title}{' '}
{option.optional && (
<sup className={styles.sorter__optional}>*Optional</sup>
)}
</p>
<div className={styles.sorter__numbers__container}>
{Array.from({ length: count }, (_, i) => i + 1).map((number) => (
<>
{number === index ? (
<>
<span className={styles.sorter__number}>{index}</span>
</>
) : (
<>
<span className={styles.sorter__numbers}>{number}</span>
</>
)}
</>
))}
</div>
</div>
<div className={styles.sorter__inputs}>
<TextField
className={[styles.sorter__input, styles.sorter__field__input].join(
' '
)}
variant="outlined"
label="Select"
select
SelectProps={{
IconComponent: () => <NewIcon />,
}}
value={option.defaultValue}
onChange={handleChange}
name={option.title}
size="small"
>
{menuItemsValue.map((title, idx) => (
<MenuItem key={idx} value={title}>
{title}
</MenuItem>
))}
</TextField>
<div onClick={handleSorting} className={styles.sorter__sortOrder}>
<div className={styles.sorter__sortOrder__alphabetical}>
<span>A</span>
<span>Z</span>
</div>
<div className={styles.sorter__sortOrder__sortLine}>
<SorterLine />
</div>
</div>
</div>
</div>
);
}
export default SorterField;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You're re-assigning a value to object in each iteration of fields.map().
If you want to build object as a map of field item name to defaultValue (or blank string), use this instead...
return Object.fromEntries(
fields.map(({ name, defaultValue }) => [name, defaultValue ?? ""])
);
See Object.fromEntries()
Also, move the fields declaration outside your component. It's static content so can be omitted from being declared every render and from any hook dependencies.
You could also use fields.reduce() which is basically the same thing
return fields.reduce(
(obj, { name, defaultValue }) => ({
...obj,
[name]: defaultValue ?? "",
}),
{}
);
As for removing selected options as you iterate through the fields, that's a little trickier.
You could use a memo hook to create an iterable data structure that includes the available options for that particular iteration.
For example
const fieldsWithOptions = useMemo(() => {
const taken = new Set(Object.values(select));
const available = menuItemsValue.filter((item) => !taken.has(item));
return fields.map((field) => ({
...field,
options: select[field.name]
? [select[field.name], ...available] // include the current selection
: available,
}));
}, [menuItemsValue, select]);
Then map over fieldsWithOptions instead of fields and use option.options instead of getMenuItems.
im having a slight problem with my react code, im trying to create a simple react appplication that has seperated components, as im still currently learning. Can someone look at this code and let me know whats going wrong? My dropdown component when added makes the browser load forever, so its something to do with that component, as when removed from app.js, it loads fine
import * as React from "react";
function Dropdown() {
const [food, setFood] = React.useState("fruit");
const [drink, setDrink] = React.useState("water");
const handleFoodChange = (event) => {
setFood(event.target.value);
};
const handleDrinkChange = (event) => {
setDrink(event.target.value);
};
return (
<div>
<Dropdown
label="What do we eat?"
options={[
{ label: "Fruit", value: "fruit" },
{ label: "Vegetable", value: "vegetable" },
{ label: "Meat", value: "meat" },
]}
value={food}
onChange={handleFoodChange}
/>
<Dropdown
label="What do we drink?"
options={[
{ label: "Water", value: "water" },
{ label: "Beer", value: "beer" },
{ label: "Wine", value: "wine" },
]}
value={drink}
onChange={handleDrinkChange}
/>
<p>We eat {food}!</p>
<p>We drink {drink}!</p>
</div>
);
}
export default Dropdown;
Below is how its being imported
import "./App.css";
import Checkbox from "./components/Checkbox";
import Dropdown from "./components/Dropdown";
function App() {
return (
<div>
<Checkbox />
<Dropdown />
<h1>test</h1>
</div>
);
}
export default App;
This is happening because you are importing Dropdown inside Dropdown component.
I updated your code to show you another way to create a Dropdown.
function DropdownContainer() {
const [food, setFood] = React.useState("fruit");
const [drink, setDrink] = React.useState("water");
const handleFoodChange = (event) => {
setFood(event.target.value);
};
const handleDrinkChange = (event) => {
setDrink(event.target.value);
};
return (
<div>
<Dropdown
label="What do we eat?"
selectedOption={food}
options={[
{ label: "Fruit", value: "fruit" },
{ label: "Vegetable", value: "vegetable" },
{ label: "Meat", value: "meat" },
]}
onHandleChange={handleFoodChange}
/>
<Dropdown
label="What do we drink?"
selectedOption={drink}
options={[
{ label: "Water", value: "water" },
{ label: "Beer", value: "beer" },
{ label: "Wine", value: "wine" },
]}
onHandleChange={handleDrinkChange}
/>
<p>We eat {food}!</p>
<p>We drink {drink}!</p>
</div>
);
}
function Dropdown({ selectedOption = "", label = "", options = [], onHandleChange }) {
return (
<select
label={label}
onChange={onHandleChange}
>
{options.map(opt => <option selected={selectedOption === opt.value} value={opt.value}>{opt.label}</option>)}
</ select>
);
}
export function App(props) {
return (
<DropdownContainer />
);
}
Why do you put <Dropdown ...> inside Dropdown function? It causes recursive call and results in infinite loading.
You should use <select ..> like below.
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
I have been looking for a reliable component with a clear API documentation that would allow me to display a "tree view" structure for a select input as part of a form. The closest I have came across is vue-treeselect with many supported features such as: disabling branch nodes, disable item selection and more; the issue is that it's only available on Vue JS. My project is using Material UI as its design system, any component that supports it would be very great. Thanks
I needed a to deal with tree data in a project as well. I ended up creating MUI Tree Select.
You can demo it in this sandbox.
I searched a lot for that in the end I made this by myself sandbox.
you can choose to parent or child with this and you can custom it easily.
import { ThemeProvider, createTheme } from "#mui/material/styles";
import React, { useState } from "react";
import ReactDOM from "react-dom";
import TreeItem from "#mui/lab/TreeItem";
import { Popover, TextField, Typography } from "#mui/material";
import clsx from "clsx";
import { TreeView, useTreeItem } from "#mui/lab";
import ExpandMoreIcon from "#mui/icons-material/ExpandMore";
import ChevronRightIcon from "#mui/icons-material/ChevronRight";
import { useMediaQuery } from "#mui/material";
const data = [
{
id: "root",
name: "Parent",
children: [
{
id: "1",
name: "Child - 1"
},
{
id: "3",
name: "Child - 3",
children: [
{
id: "4",
name: "Child - 4"
}
]
}
]
},
{
id: "1root",
name: "Parent1",
children: [
{
id: "5",
name: "Child - 1-1"
},
{
id: "7",
name: "Child - 3-1",
children: [
{
id: "8",
name: "Child - 4-1"
}
]
}
]
}
];
const CustomContent = React.forwardRef(function CustomContent(props, ref) {
const {
classes,
className,
label,
nodeId,
icon: iconProp,
expansionIcon,
displayIcon
} = props;
const {
disabled,
expanded,
selected,
focused,
handleExpansion,
handleSelection,
preventSelection
} = useTreeItem(nodeId);
const icon = iconProp || expansionIcon || displayIcon;
const handleMouseDown = (event) => {
preventSelection(event);
};
const handleExpansionClick = (event) => {
handleExpansion(event);
};
const handleSelectionClick = (event) => {
handleSelection(event);
};
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
className={clsx(className, classes.root, {
[classes.expanded]: expanded,
[classes.selected]: selected,
[classes.focused]: focused,
[classes.disabled]: disabled
})}
onMouseDown={handleMouseDown}
ref={ref}
style={{ padding: "3px 0" }}
>
<div onClick={handleExpansionClick} className={classes.iconContainer}>
{icon}
</div>
<Typography
onClick={handleSelectionClick}
component="div"
className={classes.label}
>
{label}
</Typography>
</div>
);
});
const CustomTreeItem = (props) => (
<TreeItem ContentComponent={CustomContent} {...props} />
);
export default function RichObjectTreeView({ formik, edit }) {
const [anchorEl, setAnchorEl] = React.useState(null);
const [equipmentItem, setEquipmentItem] = useState("");
const [equipmentId, setEquipmentId] = useState("");
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
const renderTree = (nodes) => (
<CustomTreeItem key={nodes.id} nodeId={nodes.id} label={nodes.name}>
{Array.isArray(nodes.children)
? nodes.children.map((node) => renderTree(node))
: null}
</CustomTreeItem>
);
return (
<>
<TextField
variant="standard"
required={false}
label="Equipment Item"
name="equipmentItem"
id="equipmentItem"
defaultValue={equipmentItem}
value={equipmentItem}
className="w-100"
inputProps={{ readOnly: !edit }}
onClick={handleClick}
/>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
>
<TreeView
aria-label="icon expansion"
defaultSelected={equipmentId}
selected={equipmentId}
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
onNodeSelect={(e, id) => {
setEquipmentId(id);
setEquipmentItem(e.target.innerText);
}}
sx={{
height: 200,
flexGrow: 1,
minWidth: "200px",
overflowY: "auto"
}}
>
{data.map((item, i) => renderTree(item))}
</TreeView>
</Popover>
</>
);
}
You can install this library here.
it cover both reactive forms and ngforms too
Check this out https://www.npmjs.com/package/mat-tree-select-input
Probably this is what you are looking for:
https://github.com/dowjones/react-dropdown-tree-select (it also has a theme for mui like style)
I have a Select and the inputs are in Chip Format. I tried console log of the value selected and it is getting it fine. But for some reason, it does not get displayed on the select box. What am I doing wrong here?
handleChange = event => {
this.setState({ badge : event.target.value });
};
const chipOptions = [
{key: 1, 'text': 'text1', 'value': 'text1'},
{key: 2, 'text':'text2', 'value':'text2'},
{key: 3, 'text':'text3', 'value':'text3'}
]
<FormControl className={classes.formControl}>
<Select
value={this.state.badge}
onChange={this.handleChange}
inputProps={{
name: 'badge',
id: 'badge-simple',
}}
>
{chipOptions && chipOptions.map(option=> (
<Chip key={option.value} label={option.value} className={classes.chip} value={option.value} />
))}
</Select>
</FormControl>
The default manner in which Select renders the selected value is to render its children. In the Select source code as it is looping through the children of the Select, it does the following:
selected = areEqualValues(value, child.props.value);
if (selected && computeDisplay) {
displaySingle = child.props.children;
}
This is based on the assumption of the Select having MenuItem children. For instance, in the following example the first MenuItem would be selected and that MenuItem's children would be the text "Item 1":
<Select value={1}>
<MenuItem value={1}>Item 1</MenuItem>
<MenuItem value={2}>Item 2</MenuItem>
</Select>
Your Chips don't have children, so nothing is displayed. You can customize this behavior by specifying the renderValue property on Select. This is a function that receives the value and can decide what to render.
The following example shows using the renderValue prop to render a Chip:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import FormControl from "#material-ui/core/FormControl";
import Chip from "#material-ui/core/Chip";
import Select from "#material-ui/core/Select";
import { withStyles } from "#material-ui/core/styles";
const styles = theme => ({
formControl: {
margin: theme.spacing.unit,
minWidth: 120
}
});
const chipOptions = [
{ key: 1, text: "text1", value: "text1" },
{ key: 2, text: "text2", value: "text2" },
{ key: 3, text: "text3", value: "text3" }
];
function App({ classes }) {
const [value, setValue] = useState("text1");
const renderChip = value => {
return <Chip label={value} className={classes.chip} />;
};
return (
<>
<FormControl className={classes.formControl}>
<Select
inputProps={{
name: "badge",
id: "badge-simple"
}}
renderValue={renderChip}
value={value}
onChange={event => {
console.log(event.target.value);
setValue(event.target.value);
}}
>
{chipOptions &&
chipOptions.map(option => (
<Chip
key={option.value}
label={option.value}
className={classes.chip}
value={option.value}
/>
))}
</Select>
</FormControl>
</>
);
}
const StyledApp = withStyles(styles)(App);
const rootElement = document.getElementById("root");
ReactDOM.render(<StyledApp />, rootElement);
I have a bit of code that looks like this:
const companies = [
{
id: 1,
name: "Acme"
},
{
id: 2,
name: "Beta"
}
];
....
<RadioGroup
aria-label={title}
name={title}
value={this.state.value}
onChange={this.handleChange}
>
{companies.map(v => (
<FormControlLabel
value={v.name}
control={<Radio />}
label={v.name}
key={v.name}
/>
))}
/>
</RadioGroup>
The tricky bit is that I have to bind a string type to radio button, for all intents and purposes the company name works.
However, later when I submit the form, I need the whole companies object.
I could do something like:
company = companies.filter(v => v.name === this.state.value);
but it's a little messy.
Is there a simpler way to do this, that I'm missing?
Since you already need the company object, I'd have everything that you'd need about the company on fetch:
const companies = [
{
id: 1,
name: "Acme",
...otherProps
},
{
id: 2,
name: "Beta",
...otherProps
}
];
then, map over the company array, and pass it down its properties to a small child component that can then pass it back to the parent on FormItemControl's onChange method. The parent then stores the selection to state:
Form.js
...
state = { company: [] };
handleSubmit = e => {
e.preventDefault();
const { company } = this.state;
...etc
}
handleCompanySelect = company => this.setState({ selectedCompany: [company] })
render = () => {
...
return (
<form onSubmit={this.handleSubmit}>
...
<RadioGroup
aria-label={title}
name={title}
value={this.state.value}
onChange={this.handleChange}
>
{companies.map(props => (
<RadioOption
company={...props}
key={props.id}
handleCompanySelect={this.handleCompanySelect}
/>
))}
</RadioGroup>
...
</form>
)
RadioOption.js
import React, { PureComponent } from 'react';
import { Radio, FormControlLabel } from '...';
export default class RadioOption extends PureComponent {
handleChange = () => this.props.handleCompanySelect(this.props.company);
render = () => {
const { name } = this.props.company;
return (
<FormControlLabel
control={<Radio />}
name={name}
value={name}
label={name}
onChange={this.handleChange}
/>
)
}
}