Material UI Select component not picking input value - reactjs

I have implemented a Material UI Select component that houses categories from which a user picks. The problem I have is that the category value is not getting picked and as such I get the following Django backend error
IntegrityError at /api/courses/, null value in column "category_id" violates not-null constraint
What could I be doing wrong? Here are the files I am working with.
NewCourseView.js
const newCourse = ({
allcategory,
category,
handleSelectChange,
}) => {
return (
<div>
<br />
<React.Fragment>
<div className="upload-course-form">
<Typography className="form-titltle" variant="h6" gutterBottom>
CREATE COURSE
</Typography>
<Grid item xs={12} sm={6}>
<InputLabel
htmlFor="select-category"
>
Category
</InputLabel>
<Select
name="category"
value={category}
onChange={handleSelectChange}
className="select-categories"
inputProps={{
id: "select-category",
}}
>
{" "}
{allcategory.map(cate => (
<MenuItem key={cate.id} value={cate.id}>
{cate.title}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12}>
<button onClick={handleSubmit} className="submit-course-button">
{" "}
SUBMIT
</button>
</Grid>
</Grid>
</div>
</React.Fragment>
</div>
);
};
newCourse.propTypes = {
handleSubmit: PropTypes.func,
handleSelectChange: PropTypes.func.isRequired,
allcategory: PropTypes.array,
category: PropTypes.number.isRequired,
};
export default newCourse;
NewCourseContainer
export class CreateCourse extends Component {
constructor(props) {
super(props);
this.state = {
category: "",
allcategory: [],
};
}
componentWillMount() {
this.categories();
}
handleSelectChange = e => {
this.setState({ category: e.target.value });
};
handleSubmit = e => {
e.preventDefault();
const data = {
video: {
category: this.state.category,
}
};
this.props.dispatch(createNewCourseAction(data));
};
categories = () => {
let initial = [];
fetch(API_URLS.FETCH_CATEGORIES, {
method: "GET",
headers: myHeaders
})
.then(response => {
return response.json();
})
.then(data => {
initial = data.categorys.results.map(category => {
return category;
});
this.setState({
allcategory: initial
});
})
.catch(error => {
toast.error(error, { autoClose: 3500, hideProgressBar: true });
});
};
render() {
const {
category,
allcategory
} = this.state;
return (
<div>
<NewCourse
handleSubmit={this.handleSubmit}
category={category}
allcategory={allcategory}
handleSelectChange={this.handleSelectChange}
/>
</div>
);
}
const mapDispatchToProps = dispatch => ({ dispatch });
export default connect(
mapStateToProps,
mapDispatchToProps
)(CreateCourse);

In selector:
onChange={(e, k, val) => this.handleSelectChange(e, k, val)}
handleSelectChange:
handleSelectChange = (e, k, val) => {
this.setState({ category: val });
};

Related

Error: Element type is invalid: expected a string or a class/function but got: undefined

Error imageThe error specifically points to:
dispatch({
type: SET_POSTS,
payload: res.data
})
Below is my func code:
export const getPosts = () => dispatch => {
dispatch({ type: LOADING_DATA })
axios.get('/posts')
.then(res => {
dispatch({
type: SET_POSTS,
payload: res.data
})
})
.catch(err => {
dispatch({
type: SET_POSTS,
payload: []
})
})
};
I checked for imports, console.log(res) and everything seems fine, but the error always points to dispatch()
Edit: After tracing the code with redux dev tools I found out that the payload for SET_POSTS kept changing from payload:[{...}] to undefined. Below I attached the code for my reducer. I still don't know what the problem is.
import { SET_POSTS, LIKE_POST, UNLIKE_POST, LOADING_DATA } from '../types';
const initialState = {
posts: [],
post: {},
loading: false,
};
export default function (state = initialState, action) {
switch (action.type) {
case LOADING_DATA:
return {
...state,
loading: true
};
case SET_POSTS:
return {
...state,
posts: action.payload,
loading: false,
};
case LIKE_POST:
case UNLIKE_POST:
var index = state.posts.findIndex((post) => post.postId === action.payload.postId);
state.posts[index] = action.payload;
// conditional from github
if (state.post.postId === action.payload.postId) {
state.post = action.payload;
}
return {
...state
};
default:
return state;
}
}
Here is where the getPosts() function is been rendered:
import { getPosts } from '../redux/actions/dataActions';
class home extends Component {
componentDidMount() {
this.props.getPosts();
}
render() {
const { posts, loading } = this.props.data;
let recentPostsMarkup = !loading ? (
posts.map(post => <Post key={post.postId} post={post} />)
) : (<p>loading...</p>);
return (
<Grid container spacing={5}>
<Grid item sm={8} xs={12}>
{recentPostsMarkup}
</Grid>
<Grid item sm={4} xs={12}>
<Profile />
</Grid>
</Grid>
);
home.propTypes = {
getPosts: PropTypes.func.isRequired,
data: PropTypes.object.isRequired,
}
const mapStateToProps = state => ({
data: state.data
});
export default connect(mapStateToProps, { getPosts })(home);
This is the Post.js file where the post prop is being passed in the home.js file.
class Post extends Component {
likedPost = () => {
if (this.props.user.likes && this.props.user.likes.find(
like => like.postId === this.props.post.postId)
) return true;
else return false;
};
likePost = () => {
this.props.likePost(this.props.post.postId);
}
unlikePost = () => {
this.props.unlikePost(this.props.post.postId);
}
render() {
dayjs.extend(relativeTime);
const {
classes,
post: { body, createdAt, userImage, userHandle, postId, likeCount, commentCount },
user: {
authenticated
}
} = this.props;
const likeButton = !authenticated ? (
<MyButton tip="Like">
<Link to="/login">
<FavoriteBorder color="primary"/>
</Link>
</MyButton>
) : (
this.likedPost() ? (
<MyButton tip="Undo like" onClick={this.unlikePost}>
<FavoriteIcon color="primary"/>
</MyButton>
): (
<MyButton tip="Like" onClick={this.likePost}>
<FavoriteBorder color="primary"/>
</MyButton>
)
)
return (
<Card className={classes.card}>
<CardMedia
image={userImage}
title="Profile Image" className={classes.image} />
<CardContent className={classes.content}>
<Typography
variant="h5"
component={Link}
to={`/users/${userHandle}`}
color="primary">
{userHandle}
</Typography>
<Typography variant="body2" color="textSecondary">
{dayjs(createdAt).fromNow()}
</Typography>
<Typography variant="body1">
{body}
</Typography>
{likeButton}
<span>{likeCount} Likes</span>
<MyButton tip="comments">
<ChatIcon color="primary" />
</MyButton>
<span>{commentCount} comments</span>
</CardContent>
</Card>
)
}
};
Ciao, this is a well known issue in React. Please, check this guide (even if is made for React Native).
In brief, your problem is related on how you are importing your component, and is not related on how you are dispatching your data.

React.js form validation with Joi-Browser not working as expected

I'm trying to make a multi-step form using React.js and material UI. for validation purpose I am using Joi-Browser. But I am getting error from Joi while validation, stating that error: ValidationError: "value" must be an object
I am very new to React.js Please guide me what I am doing wrong here.
here what I have tried so far.
class ServiceRequestForm extends Component {
state = {
step: 1,
service_category: [],
user : [
{
full_name: '',
address_one: '',
}
],
hasError: false
}
schema = Joi.object().keys({
full_name: Joi.string().alphanum().min(3).max(100).required(),
address_one: Joi.string().required(),
});
validate = () => {
const result = Joi.validate(this.state.user, this.schema)
console.log(result)
}
// Proceed to next step
nextStep = () => {
const { step } = this.state;
this.setState({
step: step + 1
});
}
// Proceed to prev step
prevStep = () => {
const { step } = this.state;
this.setState({
step: step - 1
});
}
// handle select
handleChange = (event)=> {
this.setState(oldValues => ({
...oldValues,
[event.target.name]: event.target.value,
}));
}
// handle input
handleChangeInput = name => event => {
this.setState({ [name]: event.target.value });
};
handleSubmit = ()=>{
this.validate();
}
render() {
const { step } = this.state;
const { service_category } = this.state;
const { full_name, address_one } = this.state.user;
const values = { service_category, full_name, address_one };
switch (step) {
case 1:
return (
<CategoryForm
nextStep={this.nextStep}
handleChange={this.handleChange}
values={values}
/>
);
case 2:
return (
<AddressForm
prevStep={this.prevStep}
handleChangeInput={this.handleChangeInput}
handleSubmit={this.handleSubmit}
values={values}
/>
);
case 3:
return (
<ThankYouPage
/>
);
}
}
}
export default ServiceRequestForm;
// Category form
export default class CategoryForm extends Component {
continue = e => {
e.preventDefault();
this.setState({ hasError: false });
if (!this.props.values.service_category) {
console.log(this.props.hasError);
this.setState({ hasError: true });
}
else {
this.props.nextStep();
}
}
render() {
const { handleChange, values, classes, nextStep, hasError } = this.props;
return (
<div>
<h4>Select service you want</h4>
<form>
<FormControl error={hasError}>
<Select
value={values.service_category}
onChange={handleChange}
inputProps={{
name: 'service_category'
}}
>
<MenuItem value="">
<em>Select Category</em>
</MenuItem>
<MenuItem value={10}>House Maid</MenuItem>
<MenuItem value={20}>Electricians</MenuItem>
<MenuItem value={30}>Plumber</MenuItem>
</Select>
<FormHelperText>Please select service category</FormHelperText>
{hasError && <FormHelperText>This is required!</FormHelperText>}
</FormControl>
</form>
<br />
<Button variant="contained" color="primary" onClick={this.continue}>Next</Button>
</div>
)
}
}
// address form
export default class AddressForm extends Component {
back = e => {
e.preventDefault();
this.props.prevStep();
}
render() {
const { handleChangeInput, values, classes, handleSubmit, prevStep, hasError, full_name } = this.props;
return (
<div>
<h1>Address</h1>
<TextField
label="Full Name"
//className={classes.textField}
value={values.full_name}
onChange={handleChangeInput('full_name')}
margin="normal"
variant="outlined"
/>
<TextField
label="Address Line 1"
//className={classes.textField}
value={values.address_one}
onChange={handleChangeInput('address_one')}
margin="normal"
variant="outlined"
/>
<Button variant="contained" color="primary" onClick={handleSubmit}>Submit</Button>
<Button variant="contained" color="primary" onClick={prevStep}>Back</Button>
</div>
);
}
}
Schema can be just an object.
Try using like below.
{
full_name: Joi.string().alphanum().min(3).max(100).required(),
address_one: Joi.string().required(),
}
No need to specify
Joi.object().keys(

How to test components wrapped in an antd Form?

I've been struggling with this for months, now. Although there's a lot of speculation on the correct way to test antd wrapped components, none of the suggestions worked for this particular component.
So, I have a component which is a modal with an antd form. In this form, I have a few fields: an input, a select and a tree select, nothing too fancy.
It's basically this:
class FormModal extends React.Component {
static propTypes = {
data: propTypes.object,
form: propTypes.object,
scopes: propTypes.array.isRequired,
clients: propTypes.array.isRequired,
treeData: propTypes.array.isRequired,
isEditing: propTypes.bool.isRequired,
isSaving: propTypes.bool.isRequired,
onCancel: propTypes.func.isRequired,
onSave: propTypes.func.isRequired,
onFilterTreeData: propTypes.func.isRequired,
visible: propTypes.bool.isRequired
}
static defaultProps = {
data: null,
form: {}
}
state = {
selectedScopes: [],
newScopes: [],
inputVisible: false,
inputValue: ''
};
componentDidMount() {
// do stuff
}
handleSave = () => {
// do stuff
}
handleSelectedScopesChange = (event) => {
// do stuff
}
updateTreeSelect = () => {
const { form } = this.props;
const { selectedScopes } = this.state;
form.setFieldsValue({
allowedScopes: selectedScopes
});
}
handleRemoveTag = (removedTag) => {
const selectedScopes = this.state.selectedScopes.filter(scope => scope !== removedTag);
const newScopes = this.state.newScopes.filter(scope => scope !== removedTag);
this.setState({ selectedScopes, newScopes }, this.updateTreeSelect);
}
showInput = () => {
this.setState({ inputVisible: true }, () => this.input.focus());
}
handleInputChange = (e) => {
const inputValue = e.target.value;
this.setState({ inputValue });
}
handleInputConfirm = () => {
const { newScopes, inputValue } = this.state;
let tags = newScopes;
if (inputValue && tags.indexOf(inputValue) === -1) {
tags = [inputValue, ...tags];
}
this.setState({
newScopes: tags,
inputVisible: false,
inputValue: '',
});
}
saveInputRef = input => this.input = input
renderTags = (scopeArrays) => {
const tags = scopeArrays.map(scopeArray =>
scopeArray.map((permition) => {
let scopeType = null;
if (permition.includes('read') || permition.includes('get')) scopeType = 'blue';
if (permition.includes('create') || permition.includes('write') || permition.includes('send')) scopeType = 'green';
if (permition.includes('update')) scopeType = 'gold';
if (permition.includes('delete')) scopeType = 'red';
return (
<Tag
key={permition}
color={scopeType || 'purple'}
style={{ margin: '2px 4px 2px 0' }}
closable
afterClose={() => this.handleRemoveTag(permition)}
>
{permition}
</Tag>
);
})
);
return [].concat(...tags);
}
render() {
const {
selectedScopes,
newScopes,
inputValue,
inputVisible
} = this.state;
const {
form,
treeData,
clients,
isEditing,
isSaving,
onCancel,
onFilterTreeData,
visible
} = this.props;
const {
getFieldDecorator,
getFieldsError,
} = form;
const selectedScopesTags = this.renderTags([newScopes, selectedScopes]);
const clientOptions = clients.map(client => (<Option key={client._id}>{client.name}</Option>));
return (
<Modal
className="user-modal"
title={isEditing ? 'Editing Group' : 'Creating Group'}
visible={visible}
onCancel={onCancel}
footer={[
<Button key="cancel" onClick={onCancel}>Cancel</Button>,
<Button
key="save"
type="primary"
loading={isSaving}
onClick={this.handleSave}
disabled={formRules.hasErrors(getFieldsError())}
>
Save
</Button>
]}
>
<Form layout="vertical" onSubmit={this.handleSave}>
<Row gutter={24}>
<Col span={12}>
<FormItem label="Name">
{getFieldDecorator(
'name',
{ rules: [formRules.required, { max: 20, message: 'Group name can\'t excede 20 characters' }] }
)(
<Input />
)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="Client">
{getFieldDecorator(
'client', { rules: [formRules.required] }
)(
<Select placeholder="Please select client">
{clientOptions}
</Select>
)}
</FormItem>
</Col>
<Col span={24}>
<FormItem label="Scopes">
{getFieldDecorator(
'allowedScopes'
)(
<TreeSelect
treeData={treeData}
filterTreeNode={onFilterTreeData}
onChange={this.handleSelectedScopesChange}
treeCheckable
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
showCheckedStrategy="SHOW_PARENT"
searchPlaceholder="Filter by scopes"
className="groups__filter groups__filter--fill"
/>
)}
</FormItem>
</Col>
<Col span={24}>
<Card
title="Selected Scopes"
style={{ width: '100%' }}
>
<div>
{inputVisible && (
<Input
ref={this.saveInputRef}
type="text"
size="small"
style={{ width: 350 }}
value={inputValue}
onChange={this.handleInputChange}
onBlur={this.handleInputConfirm}
onPressEnter={this.handleInputConfirm}
/>
)}
{!inputVisible && (
<Tag
onClick={this.showInput}
style={{ background: '#fff', borderStyle: 'dashed', margin: '5px 0' }}
>
<Icon type="plus" /> New Scope
</Tag>
)}
</div>
{ selectedScopesTags.length > 0 ? (
selectedScopesTags
) : <p>No scopes selected yet.</p> }
</Card>
</Col>
</Row>
</Form>
</Modal>
);
}
}
export default Form.create()(FormModal);
I know that this component is urging for a refactoring, but that's not my job right now. I need to UI test this and validate if everything is working properly.
I'm trying to test if the form fields are rendering properly. I'm using Jest and Enzyme and so far I got this:
describe('Groups modal', () => {
let props;
const groupsModal = () =>
mount(
<FormModal.WrappedComponent {...props} />
)
beforeEach(() => {
props = {
data: null,
scopes: [],
clients: [],
treeData: [],
isEditing: false,
isSaving: false,
onCancel: jest.fn(),
onSave: jest.fn(),
onFilterTreeData: jest.fn(),
visible: true,
form: {
getFieldsError: jest.fn(() => { return {} }),
getFieldDecorator: () => (component) => component
}
};
});
it('should render properly', () => {
const wrapperDivChilds = groupsModal().find('.user-modal').children();
expect(wrapperDivChilds.length).toBeGreaterThanOrEqual(1);
});
describe('form fields', () => {
it('should render name input', () => {
const nameInput = groupsModal().find(Input);
expect(nameInput.length).toBe(1);
});
it('should render clients select', () => {
const clientsSelect = groupsModal().find(Select);
expect(clientsSelect.length).toBe(1);
});
it('should render scopes tree select', () => {
const scopesTreeSelect = groupsModal().find(TreeSelect);
expect(scopesTreeSelect.length).toBe(1);
});
});
});
All of my tests that validate if the inputs were rendered are failing.
As you can see, I tried mocking the form decorator functions, but still no success...
So, my question is: how should I test this component?
If you want to assert only initially rendered info, you can directly import your wrapped component and do:
const component = mount(<WrappedComponent {...props} form={formMock} />);
On the other hand if you don't want to use mock form or want to assert any logic connected with form-observer-wrappedComponent chain you can do next:
const wrapper = mount(<FormWrapperComponent {...props} />);
// props include anything needed for initial mapPropsToFields
const component = wrapper.find(WrappedComponent);
// Do anything with form itself
const { form } = component.instance().props;
Kinda more time consuming but worked perfectly for me, spent some time to understand how to avoid using form mock. In this case you don't need to mock anything connected with Form itself and if any field is not rendered as you expect you can be sure that problem is in another piece of code.

Make Search Box Functional

I'm doing a map project in React and using the google-maps-react api. I am able to type characters in the search box, but it doesn't filter my list or markers. How can I make that work?
Here's the code for my App.js. I have the updateQuery which should update with whatever is typed in the search box. filterItems is supposed to filter all the locations. addRealMarkers is supposed to replace with the filtered markers:
var foursquare = require("react-foursquare")({
clientID: "BTMAGTC2Y5G1IXAKA4VN4QN55R2DSN1105Y1XGHB0WZ5THHR",
clientSecret: "4HOKQ0ON1V1XEHKSUSEABQMNRFZGCGPIKIUIE5JMUMWVRG5W",
url: "https://api.foursquare.com/v2/venues/search?"
});
var params = {
ll: "31.462170,-97.195732",
query: "Hewitt"
};
class App extends Component {
/* Basic state object that must be kept at the highest "parent" level per
Doug Brown's training video */
constructor(props) {
super(props);
this.state = {
lat: 31.46217,
lon: -97.195732,
zoom: 13,
items: [],
filtered: null,
open: false,
selectedId: null,
activeMarker: null
};
}
realMarkers = [];
componentDidMount() {
foursquare.venues.getVenues(params).then(res => {
this.setState({ items: res.response.venues });
});
fetch("react-foursquare")
.then(response => response.json())
.then(response => {
const items = json.response.items;
this.setState({
items,
filtered: this.filterItems(items, "")
});
})
.catch(error => {
alert("Foursquare data could not be retrieved");
});
}
//Fetches the locations requested for this map.
/*fetchPlaces(mapProps, map) {
const { google } = mapProps;
const service = new google.maps.places.PlacesService(map);
}
//fetch Foursquare API data and use Axios to catch errors, instructed by
Yahya Elharony.
// Source: https://github.com/foursquare/react-foursquare
getPlaces = () => {
const endPoint = "https://api.foursquare.com/v2/venues/explore?";
const params = {
client_id: "BTMAGTC2Y5G1IXAKA4VN4QN55R2DSN1105Y1XGHB0WZ5THHR",
client_secret: "4HOKQ0ON1V1XEHKSUSEABQMNRFZGCGPIKIUIE5JMUMWVRG5W",
near: "Hewitt",
query: "query",
v: 20181117
};
// axios site: https://www.npmjs.com/package/axios
axios
.get(endPoint + new URLSearchParams(params))
.then(response => {
this.setState(
{
venues: response.data.response.groups[0].items
},
this.fetchPlaces()
);
})
.catch(error => {
console.log("ERROR! " + error);
});
};*/
// Creating the replacement markers that goes with the list. Based on my
1:1 training from Doug Brown
addRealMarker = marker => {
let checkList = this.realMarkers.filter(
m => m.marker.id === marker.marker.id
);
if (!checkList.length) this.realMarkers.push(marker);
};
updateQuery = query => {
this.setState({
selectedIndex: null,
filtered: this.filterItems(this.state.items, query)
});
};
filterItems = (items, query) => {
return items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
};
clickListItem = id => {
const marker = this.realMarkers.filter(
marker => marker.marker.id === id
)[0];
this.setState({
selectedId: id,
activeMarker: marker
});
};
/*Google Maps React Component courtesy of
https://www.npmjs.com/package/google-maps-react*/
render() {
const style = {
width: "100%",
height: "100%"
};
return (
<div className="App">
<HewittMap
lat={this.state.lat}
lng={this.state.lng}
zoom={this.state.zoom}
style={style}
items={this.state.items}
addRealMarker={this.addRealMarker}
activeMarker={this.state.activeMarker}
clickListItem={this.clickListItem}
/>
<Sidebar
items={this.state.items}
clickListItem={this.clickListItem}
filterItems={this.updateQuery}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
export default App;
And here's the Sidebar Code. Added another updateQuery function that's supposed to call the props then you'll see some more code in the InputBase component:
class Sidebar extends Component {
state = {
mobileOpen: false,
query: ""
};
handleDrawerOpen = () => {
this.setState({ open: true });
};
handleDrawerClose = () => {
this.setState({ open: false });
};
updateQuery = newQuery => {
// Save the new query string in state and pass the string up the call
tree
this.setState({ query: newQuery });
this.props.filterItems(newQuery);
};
render() {
const { classes, theme } = this.props;
const { open } = this.state;
const items = this.props.items;
return (
<div className={classes.root}>
<CssBaseline />
<AppBar
position="fixed"
className={classNames(classes.appBar, {
[classes.appBarShift]: open
})}
>
<Toolbar disableGutters={!open}>
<IconButton
color="inherit"
aria-label="Open drawer"
onClick={this.handleDrawerOpen}
className={classNames(classes.menuButton, open && classes.hide)}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" color="inherit" noWrap>
City of Hewitt
</Typography>
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon places={this.state.places} />
</div>
<InputBase
classes={{
root: classes.inputRoot,
input: classes.inputInput
}}
placeholder="Search…"
name="filter"
type="text"
value={this.state.query}
onChange={e => {
this.updateQuery(e.target.value);
}}
/>
</div>
</Toolbar>
</AppBar>
<Drawer
className={classes.drawer}
variant="persistent"
anchor="left"
open={open}
classes={{
paper: classes.drawerPaper
}}
>
<div className={classes.drawerHeader}>
<IconButton onClick={this.handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</div>
<Divider />
<List>
{this.props.items &&
this.props.items.map((item, index) => {
return (
<ListItem key={item.id}>
<button
key={index}
onClick={e => this.props.clickListItem(item.id)}
>
<ListItemText primary={item.name}> </ListItemText>
</button>
</ListItem>
);
})}
</List>
<Divider />
</Drawer>
<main
className={classNames(classes.content, {
[classes.contentShift]: open
})}
>
<div className={classes.drawerHeader} />
</main>
</div>
);
}
}
Sidebar.propTypes = {
classes: PropTypes.object.isRequired,
// Injected by the documentation to work in an iframe.
// You won't need it on your project.
container: PropTypes.object,
theme: PropTypes.object.isRequired
};
export default withStyles(styles, { withTheme: true })(Sidebar);
You can click in my CodeSandbox to see for yourself.
You are filtering your data and assigning it to filtered but you use items to drive your map, not filtered. It would need more refactoring, but what if you did this?
updateQuery = query => {
this.setState({
selectedIndex: null,
//filtered: this.filterItems(this.state.items, query) // -
items: this.filterItems(this.state.items, query) // +
});
};
You might want an indicator, say isFiltered, that is true when the search bar has a value in it. If true, use the filtered data, else, use the original items

React Downshift autocomplete requests in an infinite loop

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()}
/>

Resources