Using MUI Autocorrect, with options populated by API - reactjs

I almost have a working solution, but the label aspect is giving an undefined error, and I also want to make sure my solution is elegant as its a component I will reuse a lot.
I have an API which returns a json response, and for the purposes of this, the important values are; (I will stub out the API and just provide its response).
const countries =
[
{ country_id: 1, name: 'France', iso: 'fr'},
{ country_id: 2, name: 'Germany', iso: 'de'},
{ country_id: 3, name: 'United Kingdom', iso: 'gb'},
{ country_id: 4, name: 'Spain', iso: 'es'}
];
It's the MUI example with some tweaks to almost make it work as desired.
I want the label in the AutoComplete to be the country name, I want the value returned to be the country_id and the text in the AutoComplete to be the name of the Country they selected. It's the label that's not being set.
const Select = ({ country, onUpdate }) => {
//country is the valuable passed in to preselect an option or the option chosen, and the onUpdate is a function passed in (its a setState in the parent component).
const [countries, setCountries] = useState([]);
const [value, setValue] = React.useState('');
useEffect(() => {
api.get(`/countries`).then((response) => {
if (response.data) {
setCountries(response.data);
}
});
}, []);
return (
<>
<Autocomplete
autoHighlight
fullWidth
value={value}
options={countries}
onChange={(event, newValue) => {
setValue(newValue.name);
}}
inputValue={country}
onInputChange={(event, newInputValue) => {
onUpdate(newInputValue);
}}
renderOption={(props, country) => (
<Box component="li" {...props}>
{`{country.name} (${country.iso})`}
</Box>
)}
renderInput={(params) => (
<TextField
{...params}
label="Choose a country"
/>
)}
/>
</>
);
};
Select.propTypes = {
country: PropTypes.string,
onUpdate: PropTypes.func.isRequired,
};
export default Select;

You need to add this line to your AutoComplete attributes:
getOptionLabel={(option) => option.name}
According to the documentation:
Options structure
By default, the component accepts the following options structures:
interface AutocompleteOption {
label: string;
}
// or
type AutocompleteOption = string;
for instance:
const options = [
{ label: 'The Godfather', id: 1 },
{ label: 'Pulp Fiction', id: 2 },
];
// or
const options = ['The Godfather', 'Pulp Fiction'];
However, you can use different structures by providing a getOptionLabel prop.
Another option is to change the name property in your response object into label and you will be set.

Related

UseEffect not re-rendering on use state object property change

I have a component that fetches data from an API and displays it in a select box. When the select box is changed, it updates a property inside the state. When this is done, I want to run a different useEffect which fetches more values from the API based on the selected item.
// State for dropdown options
const [categories, setCategories] = useState<DropdownItemProps[]>([]);
// State for form
const [category] = useState<CategoryInstance>({
id: "",
number: 1,
sponsor: "",
logo: "",
permit: "",
bumpSpot: 128,
raceEventId: "670d34d5-134b-4408-91bd-691f8c4b62d2",
categoryId: "",
configurationId: "",
category: null,
configuration: null,
raceEvent: null,
ladder: null,
entryList: [],
rounds: [],
});
// UseEffect 1, fetch dropdown items on load
useEffect(() => {
console.log("Use effect 1");
let c: DropdownItemProps[] = [];
categoryStore.loadCategories().then(() => {
categoryStore.categories.forEach((x) =>
c.push({
key: x.id,
value: x.id,
text: x.name,
})
);
setCategories(c);
});
}, [categoryStore]);
// UseEffect 2, fetch more items
useEffect(() => {
console.log("Use effect 2");
console.log(`ID: ${category.categoryId}`);
}, [category.categoryId]);
From my understanding, when the category.categoryId changes value, it should cause the UseEffect to print out the information. But this isn't happening.
<SelectInput
name='categoryId'
placeholder='Category'
label='Category'
options={categories}
search
/>
Select Input
import { useField } from "formik";
import { DropdownItemProps, Form, Label, Select } from "semantic-ui-react";
interface Props {
placeholder: string;
name: string;
type?: string;
label?: string;
options: DropdownItemProps[];
search: boolean;
}
export default function SelectInput(props: Props) {
const [field, meta, helpers] = useField(props.name);
return (
<Form.Field error={meta.touched && !!meta.error}>
<label>{props.label}</label>
<Select
clearable
options={props.options}
value={field.value || null}
onChange={(event, data) => helpers.setValue(data.value)}
onBlur={() => helpers.setTouched(true)}
placeholder={props.placeholder}
search={props.search}
/>
{meta.touched && meta.error ? (
<Label basic color='red'>
{meta.error}
</Label>
) : null}
</Form.Field>
);
}
Any help would great,
Thanks.

How to use Material UI Select with React Hook Form if we need to select Multiple values

I want to select multiple languages
const languages= [{ label: 'English', value: 'English' },{ label: 'French', value: 'French' },{ label: 'Arabic', value: 'Arabic' },{ label: 'Spanish', value: 'Spanish' }];
<Select
multiple
onChange={handleChange}
{...register('language')}>
{languages?.map((ele, index) => (
<MenuItem key={index} value={ele.value}> {ele.label}</MenuItem>
))}
</Select>
handle change function
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
onChange && onChange(name, event.target.value);
};
you need to define some state
const [selectedLang, setSelectedLang] = useState([])
Inside your handle change you need to set the new values to the state
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedLang[event.target.value]
};
they have a good example in the docs
https://mui.com/material-ui/react-select/

React-select defaultValue is not showing

I'm using Formik and React-select library and defaultValue is not working/taking effect and it remains empty, i don't know why this is happening.
MySelectInput component:
interface Props {
// .. other props
options: CategoryOptions[];
defaultCategory: CategoryOptions;
}
const MySelectInput: React.FC<Props> = (props) => {
const [field, meta, helpers] = useField(props.name);
return (
<>
<Select
options={props.options}
isSearchable
defaultValue={props.defaultCategory}
onChange={(v) => helpers.setValue(v!.value)}
onBlur={() => helpers.setTouched(true)}
placeholder={props.placeholder}
styles={customSelectStyles}
/>
</>
)
};
export default MySelectInput;
Usage:
<MySelectInput
options={CategoryOptions}
placeholder="Category"
name="category"
label="Category"
defaultCategory={{ value: activity.category, label: activity.category }}
/>
My array of objects (CategoryOptions):
export const CategoryOptions: CategoryOptions[] = [
{ value: 'drinks', label: 'Drinks' },
{ value: 'music', label: 'Music' },
{ value: 'travel', label: 'Travel' },
];
The options are working and displaying well but defaultValue is not working. If i use static strings inside object properties like:
defaultCategory={{ value: "test", label: "test" }}
is working well. Any idea?
I think you should probably use an item from CategoryOptions.
Instead of
defaultValue={props.defaultCategory}
do
defaultValue={props.defaultCategory[0]}
I'm also wondering why your CategoryOptions object is same as the CategoryOptions type. Maybe you should rename it to categoryOptions

How to update a state entry in react and display it's contents in input field before updating?

I'm creating a shopping cart form that can be used to add/update/delete user info. I've used react-hook-form for form submission and validation. My initial state is empty array. When user is added, objects are appended in the state array like -
state = [
{ name: 'abc', age: '23' },
{ name: 'katy', age: '12' },
];
How can update the value in state if a div row has an edit button and it displays it in an existing input box and when i click update(another button), it updates the corresponding value.
Note- name can be same hence i can't use a state.find().
One approach
const UpdateComponent = ({ id, user, setState }) => {
const [userData, setUserData] = React.useState({
id: 0,
name: "",
age: 0
});
React.useEffect(() => {
setUserData({ id: id, name: user.name, age: user.age });
}, [user, id]);
const onChange = (e) => {
setUserData((currentData) => ({
...currentData,
[e.target.name]: e.target.value
}));
};
const onSubmit = () => {
setState((currentState) =>
currentState.map((u) => (u.id === id ? userData : u))
);
};
return (
<>
<input
onChange={onChange}
name="name"
value={userData.name}
placeholder="Name"
/>
<input
onChange={onChange}
name="age"
value={userData.age}
placeholder="Age"
/>
<button onClick={onSubmit} type="button">
Update
</button>
</>
);
};
const List = () => {
const [state, setState] = React.useState([]);
React.useEffect(() => {
setState(
[
{ name: "abc", age: "23" },
{ name: "katy", age: "12" }
].map((u, i) => ({ ...u, id: i }))
);
}, []);
React.useEffect(() => {
// debug
console.log(state);
}, [state]);
return (
<div>
{state.map((user) => (
<UpdateComponent
key={user.id}
id={user.id}
user={user}
setState={setState}
/>
))}
</div>
);
};
Take a look https://codesandbox.io/s/fragrant-surf-p5cxh?file=/src/App.js
You could use the UUID package to generate the IDs:
React.useEffect(() => {
// Generates IDs when loading the data as example
// but ideally IDs are created on user creation
setState(
[
{ name: "abc", age: "23" },
{ name: "katy", age: "12" }
].map((u) => ({ ...u, id: uuidv4() }))
);
}, []);
Sandbox: https://codesandbox.io/s/unruffled-hypatia-tjzcx
But that is not very different from the initial approach with the map index id(on the component mount), that is not ideal because we are generating IDs when the component mounts, but at least they don't keep changing on each render
My initial state is empty array. When user is added, objects are appended in the state array like
For your case you could just have an ID that increments each time a user is added, or use the uuid when its added, so your data already comes with the ID

Cant turn on switch inside Material-Table

I am trying to create material-table with switches, that onclick changes the state of the component.
Here is component with table, which has state of the parent component as a props. I pass rows variable to the material table data prop. Switch is custom rendered field. Whenever I click on it, it triggers changeRow, which finds index of row in rows variable changes it and saves into new variable. ChangeRows is then called to change the state.
The problem is, the switch is not changing. It looks like nothing is happenning, even though I can clearly see new state in console.
const StuffTable = ({rows, changeRows}) => {
const changeRow = (oldRow, e) => {
const changeData = {[e.target.name]: e.target.checked};
const newRow = {...oldRow, ...changeData};
console.log(oldRow, e);
const index = rows.findIndex(dtaRow => dtaRow.id === oldRow.id);
const newData = rows;
newData[index] = newRow;
console.log(newData);
changeRows(newData);
};
return (
<Container maxWidth="lg">
<Button onClick={() => { changeRow({id: 6}, { target: {name: 'borrowable', checked: true} }) }}>klikni</Button>
<MaterialTable
options={{
actionsColumnIndex: -1,
search: true,
exportButton: true,
exportDelimiter: ";"
}}
actions={[
{
icon: 'edit',
tooltip: 'Edit Study',
onClick: (event, rowData) => alert("Do you want to edit?")
}]}
columns={[
{ title: "Název", field: "title" },
{ title: "Stav", field: "status", render: (data) => <Chip label={data.status} color="primary" avatar={<Avatar src="/static/images/avatar/1.jpg" />} /> },
{ title: "Půjčovat", field: "borrowable", render: (data, id) => (<FormControlLabel control={<Switch checked={data.borrowable} onChange={(e) => changeRow(data, e)} name="borrowable" color="primary"/>} label={data.borrowable ? 'půjčovat' : 'nepůjčovat'} />) },
{ title: "Vidí všichni", field: "active", render: (data, id) => (<FormControlLabel control={<Switch checked={data.borrowable} onChange={(e) => changeRow(data, e)} name="borrowable" color="primary"/>} label={data.borrowable ? 'půjčovat' : 'nepůjčovat'} />) },
{ title: "Uskladněno", field: "location" },
]}
data={rows}
title="Moje věci"
/>
</Container>
);
};
export default StuffTable;
I tried to add button, which on click changes state to empty array, and table did show nothing. But when I triggered changeRow (mockup data) with this button, result was the same - no change on the switch.
import React, {useEffect, useState} from 'react';
import StuffTable from "../components/stuffTable";
let rows = [
{id:5, title: "prošívanice", borrowable: false, surname: "Baran", status: "zapůjčeno", location: "Praha" },
{id:6, title: "prošívanice 2", borrowable: false, surname: "Baran", status: "zapůjčeno", location: "Praha" },
{id:7, title: "prošívanice 3", borrowable: false, surname: "Baran", status: "zapůjčeno" , location: "Brno"}
];
Here is Parent component
const MyStuffPage = () => {
const [data, setData] = useState(rows);
return (
<div>
<StuffTable rows={data} changeRows={(data) => {setData(data); console.log("hou",data);}} />
</div>
);
};
export default MyStuffPage;
Here is Codesandbox with this problem:
https://codesandbox.io/s/festive-gould-i1jf7
You need to call onQueryChange whenever you want to render new data or state to the datatable, make these changes:
at the begining create a ref like so:
const tableRef = useRef(null);
then use it in the material table:
<MaterialTable
//add this
tableRef={tableRef}
options={{
actionsColumnIndex: -1,
search: true,
exportButton: true,
exportDelimiter: ";"
}}
then inside your changeRow function after updating the start and the necessary work add this:
tableRef.current.onQueryChange()
this will tell the table to render the new data with the correct state of the switch

Resources