My Material-Table has a select menu as one of the columns, however when i select one of the options it changes it for all the rows. How can I make the change row specific so that they are not connected to each other? I realize that the event.target.value approach is applied to all of the mapped select menues, I cant figure out how to make it specific to one.
The row in question is the Facility column
const selectData = ["One", "Two", "Three"];
function Table() {
const columns = [
{
title: "Avatar",
render: (rowData) => (
<Avatar
maxInitials={1}
size={40}
round={true}
name={rowData === undefined ? " " : rowData.first_name}
/>
),
},
{ title: "ID", field: "_id", hidden: true },
{ title: "Name", field: "name" },
{ title: "Email", field: "email" },
{ title: "Company", field: "companyID" },
{ title: "Role", field: "role" },
{
title: "Facility",
render: (rowData) => (
<FormControl>
<InputLabel id="demo-mutiple-checkbox-label">Tags</InputLabel>
<Select
labelId="demo-mutiple-checkbox-label"
id="demo-mutiple-checkbox"
multiple
value={items}
onChange={handleChange}
input={<Input />}
renderValue={(selected) => selected.join(", ")}
MenuProps={MenuProps}
>
{selectData.map((item) => (
<MenuItem key={item} value={item}>
<Checkbox checked={items.indexOf(item) > -1} />
<ListItemText primary={item} />
</MenuItem>
))}
</Select>
</FormControl>
),
},
{ title: "Password", field: "password" },
];
const [data, setData] = useState([]);
const [items, setItems] = useState([]); //table data
const handleChange = (event) => {
setItems(event.target.value);
};
return (
<div>
<MaterialTable
title="Users"
columns={columns}
data={data}
icons={tableIcons}
editable={{
onRowUpdate: (newData, oldData) =>
new Promise((resolve) => {
handleRowUpdate(newData, oldData, resolve);
}),
onRowAdd: (newData) =>
new Promise((resolve) => {
handleRowAdd(newData, resolve);
}),
onRowDelete: (oldData) =>
new Promise((resolve) => {
handleRowDelete(oldData, resolve);
}),
}}
/>
</div>
);
}
I think you should define an editComponent in the Facility column, by doing so you will able to modify the value of the current rowData and not just the items variable which is being set as valuein the select component and therefore shown in every row.
Here is an example:
const tableColumns = [
{ title: "Client", field: "id" },
{ title: "Name", field: "name" },
{
title: "Choose a Fruit",
field: "fruit",
editComponent: ({ value, onChange }) => (
<select onChange={(e) => onChange(e.target.value)}>
<option selected value={value}>
{value}
</option>
{fruitsList.map(
(item) =>
item !== value && (
<option key={item} value={item}>
{item}
</option>
)
)}
</select>
)
}
];
Hope that works for you! Full code and sandbox here.
Related
React-table multi select filter not filtering data
Hi there, just wondering if anyone has a solution for this? I am struggling with the same issue.
I have a multi select dropdown which needs to show any changes in a react table.
so these are my columns: { Header: 'First Name', accessor: 'first_name', Cell: ({ cell }) => <ColorCell cell={cell} name="first_name" />, }, { Header: 'Last Name', accessor: 'last_name', Cell: ({ cell }) => <ColorCell cell={cell} name="last_name" />, },
an example of my dropdown:
//create a state to store the value const [valueState, setValueState] = useState(''); //handler function const handleSelectChange = (value) => { setValueState(value); } // on your onChange prop <SelectBox value={valueState} defaultValue="" placeholder="Select columns" isMulti options={filterOptions} onChange={ (value) => handleSelectChange (value) } name="ColumnSelect" label="" isClearable />
this is how I check for changes in the api:
{_.includes(cell.row.values.has_changed, name) ? ( <p style={{ backgroundColor: ${getCellColor(name)} }}>{cell.value}</p> ) : ( <p style={{ backgroundColor: 'white' }}>{cell.value}</p> )}
We can get TextField value dynamically with [evt.target.name]: evt.target.value but
How can I get Autocomplete selected value dynamically, Like bellow example:
import { useState } from 'react'
import Autocomplete from '#mui/material/Autocomplete'
import TextField from '#mui/material/TextField'
import Button from '#mui/material/Button'
const brands = ['niki', 'adidas', 'ramond', 'oliver', 'zara', 'casely']
const categories = ['pant', 'shirt']
const inputItems = [
{ label: 'Product Name', type: 'text', name: 'name' },
{ label: 'Brand', type: 'text', name: 'brand', options: brands },
{ label: 'Category', type: 'text', name: 'category', options: categories },
{ label: 'Price', type: 'number', name: 'price' },
{ label: 'Description', type: 'text', name: 'description' },
{ label: 'Image', type: 'text', name: 'image' },
]
const inputItemsObj = {}
inputItems.forEach(item => inputItemsObj[item.name] = '')
const Products = () => {
const [ fields, setFields ] = useState(inputItemsObj)
const [ fieldsError, setFieldsError ] = useState(inputItemsObj)
const changeHandler = (evt, newValue, action, option ) => {
setFields({ ...fields,[evt.target.name]: evt.target.value })
}
const submitHandler = (evt) => {
evt.preventDefault()
console.log(fields)
}
return (
<form noValidate onSubmit={submitHandler}>
{inputItems.map(({label, type, name, options}, key) => (name === 'category' || name === 'brand') ? (
<Autocomplete key={key}
options={options}
getOptionLabel={option => option}
renderInput={ params => <TextField {...params}
label={label}
placeholder={label}
type={type}
fullWidth
margin='dense'
InputLabelProps={{ shrink: true }}
name={name}
error={!fields[name] || !!fieldsError[name]}
helperText={fieldsError[name]}
/>}
name={name}
value={options[0]}
onChange={changeHandler}
/>
) : (
<TextField key={key}
label={label}
placeholder={label}
type={type}
fullWidth
autoFocus={key === 0}
margin='dense'
InputLabelProps={{ shrink: true }}
name={name}
value={fields[name]}
onChange={changeHandler}
multiline
rows={name === 'description' ? 3 : 1}
error={!fields[name] || !!fieldsError[name]}
helperText={fieldsError[name]}
/>
))}
<Button type='submit' variant='contained' >Submit</Button>
</form>
)
}
export default Products
The second value of onChange will give you a list of all the selected values.
function(event: React.SyntheticEvent, value: T | Array<T>, reason: string, details?: string) => void
So in your example use newValue rather than evt in your changeHandler
More info found here: https://mui.com/api/autocomplete/
Edit: I don't think you will be able to get the name from evt.target.name so some solution like this should work.
const changeHandler = (name) => (evt, newValue, action, option ) => {
// Also as you are updating/adding to state a callback should be used in setFields
setFields((prevState) => ({ ...prevState, [name]: newValue }))
}
Then in the Autocomplete:
<Autocomplete key={key}
//...other props
onChange={changeHandler(name)}
/>
I am trying to create a multi-select form control, however, whenever I select something it does not get rendered. The function handleChange does get the event.target.value but it does not seem to add to the roleIds state. Furthermore, the console.log for the variable selected does not log anything to console.
Component Code:
const allRoleIds = [
"12345678",
"98765423",
"56465735683578",
];
const [roleIds, setRoleIds] = React.useState([]);
function handleChange(event) {
setRoleIds(event.target.value);
}
const [cassowaries, setCassowaries] = React.useState({
columns: [
{ title: "Cassowary Name", field: "name" },
{
title: "Cassowary Roles",
field: "roles",
render: (rowData) => {
return (
<li>
{rowData.roles.map((role) => (
<Chip label={role} className={classes.chip} />
))}
</li>
);
},
editComponent: (props) => (
<FormControl className={classes.formControl}>
<InputLabel>Roles</InputLabel>
<Select
multiple
value={roleIds}
onChange={handleChange}
input={<Input id="select-multiple-chip" />}
renderValue={(selected) => {
console.log(selected);
return (
<div className={classes.chips}>
{selected.map((value) => (
<Chip
key={value}
label={value}
className={classes.chip}
/>
))}
</div>
);
}}
// MenuProps={MenuProps}
>
{allRoleIds.map((id) => (
<MenuItem key={id} value={id}>
{id}
</MenuItem>
))}
</Select>
</FormControl>
),
},
{ title: "Penguin", field: "penguin" },
],
data: [{ name: "Mehmet", roles: roleIds, penguin: true }],
});
You are doing concat in your handleChange. Material ui already gives you the array of selected values to you. Sp fix your handleChange everything should be fine.
Like this
function handleChange(event) {
setRoleIds(event.target.value);
}
Working demo is here
EDIT:
Based on additional req (see comments):
If a custom multi select component needs to be used inside materail table editcomponent, then the state of select should not be managed outside but state & onChagne needs to managed inside editComponent using the prop it provides.
See working demo here
Code snippet - material ui table
...
columns: [
{ title: "Tag Name", field: "name" },
{
title: "Tag Roles",
field: "roles",
render: rowData => {
return (
<li>
{rowData.roles.map(role => (
<Chip label={role} className={classes.chip} />
))}
</li>
);
},
editComponent: props => {
console.log("props", props);
return (
<FormControl className={classes.formControl}>
<InputLabel>Roles</InputLabel>
<Select
multiple
value={props.value}
onChange={e => props.onChange(e.target.value)}
input={<Input id="select-multiple-chip" />}
renderValue={renderChip}
>
{allRoleIds.map(id => (
<MenuItem key={id} value={id}>
{id}
</MenuItem>
))}
</Select>
</FormControl>
);
}
},
{ title: "Penguin", field: "penguin" }
],
data: [{ name: "Mehmet", roles: [], penguin: true }]
...
I am new to react and working on a table with MaterialUI. It is an editable table and I am trying to have an icon that returns a dialog box. I can get the dialog returned but when trying to pass through the rowData I get nothing. I have tried useState (probably using it wrong) and also trying to pass the data as a parameter.
See code below
export default function MaterialTableDemo() {
const [state, setState] = React.useState({
columns : [
{ title: "Program", field: "Program", defaultFilter: "Program" },
{
title: 'Program Type',
field: 'ProgramType',
lookup: { 1: 'Assignment', 2: 'Auto', 3: 'Default' },
},
],
data : [
{ Program: 'Program', ProgramType: 1 },
{ Program: 'Program 2', ProgramType: 3 },
],
});
function handleOnClose() {
setOpen(false)
}
function handleOnOpen() {
setOpen(true)
//console.log(rowData);
}
const [open, setOpen] = React.useState(false);
const [close, setClose] = React.useState();
return (
<div className="section">
<h6>filtering</h6>
<MaterialTable
columns={state.columns}
icons={tableIcons}
data={state.data}
dataIcon = {rowData => {
return (
console.log(rowData),
React.useState(rowData)
)
}}
detailPanel={rowData => {
return (
console.log(rowData),
<div>
<h2>Expired Time </h2> <br></br>
<span>{rowData.ExpireDate}</span>
<br></br>
<Popup modal trigger={<button>Edit </button>}>
{close => <Content close={close} />}
</Popup>
</div>
)
}}
options={{
filtering: true
}}
actions={rowData => {
return (
console.log(rowData)
)},
[
{
icon: tableIcons.Star,
tooltip: 'Popup to Edit',
onClick: (event, handleOnOpen)
},
]}
title="Program"
editable={{
onRowAdd: newData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
setState(prevState => {
const data = [...prevState.data];
data.push(newData);
return { ...prevState, data };
});
}, 600);
}),
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
if (oldData) {
setState(prevState => {
const data = [...prevState.data];
data[data.indexOf(oldData)] = newData;
return { ...prevState, data };
});
}
}, 600);
}),
onRowDelete: oldData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
setState(prevState => {
const data = [...prevState.data];
data.splice(data.indexOf(oldData), 1);
return { ...prevState, data };
});
}, 600);
}),
}
}
/>
<Dialog
open={open}
onClose={handleOnClose}
aria-labelledby="draggable-dialog-title"
>
<DialogTitle id="draggable-dialog-title">Edit Program</DialogTitle>
<DialogContent>
<DialogContentText>
Edit Program
</DialogContentText>
<div className="group">
{" "}
<label label for="ProgramCode"><b> Program: </b></label>
<input align="right" type="ProgramCode" placeholder="Enter Program Code" align="right" width="300" required></input>
</div>
<div className="group">
{" "}
<label for="ProgramTypeCode"><b> Program Type: </b></label>
<input type="ProgramTypeCode" placeholder="Enter Program Type" align="right" width="300" name="CPTC" required></input>
</div>
<br></br>
<button>Save</button>
</DialogContent>
</Dialog>
<br></br>
</div>
);
}
I use Autocomplete component and I have code like this:
const countries = [
{country: 'Poland', code: 'PL'},
{country: 'Germany', code: 'DE'},
{country: 'Spain', code: 'ES'},
{country: 'France', code: 'FR'}
]
const Input = ({onCountryChange}) => (
<Autocomplete
id="combo-box"
options={countries}
getOptionLabel={option => option.country}
style={{ width: 300, marginTop: 10 }}
onChange={onCountryChange}
renderInput={params => (
<TextField {...params} label="Type a country" variant="outlined" fullWidth />
)}
/>
)
onCountryChange = (event) => {
this.setState({
countryCode: event.target.textContent
});
};
Now it sets as countryCode the country from countries options and I would like to use code instead (like PL). Any ideas how I can do this?
You have almost got the answer. I changed the onCountryChange function and look if this what you seek.
here is a working example: https://codesandbox.io/s/brave-mccarthy-kqx97
const countries = [
{ country: "Poland", code: "PL" },
{ country: "Germany", code: "DE" },
{ country: "Spain", code: "ES" },
{ country: "France", code: "FR" }
];
class App extends React.Component {
state = {
countryCode: ""
};
onCountryChange = (object, value) => {
this.setState({
countryCode: value.code
});
};
render() {
const { countryCode } = this.state;
return (
<div className="App">
{countryCode}
<Autocomplete
id="combo-box"
options={countries}
getOptionLabel={option => option.country}
style={{ width: 300, marginTop: 10 }}
onChange={this.onCountryChange}
renderInput={params => (
<TextField
{...params}
label="Type a country"
variant="outlined"
fullWidth
/>
)}
/>
</div>
);
}
}
From Material UI documentation:
function(event: React.SyntheticEvent, value: T | Array<T>, reason: string, details?: string) => void
event: The event source of the callback.
value: The new value of the
component. reason: One of "createOption", "selectOption",
"removeOption", "blur" or "clear".
This is my onChange function from Grouped template with TypeScript:
onChange={(event, value, reason, details) => selectChangeHandler(event, value as { title: string, year: number, firstLetter: string }, reason, details)}
const selectChangeHandler = (event: SyntheticEvent, value: { title: string, year: number, firstLetter: string }, reason: string, details: unknown) => {
event.preventDefault()
console.log('value', value)
console.log('reason', reason)
console.log('details', details)
}