React - MUI - Autcomplete, store & load checked items - reactjs

I want to select and store some items. After reloading these values have to be already selected in the list. how i can store and load selected values, that for the next time (reloading, the stored data), the selected values will be selected in the Autocomplete list ?
export default function BuzwordTags(props) {
const tNdsProps = props.titelNdsProps.NotesDashboradProps;
const [selectedOptions, setSelectedOptions] = React.useState("");
const updates = {
buzwords: selectedOptions,
};
React.useEffect(
() => (
setSelectedOptions(tNdsProps.expenses[0].buzwords),
console.log("Selectet options changed", selectedOptions)
),
[tNdsProps.activeNote]
);
return (
<Grid>
<Autocomplete
multiple
onChange={(event, value) => setSelectedOptions(value)}
id="checkboxes-tags-demo"
options={top100Films}
disableCloseOnSelect
getOptionLabel={(option) => option.title}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.title}
</li>
)}
style={{ width: 500 }}
renderInput={(params) => (
<TextField
{...params}
label="Checkboxes"
placeholder="Favorites"
value={selectedOptions != "" ? selectedOptions[0].title : ""}
/>
)}
/>
<Button
variant="outlined"
onClick={() => {
setSelectedOptions(tNdsProps.activeNote.id, updates);
}}
>
Add B-Words
</Button>
</Grid>
);
}
const top100Films = [
{ title: "The Shawshank Redemption", year: 1994 },
{ title: "The Godfather", year: 1972 },
{ title: "The Godfather: Part II", year: 1974 },
];

Related

Not able to set values on Material UI TextField Select component

I am new to React.
I want to build a Form that has a Text field and a dropdown with 2 selections.
The form renders but I am unable to select any values from the dropdown. The handleChange function doesnt run.
const Category = () => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const [formValues, setformValues] = useState({
categoryName: "Test",
categoryType: "Expense",
})
const categories = [
{
value: 'Income',
label: 'Income',
},
{
value: 'Expense',
label: 'Expense',
},
];
const handleFormSubmit = (values) => {
console.log(`Submitting : ${JSON.stringify(values)}`);
};
const isNonMobile = useMediaQuery("(min-width:600px)");
const handleCategoryTextChange = (event) => {
console.log(`Category Text Change ${event}`);
setformValues({
...formValues,
categoryName: event.target.value,
})
}
const handleCategoryChange = (event) => {
console.log(`Category Change Event ${event.target.value}`);
setformValues({
...formValues,
categoryType: event.target.value
})
};
return (
<Box>
<Box m="20px">
<Header title="CREATE CATEGORY" subtitle="Create a New Category" />
<form onSubmit={handleFormSubmit}>
<Box
display="grid"
gap="30px"
gridTemplateColumns="repeat(4, minmax(0, 1fr))"
sx={{
"& > div": { gridColumn: isNonMobile ? undefined : "span 4" },
}}
>
<TextField
fullWidth
variant="filled"
type="text"
label="Category Name"
onChange={handleCategoryTextChange}
value={formValues.categoryName}
name="categoryName"
sx={{ gridColumn: "span 2" }}
/>
<TextField
fullWidth
variant="filled"
label="Category Type"
onChange={handleCategoryChange}
value={formValues.categoryType}
name="categoryType"
sx={{ gridColumn: "span 2" }}
select
>
{categories.map((option) => (
<MenuItem key = {option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
</Box>
</form>
</Box>
</Box>
);
};
I am expecting the dropdown to be selectable and the handleChange events to be triggered.
Once this is working, I would submit the form values to an API endpoint

How can I pass data from row to dialog component in react-table?

This is my first time using react-table, after I decided to redesign a component in my app. I'm almost there, but I found myself stuck while trying to pass some data to a dialog pop-up on button click.
In my previous table design, using MUI Table, I used array.map() to render each row entry. Then, all I needed to do was using props to pass data to the Dialog components, so every action button would load the data from their respective entries (including data not displayed, like _id). Now with react-table I don't know how to achieve the same result, due to the logic change. The Edit and Delete buttons are there, they trigger the dialog opening, but my progress ends here.
Entries.jsx
function Entries(props) {
const navigate = useNavigate();
const [openEdit, setOpenEdit] = useState(false);
const [openDelete, setOpenDelete] = useState(false);
const handleEdit = () => {
setOpenEdit(true);
};
const handleDelete = () => {
setOpenDelete(true);
};
const handleClose = () => {
setOpenEdit(false);
setOpenDelete(false);
};
const data = props.data;
const columns = useMemo(
() => [
{
Header: "#",
id: "row",
Cell: ({ row }) => {
return <div>{row.index + 1}</div>;
},
},
{
Header: "Title",
accessor: "title",
className: "column-left",
Cell: ({ cell: { value }, row: { original } }) => (
<Link
sx={{
fontSize: "14px",
fontWeight: 500,
"&:hover": { color: "#00D67E" },
}}
color="#fff"
underline="none"
style={{ cursor: "pointer" }}
onClick={() => navigate(`/details/${original.movieId}`)}
>
{value}
</Link>
),
},
{
Header: "Year",
accessor: "year",
className: "column-right",
},
{
Header: "Rating",
accessor: "details[0].rating",
className: "column-right",
Cell: ({ cell: { value } }) => (
<Rating
size="small"
value={value}
readOnly
emptyIcon={<StarIcon fontSize="inherit" />}
/>
),
},
{
Header: "Watched",
accessor: "date",
className: "column-right",
Cell: ({ cell: { value } }) => format(new Date(value), "MMM dd, yyyy"),
},
{
Header: "View Count",
accessor: "details[0].view_count",
className: "column-right",
},
{
Header: "Review",
accessor: "details[0].review",
className: "column-right",
Cell: ({ cell: { value } }) =>
!value ? null : <CheckIcon color="primary" />,
},
{
Header: "Actions",
className: "column-right",
Cell: () => (
<div>
<IconButton
aria-label="edit"
style={{ color: "#e0e0e0" }}
onClick={handleEdit}
>
<EditIcon fontSize="small" />
</IconButton>
<IconButton
aria-label="delete"
style={{ color: "#e0e0e0", paddingRight: 0 }}
onClick={handleDelete}
>
<DeleteIcon fontSize="small" />
</IconButton>
</div>
),
},
],
[]
);
return (
<div>
{/* TABLE */}
<CustomTable columns={columns} data={data} />
{/* DIALOGS */}
<EditDialog
isOpen={openEdit}
onClose={handleClose}
id={data._id}
movieId={data.movieId}
date={data.date}
rating={data.details[0].rating}
review={data.details[0].review}
onUpdate={props.onUpdate}
/>
<DeleteDialog
isOpen={openDelete}
onClose={handleClose}
title="Delete this entry?"
message={
<span>
<strong>
{data.title} ({data.date})
</strong>{" "}
will be removed. This action cannot be undone.
</span>
}
onRemove={() => {
props.onRemove(data._id, data.movieId);
}}
/>
</div>
);
}
export default Entries;
Later on I even tried something to mimic the logic of my previous design, since the mapping is handled by the rows.map((row) => { prepareRow(row) } in my CustomTable.jsx component. Then I came up with this:
{
Header: "Actions",
className: "column-right",
Cell: () => (
<div>
<IconButton
aria-label="edit"
style={{ color: "#e0e0e0" }}
onClick={({row}) => {
handleEdit();
return (
<EditDialog
isOpen={openEdit}
onClose={handleClose}
id={row._id}
movieId={row.movieId}
date={row.date}
rating={row.details[0].rating}
review={row.details[0].review}
onUpdate={props.onUpdate}
/>
);
}}
>
<EditIcon fontSize="small" />
</IconButton>
<IconButton
aria-label="delete"
style={{ color: "#e0e0e0", paddingRight: 0 }}
onClick={handleDelete}
>
<DeleteIcon fontSize="small" />
</IconButton>
</div>
),
}
But it didn't work either. It stops right at the first prop and throws an error saying it can't read properties row._id. That was my last shot.
That is simple, you can store the selected row to the state and then use it in your Edit and Delete components. Try this
function Entries(props) {
const navigate = useNavigate();
const [openEdit, setOpenEdit] = useState(false);
const [openDelete, setOpenDelete] = useState(false);
const [selectedRow, setSelectedRow] = useState();
const handleEdit = (row) => {
setOpenEdit(true);
setSelectedRow(row);
};
const handleDelete = (row) => {
setOpenDelete(true);
setSelectedRow(row);
};
const handleClose = () => {
setOpenEdit(false);
setOpenDelete(false);
setSelectedRow();
};
const data = props.data;
const columns = useMemo(
() => [
{
Header: "#",
id: "row",
Cell: ({ row }) => {
return <div>{row.index + 1}</div>;
}
},
{
Header: "Title",
accessor: "title",
className: "column-left",
Cell: ({ cell: { value }, row: { original } }) => (
<Link
sx={{
fontSize: "14px",
fontWeight: 500,
"&:hover": { color: "#00D67E" }
}}
color="#fff"
underline="none"
style={{ cursor: "pointer" }}
onClick={() => navigate(`/details/${original.movieId}`)}
>
{value}
</Link>
)
},
{
Header: "Year",
accessor: "year",
className: "column-right"
},
{
Header: "Rating",
accessor: "details[0].rating",
className: "column-right",
Cell: ({ cell: { value } }) => (
<Rating
size="small"
value={value}
readOnly
emptyIcon={<StarIcon fontSize="inherit" />}
/>
)
},
{
Header: "Watched",
accessor: "date",
className: "column-right",
Cell: ({ cell: { value } }) => format(new Date(value), "MMM dd, yyyy")
},
{
Header: "View Count",
accessor: "details[0].view_count",
className: "column-right"
},
{
Header: "Review",
accessor: "details[0].review",
className: "column-right",
Cell: ({ cell: { value } }) =>
!value ? null : <CheckIcon color="primary" />
},
{
Header: "Actions",
className: "column-right",
Cell: ({ row }) => (
<div>
<IconButton
aria-label="edit"
style={{ color: "#e0e0e0" }}
onClick={() => handleEdit(row.original)}
>
<EditIcon fontSize="small" />
</IconButton>
<IconButton
aria-label="delete"
style={{ color: "#e0e0e0", paddingRight: 0 }}
onClick={() => handleEdit(row.original)}
>
<DeleteIcon fontSize="small" />
</IconButton>
</div>
)
}
],
[]
);
return (
<div>
{/* TABLE */}
<CustomTable columns={columns} data={data} />
{/* DIALOGS */}
<EditDialog
isOpen={openEdit}
onClose={handleClose}
id={selectedRow?._id}
movieId={selectedRow?.movieId}
date={selectedRow?.date}
rating={selectedRow?.details[0]?.rating}
review={selectedRow?.details[0]?.review}
onUpdate={props.onUpdate}
/>
<DeleteDialog
isOpen={openDelete}
onClose={handleClose}
title="Delete this entry?"
message={
<span>
<strong>
{selectedRow?.title} ({selectedRow?.date})
</strong>{" "}
will be removed. This action cannot be undone.
</span>
}
onRemove={() => {
props.onRemove(selectedRow?._id, selectedRow?.movieId);
}}
/>
</div>
);
}
export default Entries;

Unexpected useState behaviour in ant design table

I have ant design form and table side by side,When I fill the form data and click on add entity button the data should be displayed in the table. Now the data does get rendered when I click on add entity but again when I fill the form and click on the add entity button the previous data gets disappeared, Now I know that I need to copy the previous data and I did that too but its not working.
My Problem:- The previous form state value which is rendered in the table gets disappeared when new form data is added
My query:- How and Where should I copy the previous object with the spread operator so that it does not get reset.
My code is
import { Card, Table } from "antd";
import { Form, Input, Button, Select, Space, AutoComplete } from "antd";
import { DeleteOutlined, PlusOutlined, SendOutlined } from "#ant-design/icons";
import { useState } from "react";
const ExampleComponent = (props) => {
// Destructuring props
const { intent_data, entity_data } = props;
const [dataSource, setDataSource] = useState([{}]);
// To handle the disable state of Select Entity Select DropDown
const [addentity,setAddEntity] = useState(false)
// Handler function passed to YES/NO Select Option
const addEntityHandler = (addEntity) => {
if(addEntity === 'no'){
setAddEntity(true)
}else{
setAddEntity(false)
}
}
const [form] = Form.useForm();
const onFinish = (values) => {
console.log(values);
form.resetFields();
const dataArr = [];
// Push values to array since dataSource takes array not an object
dataArr.push(values);
setDataSource(dataArr);
};
const columns = [
{
title: "Entity",
dataIndex: "entity_name",
key: "entity_name",
},
{
title: "Entity Value",
dataIndex: "entity_value",
key: "entity_value",
},
{
title: "Operation",
key: "operation",
render: (record: any) => (
<DeleteOutlined
style={{ color: "red" }}
onClick={() => console.log(record)}
/>
),
},
];
return (
<Card className="csi-project-card-0934">
<div className="example-layout">
<div style={{ flexBasis: "100%" }}>
<Form
form={form}
labelCol={{ span: 7 }}
wrapperCol={{ span: 10 }}
layout="horizontal"
colon={true}
onFinish={onFinish}
size="large"
>
{/* <h4>Create Example</h4> */}
<Form.Item
label="Select Intent"
name="intent_name"
className="csi-ant-form-item"
rules={[{ required: true, message: "Intent Cannot be Empty!" }]}
>
<Select>
{/* {intent_data?.map?.((value) => (
<Select.Option
key={value.intent_ID}
value={value.intent_name}
>
{value.intent_name}
</Select.Option>
))} */}
<Select.Option value="intent demo">Intent Demo</Select.Option>
<Select.Option value="intent test">Intent Test</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="Enter Example"
name="example_name"
className="csi-ant-form-item"
hasFeedback
rules={[
{ required: true, message: "This Field Cannot be Empty!" },
({ getFieldValue }) => ({
validator(_, value) {
if (value.length < 4) {
return Promise.reject("Length too short");
}
return Promise.resolve();
},
}),
]}
>
<AutoComplete>
<Input allowClear/>
</AutoComplete>
</Form.Item>
<Form.Item
label="Do you want to add Entity"
name="add_entity"
className="csi-ant-form-item"
rules={[{ required: true, message: "This Cannot be Empty!" }]}
>
<Select placeholder="SELECT" onSelect={(addEntity) => addEntityHandler(addEntity)}>
<Select.Option value="yes">YES</Select.Option>
<Select.Option value="no">NO</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="Select Entity"
name="entity_name"
className="csi-ant-form-item"
>
<Select disabled = {addentity}>
<Select.Option value="entity demo">Entity Demo</Select.Option>
<Select.Option value="entity test">Entity Test</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="Select Value"
name="entity_value"
className="csi-ant-form-item"
hasFeedback
rules={[
{ required: true, message: "This Field Cannot be Empty!" },
]}
>
<AutoComplete>
<Input placeholder="Select Value from Example" />
</AutoComplete>
</Form.Item>
<Form.Item className="csi-ant-form-item">
<Button
key="submit"
type="primary"
htmlType="submit"
shape="round"
>
Add Entity <PlusOutlined />
</Button>
</Form.Item>
</Form>
</div>
<div
style={{
flexBasis: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Table
bordered
className="ib-table"
dataSource={dataSource}
columns={columns}
pagination={{ pageSize: 6 }}
rowKey={Math.random().toString()}
/>
<Button key="submit" type="primary" htmlType="submit" shape="round">
Submit <SendOutlined />
</Button>
</div>
</div>
</Card>
);
};
export default ExampleComponent;
The form data is stored in values object and the structure of values is
{
add_entity: "yes"
entity_name: "entity demo"
entity_value: "Test"
example_name: "Test"
intent_name: "intent demo"
}
One thing to note here is that dataSource state variable is array of objects, like
[{
add_entity: "yes"
entity_name: "entity demo"
entity_value: "Test"
example_name: "Test"
intent_name: "intent demo"
}]
My Expected Output is Below
Entity
Entity Value
Operation
entity demo
test
delete icon
intent demo
test
delete icon
What if you try with
setDataSource(prevDataSource => [...prevDataSource, values]);
?
In the same way, to delete an item:
{
title: "Operation",
key: "operation",
render: (record) => (
<DeleteOutlined
style={{ color: "red" }}
onClick={() => {
setDataSource(prevDataSource => prevDataSource.filter(item => item.entity_name !== record.entity_name //entity_name or whatever id the item has ))
}}
/>
),
},
By the way, try to avoid using any whenever possible if you're using typescript. Here is how to:
import { Table } from 'antd';
import { ColumnsType } from 'antd/es/table';
interface User {
key: number;
name: string;
}
const columns: ColumnsType<User> = [
{
key: 'name',
title: 'Name',
dataIndex: 'name',
},
];
const data: User[] = [
{
key: 0,
name: 'Jack',
},
];
export default () => (
<>
<Table<User> columns={columns} dataSource={data} />
/* JSX style usage */
<Table<User> dataSource={data}>
<Table.Column<User> key="name" title="Name" dataIndex="name" />
</Table>
</>
);

How to select option by id or by name in Autocomplete Material-UI

I can't find solution how I can select same option by id and by name using Material-UI Autocomplete.
My array of options:
const options = [
{id: 1, name: 'onion'},
{id: 2, name: 'potato'},
{id: 3, name: 'carrot'}
]
What I expect:
If I type 3 I get option carrot
I type carrot I get same option carrot
This is how my code is looking:
<Autocomplete
classes={{
root: classes.root
}}
style={{ padding: 0 }}
options={options}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
required
className='inputPadding'
classes={{ root: classes.labelRoot }}
label={t('mainRank')}
variant='outlined'
helperText={positionObjErr && t('required')}
error={positionObjErr}
/>
)}
getOptionSelected={(opt, val) => opt === val}
value={positionObj}
onChange={(e, val) => {
val && setPositionObj(val)
}}
/>
You can override the default filterOptions and make it filters by id after the label filter returns no result:
import TextField from "#material-ui/core/TextField";
import Autocomplete, {
createFilterOptions
} from "#material-ui/lab/Autocomplete";
const options = [
{ id: 1, name: "onion" },
{ id: 2, name: "potato" },
{ id: 3, name: "carrot" }
];
const _filterOptions = createFilterOptions();
const filterOptions = (options, state) => {
const result = _filterOptions(options, state);
if (result.length === 0) {
return _filterOptions(options, {
...state,
getOptionLabel: (o) => o.id.toString()
});
}
return result;
};
Usage
export default function Filter() {
return (
<Autocomplete
options={options}
getOptionLabel={(option) => option.name}
filterOptions={filterOptions}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} variant="outlined" />}
/>
);
}
Live Demo
You can put a condition for the prop getOptionSelected like below so it will match for both string and id
<Autocomplete
classes={{
root: classes.root
}}
style={{ padding: 0 }}
options={options}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
required
className='inputPadding'
classes={{ root: classes.labelRoot }}
label={t('mainRank')}
variant='outlined'
helperText={positionObjErr && t('required')}
error={positionObjErr}
/>
)}
getOptionSelected={(opt, val) => opt.id === Number(val) || opt.name === val}
value={positionObj}
onChange={(e, val) => {
val && setPositionObj(val)
}}
/>

Moving the chips/tags outside Autocomplete box in MUI

I am using the MUI Autocomplete component and am trying to figure out if it is possible to move the chips/tags outside of the input box. Is this even possible? I would prefer if the chips were listed just underneath the box instead. This way the text box can be used solely for the user input and not both displaying the chips/tags & user input.
I have tried to play with one of the basic demo's but didn't have any luck. I cleared it back to the default state in case there are some users here that have experience with this. A starting point for an example can be the following sandbox
https://codesandbox.io/s/material-demo-forked-7vroo?file=/demo.js
You can do that by disabling tag rendering inside Autocomplete and add your own Chip list below the Autocomplete.
const [value, setValue] = useState([]);
const onDelete = (title) => () => {
setValue((value) => value.filter((v) => v.title !== title));
};
return (
<Box sx={{ width: 500 }}>
<Autocomplete
multiple
options={top100Films}
defaultValue={[top100Films[13]]}
getOptionLabel={(option) => option.title}
value={value}
onChange={(e, newValue) => setValue(newValue)}
renderTags={() => null}
renderInput={(params) => (
<TextField {...params} variant="outlined" placeholder="Favorites" />
)}
/>
<Box
mt={3}
sx={{
'& > :not(:last-child)': { marginRight: 1 },
'& > *': { marginBottom: 1 },
}}
>
{value.map((v) => (
<Chip key={v.title} label={v.title} onDelete={onDelete(v.title)} />
))}
</Box>
</Box>
);
const top100Films = [
{ title: "The Shawshank Redemption", year: 1994 },
{ title: "The Godfather", year: 1972 },
]
Live Demo
V5
V4

Resources