Re render component after fetching searched data in Material UI autocomplete - javascript

I'm using Material UI Autocomplete to get searched data, it works fine, but it's re-render after the search runs and clears the value without focus, then we have to click again to get fetched value.
here Is my auto-complete component
<AutoCompleteNewDesign
disableClearable
freeSolo
id="combo-box-demo"
onChange={(e, selectValue) => {
setInvoiceSelectedData(selectValue);
}}
options={invoiceSearchList}
getOptionLabel={option => (option ? String(option?.data1) : '')}
renderOption={option => (
<div>
{option.data1} - {option.data2}
</div>
)}
renderInput={params => (
<TextField
params={params}
name="name-here"
variant="outlined"
onChange={e => {
dispatch(
searchName(e.target.value);
}}
/>
)}
filterOptions={filterOptions}
/>
I used this reducer to store data which fecth when user searched
const { searchedNames } = useSelector(state => state.searchReducer);
How to call my dispatch asynchronous as well as prevent this re-render

Related

show search results on different component-page with react/MUI

I have a MUi autocomplete component inside a top bar, so it is a fixed general bar shown on all pages. My question is, how can I show the results on a different pages?
`<Autocomplete
fullWidth
id="free-solo-demo"
freeSolo
options={interests.map((option) => option.name)}
renderInput={(params) => <TextField {...params} placeholder="Search" />}
renderOption={renderOption}
/>`
Should I pass the props as paramenters?
If you want to show the autocomplete results on a different page, you will need to pass the state and options as props to the component on that page.
One way to do this is to store the state of the Autocomplete component in a higher-level component, such as the parent component that is responsible for rendering the pages. You can pass the state and options down to the Autocomplete component as props. Then, when the user navigates to a different page, you can pass the same state and options to the Autocomplete component on that page.
Here is an example:
// Parent component
function App() {
const [interests, setInterests] = useState([]);
const [selectedInterest, setSelectedInterest] = useState("");
// fetch interests data and set state
return (
<div>
<TopBar interests={interests} selectedInterest={selectedInterest} onInterestChange={setSelectedInterest} />
// Render other pages and pass interests and selectedInterest as props as needed
</div>
);
}
// TopBar component
function TopBar({ interests, selectedInterest, onInterestChange }) {
return (
<AppBar>
<Autocomplete
fullWidth
id="free-solo-demo"
freeSolo
options={interests.map((option) => option.name)}
value={selectedInterest}
onInputChange={(event, newInputValue) => {
onInterestChange(newInputValue);
}}
renderInput={(params) => <TextField {...params} placeholder="Search" />}
renderOption={renderOption}
/>
</AppBar>
);
}

React chip input field

I'm using React 17 and tried to use different chip input fields like material-ui-chip-input etc. Unfortunately, all npm packages I tried do not work with react version 17.
Do you know of any component that works with this version or how I could create one myself?
Thanks in advance
Edit:
I have managed to do what I wanted, but unfortunately, I now get the following error when pressing enter / adding a new item:
index.js:1 Warning: Cannot update a component (CreatePoll) while rendering a different component (ForwardRef(Autocomplete))
Here is the codesandbox which shows the problem:
https://codesandbox.io/s/compassionate-greider-wr9wq?file=/src/App.tsx
why don't you use this instead of a new package?
Autocomplete exactly do that.
const [receivers, setReceivers] = React.useState<string[]>([]);
import Autocomplete from '#mui/material/Autocomplete';
<Autocomplete
multiple
onChange={(e, value) => setReceivers((state) => value)}
id="tags-filled"
options={top100Films.map((option) => option.title)}
defaultValue={[top100Films[13].title]}
freeSolo
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip variant="outlined" label={option} {...getTagProps({ index })} />
))
}
renderInput={(params) => (
<TextField
{...params}
variant="filled"
label="freeSolo"
placeholder="Favorites"
/>
)}
/>
import React from 'react'
import { MuiChipsInput } from 'mui-chips-input'
const MyComponent = () => {
const [chips, setChips] = React.useState([])
const handleChange = (newChips) => {
setChips(newChips)
}
return (
<MuiChipsInput value={chips} onChange={handleChange} />
)
}
Source:
https://github.com/viclafouch/mui-chips-input
Supports both React 17 / 18 and MUI V5
Use the freeSolo prop of Autocomplete, and just set the options prop to an empty array:
<Autocomplete
multiple
freeSolo
options={[]}
renderTags={(value, getTagProps) =>
value.map(option, index) => (
<Chip label={option} {...getTagProps({ index })} />
))
}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="Add Chip"
placeholder="Type and press enter"
/>
)}
/>

Select all and Select None buttons in Autocomplete Material UI React

I want to implement two buttons Select All and Select None inside Autocomplete React Material UI along with checkbox for each option.When Select All button is clicked all the options must be checked and when I click Select None all the options must be unchecked.
How do I implement that ?
<Autocomplete
id={id }
size={size}
multiple={multiple}
value={value}
disabled={disabled}
options={items}
onChange={handleChange}
getOptionLabel={option => option.label}
renderOption={(option, { selected }) => (
<React.Fragment >
{isCheckBox(check, selected)}
{option.label}
</React.Fragment>
)}
renderInput={params => (
<TextField id="dropdown_input"
{...params} label="controlled" variant={variant} label={label} placeholder={placeholder} />
)}
/>
export function isCheckBox(check, selected) {
if (check) {
const CheckBox = <Checkbox
id="dropdown_check"
icon={icon}
checkedIcon={checkedIcon}
checked={selected}
/>
return CheckBox;
}
return null;
}
I stumbled into the same issue earlier today.
The trick is to use local state to manage what has been selected, and change the renderOption to select * checkboxes if the local state has the 'all' key in it.
NB: At the time of writing React 16 is what I'm working with
I'm on a deadline, so I'll leave a codesandbox solution for you instead of a rushed explanation. Hope it helps :
Select All AutoComplete Sandbox
Updated
for React version 16.13.1 and later. codesandbox
const [open, setOpen] = useState(false);
const timer = useRef(-1);
const setOpenByTimer = (isOpen) => {
clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
setOpen(isOpen);
}, 200);
}
const MyPopper = function (props) {
const addAllClick = (e) => {
clearTimeout(timer.current);
console.log('Add All');
}
const clearClick = (e) => {
clearTimeout(timer.current);
console.log('Clear');
}
return (
<Popper {...props}>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button color="primary" onClick={addAllClick}>
Add All
</Button>
<Button color="primary" onClick={clearClick}>
Clear
</Button>
</ButtonGroup>
{props.children}
</Popper>
);
};
return (
<Autocomplete
PopperComponent={MyPopper}
onOpen={(e) => {
console.log('onOpen');
setOpenByTimer(true);
}}
onClose={(obj,reason) => {
console.log('onClose', reason);
setOpenByTimer(false);
}}
open={open}
.....
....
/>
);
Old Answer
Just customise PopperComponent and do whatever you want.
Autocomplete API
const addAllClick = (e: any) => {
setValue(items);
};
const clearClick = (e: any) => {
setValue([]);
};
const MyPopper = function (props: any) {
return (
<Popper {...props}>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button color="primary" onClick={addAllClick}>
Add All
</Button>
<Button color="primary" onClick={clearClick}>
Clear
</Button>
</ButtonGroup>
{props.children}
</Popper>
);
};
<Autocomplete
PopperComponent={MyPopper}
...
/>
If you want to make an autocomplete with select all option using react material ui and react hook form, you can implement to Autocomplete like so
multiple: To allow multiple selection
disableCloseOnSelect: To disable the close of the box after each selection
options: Array of items of selection
value: Selected options.
getOptionLabel: The string value of the option in our case is name
filterOptions: A function that determines the filtered options to be rendered on search, in our case we used it to add selectAll checkbox.
renderOption: Render the option, use getOptionLabel by default.
renderInput: To render the input,
onChange: Callback fired when the value changes
Now you can play with selected values using handleChange so once the select is fired check if the selected option is select all if yes then set the newest selectedOptions
<Autocomplete
multiple
disableCloseOnSelect
options={items}
value={selectedOptions}
getOptionLabel={(option) => option.name}
filterOptions={(options, params) => {
const filtered = filter(options, params)
return [{ id: 0, name: selectAllLabel }, ...filtered]
}}
renderOption={(props, option, { selected }) => {
// To control the state of 'select-all' checkbox
const selectAllProps =
option.name === 'Sélectionner Tous' ? { checked: allSelected } : {}
return (
<li {...props}>
<Checkbox checked={selected} {...selectAllProps} />
{option.name}
</li>
)
}}
renderInput={(params) => (
<TextField {...params} label={label} placeholder={label} />
)}
onChange={handleChange}
/>
you can refer to the Autocomplete API to get detailed definition of each item
You can refer to this codeSendBox to check a demo of react material Autocomplete with select all using react material ui version 5 and react hook form verion 7

Material ui Autocomplete press enter to create new chips

I wish I could do such a thing using Autocomplete of material ui: wertarbyte
That is, inserting text (string) without having a list of elements from which you must select.
Therefore the noOptions message should not appear, every time the enter key is pressed on the keyboard the text is inserted.
Link: codesandbox
Code:
import React from "react";
import Chip from "#material-ui/core/Chip";
import Autocomplete from "#material-ui/lab/Autocomplete";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
const useStyles = makeStyles(theme => ({
root: {
width: 500,
"& > * + *": {
marginTop: theme.spacing(3)
}
}
}));
export default function Tags() {
const classes = useStyles();
return (
<div className={classes.root}>
<Autocomplete
multiple
id="tags-outlined"
options={[]}
defaultValue={["foo", "bar"]}
//getOptionLabel={(option) => option}
//defaultValue={[top100Films[13]]}
//filterSelectedOptions
renderInput={params => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
/>
)}
/>
</div>
);
}
In case you have simple elements (not objects, just strings), and you don't really need to handle state (your autocomplete is not controlled) you can use the freeSolo prop of the Autocomplete.
<Autocomplete
multiple
freeSolo
id="tags-outlined"
options={["foo", "bar"]}
defaultValue={["foo", "bar"]}
renderInput={params => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
/>
)}
/>
In case your elements are more complex and you do need to control the element:
Make sure the Autocomplete tag is a controlled one (you manage to value).
Listen to key down event on the TextField.
If the code is Enter (e.code === 'Enter') - take the value of the input and push it to the list of the current values that you have.
Make sure you also handle the onChange to handle the removal of elements:
Here is the code:
const [autoCompleteValue, setAutoCompleteValue] = useState(["foo", "bar"]);
return (
<Autocomplete
multiple
id="tags-outlined"
options={[]}
value={autoCompleteValue}
onChange={(e, newval, reason) => {
setAutoCompleteValue(newval);
}}
renderInput={params => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
onKeyDown={e => {
if (e.code === 'enter' && e.target.value) {
setAutoCompleteValue(autoCompleteValue.concat(e.target.value));
}
}}
/>
)}
/>
);
Check the live working example of both options: https://codesandbox.io/s/mui-autocomplete-create-options-on-enter-gw1jc
For anyone who wants to input the current best match on enter key press (as opposed to any custom text) you can use the autoHighlight prop.
<Autocomplete
multiple
autoHighlight
id="tags-outlined"
options={["foo", "bar"]}
defaultValue={["foo", "bar"]}
renderInput={params => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
/>
)}
/>
To do this, don't use the Autocomplete element from MUI. Just use a a standard TextField with the use of InputProps. All you need to do is add a onKeyDown listener to the TextField that listens for 'Enter' and when the function is triggered, have it add to an array of Chips in the InputProps. It might look something like this:
const [inputValue, setInputValue] = useState('');
const [chips, setChips] = useState([])
const inputChange = ({target: {value}}) => {setInputValue(value)};
const handleKeyDown = ({key}) => {
if(key === 'Enter') {
setChips([...chips, inputValue])
}
};
<TextField
fullWidth
variant="outlined"
label="Fish and Chips"
value={inputValue}
onChange={inputChange}
multiline
InputProps={{
startAdornment: chips.map((item) => (
<Chip
key={item}
label={item}
/>
)),
}}
/>
This is untested as written here, but it should work. I've done something similar in one of my apps.

React Input losing focus after keystroke

I'm building a an app in React/Redux and I have some input fields that lose focus after each key stroke. My code looks like this:
<General fields={props.general}
onChange={value => props.updateValue('general', value)} />
<FormWrapper>
<Network fields={props.network}
onChange={value => props.updateValue('network', value} />
</NetworkTableForm>
</FormWrapper>
General and Network just contain different sections of the form and FormWrapper can wrap a subsection of the form and provide some interesting visualisations. The problem is that all of the fields in General work fine but the fields in Network lose focus after each keystroke. I've already tried appending keys to a bunch of different elements such as General Network, FormWrapper and my components wrapping my input fields and the reason that I'm not using redux-form is because it doesn't work well with the UI framework that I'm using(Grommet). Any idea what could be causing this?
Edit: Adding more code as per request. Here's the full component:
const InfoForm = (props) => {
let FormWrapper = FormWrapperBuilder({
"configLists": props.configLists,
"showForm": props.showForm,
"loadConfiguration": props.loadConfiguration,
"updateIndex": props.updateIndex,
"metadata": props.metadata})
return (
<CustomForm onSubmit={() => console.log('test')}
loaded={props.loaded} heading={'System Details'}
header_data={props.header_data}
page_name={props.general.name} >
<General fields={props.general}
onChange={(field, value) => props.updateValue('general', field, value)} />
<FormWrapper labels={["Interface"]} fields={["interface"]} component={'network'}>
<Network fields={props.network}
onChange={(field, value) => props.updateValue('network', field, value)} />
</FormWrapper>
<User fields={props.user}
onChange={(field, value) => props.updateValue('user', field, value)} />
</CustomForm>
)
}
export default InfoForm
Here's an example of a form section component:
const Network = ({fields, onChange}) => (
<CustomFormComponent title='Network'>
<CustomTextInput value={fields.interface} label='Interface'
onChange={value => onChange('interface', value)} />
<CustomIPInput value={fields.subnet} label='Subnet'
onChange={value => onChange('subnet', value)} />
<CustomIPInput value={fields.network} label='Network'
onChange={value => onChange('network', value)} />
<CustomIPInput value={fields.gateway} label='Gateway'
onChange={value => onChange('gateway', value)} />
<CustomIPInput value={fields.nat_ip} label='NAT'
onChange={value => onChange('nat_ip', value)} />
</CustomFormComponent>
)
export default Network
Here's an example of one of the custom inputs:
const CustomTextInput = ({label, value, onChange}) => (
<FormField label={<Label size='small'>{label}</Label>}>
<Box pad={{'horizontal': 'medium'}}>
<TextInput placeholder='Please Enter' value={value}
onDOMChange={event => onChange(event.target.value)}
/>
</Box>
</FormField>
)
export default CustomTextInput
And here's the FormWrapperBuilder function:
const FormWrapperBuilder = (props) =>
({component, labels, fields, children=undefined}) =>
<VisualForm
configLists={props.configLists[component]}
showForm={visible => props.showForm(component, visible)}
loadConfiguration={index => props.loadConfiguration(component, index)}
updateIndex={index => props.updateIndex(component, index)}
metadata={props.metadata[component]}
labels={labels} fields={fields} >
{children}
</VisualForm>
export default FormWrapperBuilder
All of these are kept in separate files which may affect the rerendering of the page and it's the functions that are wrapped in the FormWrapper that lose focues after a state change. Also I've tried adding keys to each of these components without any luck. Also here's the functions in my container component:
const mapDispatchToProps = (dispatch) => {
return {
'updateValue': (component, field, value) => {
dispatch(updateValue(component, field, value))},
'showForm': (component, visible) => {
dispatch(showForm(component, visible))
},
'loadConfiguration': (component, index) => {
dispatch(loadConfiguration(component, index))
},
'pushConfiguration': (component) => {
dispatch(pushConfiguration(component))
},
'updateIndex': (component, index) => {
dispatch(updateIndex(component, index))
}
}
}
Edit 2: Modified my custom text input as such as per Hadas' answer. Also slathered it with keys and refs for good measure
export default class CustomTextInput extends React.Component {
constructor(props) {
super(props)
this.state = {value: ""}
}
render() {
return (
<FormField key={this.props.label} ref={this.props.label} label={<Label size='small'>{this.props.label}</Label>}>
<Box key={this.props.label} ref={this.props.label} pad={{'horizontal': 'medium'}}>
<TextInput placeholder='Please Enter' value={this.state.value} key={this.props.label} ref={this.props.label}
onDOMChange={event => { this.props.onChange(event.target.value); this.state.value = event.target.value }} />
</Box>
</FormField>
)
}
}
React is re-rendering the DOM on each key stroke. Try setting value to a variable attached to the component's state like this:
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
onChangeText={(text) => this.setState({text})}
value={this.state.text}></TextInput

Categories