React Downshift autocomplete requests in an infinite loop - reactjs

I have the following React component
class Search extends Component {
constructor(props){
super(props);
this.state = {
suggestions: []
};
this.getSuggestions = this.getSuggestions.bind(this);
}
renderSuggestion(){
return (
this.state.suggestions.map((suggestion, index) =>
<MenuItem component="div" key={index} value={index} >
{suggestion}
</MenuItem>
)
);
};
getSuggestions (value) {
const inputValue = deburr(value.trim()).toLowerCase();
if(inputValue.length >= 3){
axios.get('http://localhost:5001/api/v1/products',{
params: {
q: inputValue
}
}).then(response => {
this.setState({suggestions : response.data.data });
});
}
};
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<Downshift id="downshift-simple">
{({
getInputProps,
getItemProps,
getMenuProps,
highlightedIndex,
inputValue,
isOpen,
}) => (
<div>
<TextField placeholder="Search a country (start with a)"
fullWidth={true}
onChange={this.getSuggestions(inputValue)}
{...getInputProps()}
/>
<div {...getMenuProps()}>
{isOpen ? (
<Paper className={classes.paper} square>
{this.renderSuggestion}
</Paper>
) : null}
</div>
</div>
)}
</Downshift>
</div>
);
}
}
export default withStyles(styles)(Search);
The autocomletion wors as expected as long as i do not perform an axios request in getSuggestions(). It seems to perform the request in an infinite loop as long as i do not refresh the page. Any ideas why this strange behaviour occures?

Because you are calling that function instead of passing the function to onChange. Kindly change your function to arrow function. refer this link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
getSuggestions (e) {
let value = e.target.value
const inputValue = deburr(value.trim()).toLowerCase();
if(inputValue.length >= 3){
axios.get('http://localhost:5001/api/v1/products',{
params: {
q: inputValue
}
}).then(response => {
this.setState({suggestions : response.data.data });
});
}
};
<TextField placeholder="Search a country (start with a)"
fullWidth={true}
onChange={(e)=> this.getSuggestions(e)}
{...getInputProps()}
/>

Related

Having trouble updating state in React after API call

I am trying to create a textbox with an autocomplete feature that pulls suggestions from an API but having trouble updating the state after I receive the array from the API. I am modifying code from here: https://blog.bitsrc.io/building-a-react-autocomplete-component-from-scratch-b78105324f4c
I think I have to use ComponentDidMount() but I am not sure how to apply it to an onChange Function.
class App extends React.Component {
render() {
return (
<div className="App">
<Autocomplete/>
</div>
);
}
}
class Autocomplete extends React.Component{
state = {
activeOption: 0,
filteredOptions: [],
showOptions: false,
userInput: ''
};
onChange = (e) => {
const userInput = e.currentTarget.value;
fetch("/places", {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(userInput)
}).
then(response => response.json())
.then(data => data.filter(element =>
element.PlaceName.toLowerCase().indexOf(userInput.toLowerCase()) > -1))
.then(filteredOptions => this.setState(
{
activeOption: 0,
filteredOptions: filteredOptions,
showOptions: true,
userInput: e.currentTarget.value
}));
};
.
.
.
.
render() {
const {
onChange,
onClick,
onKeyDown,
state: { activeOption, filteredOptions, showOptions, userInput }
} = this;
let optionList;
if (showOptions && userInput) {
console.log(filteredOptions)
if (filteredOptions.length) {
optionList = (
<ul className="options">
{filteredOptions.map((optionName, index) => {
let className;
if (index === activeOption) {
className = 'option-active';
}
return (
<li className={className} key={optionName} onClick={onClick}>
{optionName}
</li>
);
})}
</ul>
);
} else {
optionList = (
<div className="no-options">
<em>No Option!</em>
</div>
);
}
}
return (
<React.Fragment>
<div className="search">
<input
type="text"
className="search-box"
onChange={onChange}
onKeyDown={onKeyDown}
value={userInput}
/>
<input type="submit" value="" className="search-btn" />
</div>
{optionList}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.querySelector("#app"));
Once I try and run this, I get two errors: one for a synthetic event being reused for performance issues and one for a component changing an controlled input
You could put a setTimeout in the onChange method. And if the user doesn't type, you make the request, in other wise, you can't make the request
It looks like you were anticipating the filteredOptions variable to be a list of strings. It is actually a list of objects, which was causing React to throw the "object not allowed as children" error. Also, you can't use the event object to set state as it is already released. However, you were already storing the value in a variable which you could use, userInput. I updated your code with some very minor tweaks, and it appears to work. Take a look at a working example:
import React from "react";
import "./styles.css";
class App extends React.Component {
render() {
return (
<div className="App">
<Autocomplete />
</div>
);
}
}
class Autocomplete extends React.Component {
state = {
activeOption: 0,
filteredOptions: [],
showOptions: false,
userInput: ""
};
onChange = (e) => {
const userInput = e.currentTarget.value;
// Mock out the API call and JSON
Promise.resolve()
.then(() => {
const data = [
{ PlaceName: "Place 1" },
{ PlaceName: "Place 2" },
{ PlaceName: "Another Place 1" },
{ PlaceName: "Another Place 2" }
];
return data.filter(
(element) =>
element.PlaceName.toLowerCase().indexOf(userInput.toLowerCase()) >
-1
);
})
.then((filteredOptions) =>
this.setState({
activeOption: 0,
filteredOptions: filteredOptions,
showOptions: true,
userInput: userInput
})
);
};
render() {
const {
onChange,
onClick,
onKeyDown,
state: { activeOption, filteredOptions, showOptions, userInput }
} = this;
let optionList;
if (showOptions && userInput) {
if (filteredOptions.length) {
optionList = (
<ul className="options">
{filteredOptions.map((option, index) => {
let className;
if (index === activeOption) {
className = "option-active";
}
return (
<li
className={className}
key={option.PlaceName}
onClick={onClick}
>
{option.PlaceName}
</li>
);
})}
</ul>
);
} else {
optionList = (
<div className="no-options">
<em>No Option!</em>
</div>
);
}
}
return (
<React.Fragment>
<div className="search">
<input
type="text"
className="search-box"
onChange={onChange}
onKeyDown={onKeyDown}
value={userInput}
/>
<input type="submit" value="" className="search-btn" />
</div>
{optionList}
</React.Fragment>
);
}
}
export default App;

How to fix inifnity loop?

Hi guys i have this problem. I have input and dropdown I need choose from option dropdown and write value on input but first value was everytime empty. I solved this problem with componentDidUpdate and set locationValue on undefined. After i have sometimes this error.How can I fixed it? I need change lifecycle method or what ?
Here is code
class AccordionForm extends Component {
state = {
value: '',
locationValue: undefined,
};
componentDidUpdate() {
const { nameOptions } = this.props;
if (nameOptions && nameOptions && this.state.locationValue === undefined) {
this.setState({
locationValue: nameOptions[0],
});
}
}
handleChangeInputSelect = ({ target }) => {
this.setState({
locationValue: target.value,
});
};
handleChangeInput = ({ target }) =>
this.setState({
value: target.value,
});
onSubmit = event => {
const { value, locationValue } = this.state;
const { handleSubmitForm } = this.props;
event.preventDefault();
handleSubmitForm(value, locationValue);
this.setState({ value: '' });
};
render() {
const { name, nameOptions } = this.props;
const { value } = this.state;
return (
<Form className="accordion_form" name={name} onSubmit={this.onSubmit}>
<FormGroup className="form-group-locations">
{nameOptions && (
<Input
className="form-input"
required
name={name}
type="select"
onChange={this.handleChangeInputSelect}
>
{nameOptions.map((option, index) => {
return (
<option key={index} value={option}>
{option}
</option>
);
})}
</Input>
)}
<Input
placeholder={`add new ${name}...`}
type="text"
required
name={name}
value={value}
onChange={this.handleChangeInput}
className="form-input"
/>
<Button className="tagBtn" color="success" type="submit">
Add
</Button>
</FormGroup>
</Form>
);
}
}
export default AccordionForm;
This could be happening because at the first instance your nameOptions[0] is undefined. Please update your if statement's conditions like this:
componentDidUpdate() {
const { nameOptions } = this.props;
if (nameOptions && !!nameOptions.length && nameOptions[0] && this.state.locationValue !== nameOptions[0]) {
this.setState({
locationValue: nameOptions[0],
});
}
}

How to perform multiple filtering?

I am working on Filters which are based on categories. For the single category it's working, but how can I implement it for multiple category selections?
Example: If the user clicks on 'clothing' and 'sport', he should be able to see the list of both categories.
Redux state:
categories
>0 :{id:999 , name:'All', slug:'all'}
>1 :{id:2 , name:'clothing', slug:'clothing'}
>2 :{id:1 , name:'sport', slug:'sport'}
class ListFilter extends React.Component {
changeFilter = (category) => {
this.props.changeFilter(category, this.props.text);
gaEvent("Home - ListFilter", category, this.props.text);
};
clearFilters = () => {
this.props.changeFilter('all', '');
gaEvent("Home - ListFilter", "Reset");
};
render() {
return (
<>
<div className={classNames({
"search_list__filters": true,
"search_list--show": this.props.search
})}>
{this.props.categories.map((category, index) => {
return (
<Form.Group key={index} className="search_filters" >
<Form.Check onClick={(event)=>(event.target.checked!==true)?this.clearFilters():this.changeFilter(category.slug)} custom inline label={category.name} className='search_list__btn' type='checkbox' id={category.name} />
</Form.Group>
)
})}
<Row className="search_list_btn search_list__clear ">
<Col className="clear_wrapper">
{this.props.filters &&
<button className="clear_btn" onClick={this.clearFilters} >
Clear all filters
</button>
}
</Col>
</Row>
</div>
</>
);
}
}
const mapStateToProps = state => {
return state.Store
}
;
const mapDispatchToProps = dispatch => ({
changeFilter: (category, text) => dispatch(changeFilter(category, text))
});
export default connect(mapStateToProps, mapDispatchToProps)(ListFilter);
Currently you are dispatching the changeFilter event with single category. You can store the Filters in State and dispatch the event with array of Categories. Refer the CodeSandbox for working with multiple categories filters.
class ListFilter extends React.Component {
constructor(props) {
super(props);
this.state = {
filters: []
};
}
changeFilter = category => {
const { filters } = this.state;
const updatedFilter = [...filters, category];
this.setState({
filters: updatedFilter
});
this.props.changeFilter(updatedFilter, "testText");
};
render() {
console.log(this.state.filters);
return (
<div className="App">
{categories.map((category, index) => {
return (
<Form.Group key={index} className="search_filters">
<Form.Check
onClick={event =>
event.target.checked !== true
? this.clearFilters()
: this.changeFilter(category.slug)
}
custom
inline
label={category.name}
className="search_list__btn"
type="checkbox"
id={category.name}
/>
</Form.Group>
);
})}
</div>
);
}
}

How to fix Submit form Error with formik in Reactjs

Trying to display a dynamic Form based on Formik in Reactjs but when i try to submit there's no error and nothing is submitted.
Although i do get a warning sayning:
A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
this is my component DynamicForm that has all the behaviors of the form.
class DynamicForm1 extends Component {
renderCheckBox(input) {
return (
<Fragment key={input.name}>
<label>{input.label}</label>
<Field
name={input.name}
render={(prop) => {
const { field } = prop;
return (
<input
name={input.name}
type="checkbox"
checked={field.value}
onChange={field.onChange} />
);
}}
/>
</Fragment>
);
}
renderTextArea(input) {
return (
<Fragment key={input.name}>
<label>{input.label}</label>
<div>
<Field
name={input.name}
render={(props) => {
const { field } = props;
const { errors, touched } = props.form;
const hasError = errors[input.name] && touched[input.name] ? 'hasError' : '';
return (
<div>
<textarea {...field} id={hasError}>
</textarea>
</div>
);
}}
/>
</div>
</Fragment>
);
}
renderSelect(input) {
return (
<Fragment key={input.name}>
<label>{input.label}</label>
<div>
<Field
name={input.name}
render={(props) => {
const { field } = props;
const { errors, touched } = props.form;
const hasError = errors[input.name] && touched[input.name] ? 'hasError' : '';
const defaultOption = <option key='default' value='Please Select'>Please Select</option>;
const options = input.data.map(i => <option key={i} value={i}> {i} </option> );
const selectOptions = [defaultOption, ...options];
return (
<div className='dropdown'>
<select value={field.value} {...field} id={hasError}>
{
selectOptions
}
</select>
</div>
);
}}
/>
</div>
</Fragment>
);
}
renderFields(inputs) {
return inputs.map(input => {
if(input.type === 'select') {
return this.renderSelect(input);
}
if(input.type === 'checkbox') {
return this.renderCheckBox(input);
}
if(input.type === 'textarea') {
return this.renderTextArea(input);
}
return (
<div key={input.name}>
<label>{input.label}</label>
<div>
<Field
name={input.name}
render={(props) => {
const { field } = props;
const { errors, touched } = props.form;
const hasError = errors[input.name] && touched[input.name] ? 'hasError' : '';
return (
<input
{...field}
id={hasError}
type='text'
/>
);
}}
/>
</div>
</div>
);
})
}
getInitialValues(inputs) {
//declare an empty initialValues object
const initialValues = {};
//loop loop over fields array
//if prop does not exit in the initialValues object,
// pluck off the name and value props and add it to the initialValues object;
inputs.forEach(field => {
if(!initialValues[field.name]) {
initialValues[field.name] = field.value;
}
});
//return initialValues object
console.log(" initial values1" + initialValues);
return initialValues;
}
render() {
const initialValues = this.getInitialValues(this.props.fields);
console.log(" initial values2" + JSON.stringify(initialValues));
return (
<div className="app">
<h1>Dynamic Form</h1>
<Formik
onSubmit={(values) => {console.log("values :" +JSON.stringify(values))}}
validationSchema={this.props.validation}
initialValues={initialValues}
render={(form) => {
const errorMessageShow = Object.keys(form.errors).length > 0 ? 'error' : 'hidden';
return <div>
<form onSubmit={form.handleSubmit} onChange={form.handleChange}>
<div className={errorMessageShow}>
Please correct the errors below
</div>
{this.renderFields(this.props.fields)}
<button type='submit' className='btn'>Submit</button>
</form>
</div>
}}
/>
</div>
);
}
}
export default DynamicForm1;
And then there is App.js that contains my data that i collect from an API and i push it to the DynamicForm.
class App3 extends Component {
constructor(props){
super(props);
this.state={
fields:[]
};
this.getData = this.getData.bind(this);
}
getData(){
axios.get(`http://localhost:3006/formulaire`)
.then(res => {
this.setState({
fields: res.data
})
})
}
componentDidMount() {
this.getData();
}
render() {
return (
<DynamicForm1 fields={this.state.fields} validation={validation} />
)
}
}
export default App3;

React Redux add line-through on checkbox click

I am trying to add a line-through on after checking a checkbox. I'm using react and redux. The action and reducer works. I just need a way of adding this line-through when checked is true Please find the code i tried implementing this below. Thanks in advance.
/actions/items.js
export const CHECK_ITEM = "CHECK_ITEM"
export function checkItem(id) {
return {
type: CHECK_ITEM,
id
}
}
/reducers/items.js
case types.CHECK_ITEM:
return state.map((item) => {
if(item.id === action.id) {
return Object.assign({}, item,
{
checked: !item.checked
})
}
return item
})
/components/Editor.jsx
renderValue = () => {
const onDelete = this.props.onDelete
const onCheck = this.props.onCheck
return (
<div>
{onCheck ? this.renderCheckItem() : null}
<div onClick={this.props.onValueClick}>
<span className='value'>{this.props.value}</span>
{onDelete ? this.renderDelete() : null}
</div>
</div>
)
}
renderCheckItem = () => {
return (
<input
type="checkbox"
className='check-item checked'
defaultChecked={false}
onClick={this.props.onCheck}
/>
)
}
/components/Item.jsx
export default class Items extends React.Component {
render () {
const {items, onEdit, onDelete, onValueClick, onCheck, isEditing} = this.props
return (
<ul className="items">{items.map(item =>
<Item
className="item"
key={item.id}
id={item.id}>
<Editor
value={item.text}
onCheck={onCheck.bind(null, item.id)}
style={{textDecoration: item.checked ? 'line-through' : 'none'}}
/>
</Item>
)}</ul>
)
}
}
You need to connect your components to the redux store. Here's how to do it. In short you need something like:
export default connect(
state => {
return {items: state.items};
}
)(Items);
Where connect comes from react-redux.
I basically passed item.checked as item to my Editor component and used it like so
...
render() {
const {item, value, onEdit, onValueClick, isEditing, onCheck, ...props} = this.props
...
then in my Editor.jsx i did the following
/components/Editor.jsx
renderValue = () => {
const onDelete = this.props.onDelete
const onCheck = this.props.onCheck
const itemChecked = this.props.item
const isChecked = {textDecoration: itemChecked ? 'line-through' : 'none'}
return (
<div>
{onCheck ? this.renderCheckItem() : null}
<div onClick={this.props.onValueClick}>
<span style={isChecked} className='value'>{this.props.value}</span>
{onDelete && this.renderDelete()}
</div>
</div>
)
}
renderCheckItem = () => {
return (
<input
type="checkbox"
className='check-item'
defaultChecked={false}
onClick={this.props.onCheck}
/>
)
}
/components/Items.jsx
export default class Items extends React.Component {
render () {
...
return (
<ul className='items'>{items.map((item) =>
<Item
className='item'
key={item.id}
id={item.id}>
<Editor
item={item.checked}
isEditing={item.isEditing}
...

Resources