How to test components wrapped in an antd Form? - reactjs

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.

Related

React.js: state is not updating in parent component

I have search filter and categories. I just want to have a possibility to reset state in single page application.
Due to React.js I guess I do everything correct to pass state from parent to child and then from child to parent. But, unfortunately, something is going wrong. I tried a lot and what I discovered, that onAddCategory() in DropdownGroup doesn't update current state.
Sorry in advance, I add whole code, my be something there could affect this. But i guess you can see first halfs of two codes and it will be enough.
Thank you in advance.
I have parent component:
class DropdownGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: [], // we have empty array, that pass to CategoryDropdown
};
this.onAddCategory = this.onAddCategory.bind(this);
}
onAddCategory(newCategory) {
this.setState(() => ({
categories: newCategory,
}));
}
onSelectCategory(path) {
this.props.onChangeEvents(path);
}
render() {
const months = ['January', 'February' ... ];
const eventsType = ['Party', 'Karaoke ... ];
const { categories } = this.state;
return (
<ButtonToolbar className="justify-content-center pb-4 pt-4">
{ console.log(categories) }
<CategoryDropdown
items={eventsType}
homePath="events"
path="events/categories/"
categories={categories} // here we pass our empty array (or updated later)
addCategories={this.onAddCategory} // this is what helps to update our array
onApply={(path) => this.onSelectCategory(path)}
/>
<MyDropdown
id="sort-by-month"
name="By month"
items={months}
onSelect={(e) => this.onSelectCategory(`events/month/${e}`)}
/>
<DropdownWithDate
oneDate="events/date/"
rangeDate="events/dates?from="
onApply={(path) => this.onSelectCategory(path)}
/>
<Button
onClick={() => this.setState({ categories: [] })} // here we can reset the value of our array
className="m-button ml-5"
>
Reset
</Button>
</ButtonToolbar>
);
}
}
DropdownGroup.propTypes = {
onChangeEvents: PropTypes.any.isRequired,
};
export default DropdownGroup;
and this is child component
class CategoryDropdown extends Component {
constructor(props) {
super(props);
this.state = {
visible: false,
selected: this.props.categories, // here we get values from props (now empty, then updated values)
};
this.onVisibleChange = this.onVisibleChange.bind(this);
}
onVisibleChange(visible) {
this.setState({
visible: visible,
});
}
saveSelected(selectedKeys) {
this.setState({
selected: selectedKeys,
});
}
addCategories() {
this.props.addCategories(this.state.selected); // here props are updated
}
confirm() {
const { selected } = this.state;
this.addCategories(this.state.selected);
const { homePath, path } = this.props;
if (selected.length > 0) {
this.props.onApply(path + selected);
} else {
this.props.onApply(homePath);
}
this.onVisibleChange(false);
}
render() {
const { visible } = this.state;
const { items } = this.props;
const menu = (
<Menu
multiple
onSelect={(e) => { this.saveSelected(e.selectedKeys); }}
onDeselect={(e) => { this.saveSelected(e.selectedKeys); }}
>
{items.map((item) => (
<MenuItem
key={item.replace('\u0020', '\u005f').toLowerCase()}
>
{item}
</MenuItem>
))}
<Divider />
<MenuItem disabled>
<Container
className="text-center "
style={{
cursor: 'pointer',
pointerEvents: 'visible',
}}
onClick={() => {
this.confirm();
}}
>
Select
</Container>
</MenuItem>
</Menu>
);
return (
<Dropdown
trigger={['click']}
onVisibleChange={this.onVisibleChange}
visible={visible}
closeOnSelect={false}
overlay={menu}
>
<Button className="m-button">By Category</Button>
</Dropdown>
);
}
}
CategoryDropdown.propTypes = {
onApply: PropTypes.any.isRequired,
items: PropTypes.any.isRequired,
path: PropTypes.string.isRequired,
homePath: PropTypes.string.isRequired,
categories: PropTypes.array.isRequired,
addCategories: PropTypes.any.isRequired,
};
export default CategoryDropdown;

Function not defined while passing child data to parent

I am trying to pass a query from a child component to a parent component where my parent component can complete an api call. I have search online and found that people have been creating a function in the parent component and calling it in the child component to pass the data via props but my code is not working. When i try the solutions suggested I get a call that my function getArtistName() is not defined in the child component. I am new to react and would like to know why and how this code works.
Parent component
class App extends Component {
state = {
items: [],
didLoad: false,
query: ''
}
async grabArtistInfo() {
const url = `https://api.songkick.com/api/3.0/artists/4971683/calendar.json?apikey=${myKey}`
const response = await fetch(url);
const data = await response.json();
this.setState({items:data, didLoad:true});
console.log(this.state.items)
}
async componentDidMount() {
this.grabArtistInfo()
}
getArtistName = (artist) => {
this.setState({query: artist});
console.log(this.state.query);
}
render() {
if (this.state.didLoad == true) {
return (
<div className="App">
<Header q = {(fromChild) => this.getArtistName(fromChild)} />
<Element name = 'Featured'>
<Featured
deadline = {this.state.items.resultsPage.results.event[0].start.datetime}
/>
</Element>
<Element name = "Venue_info">
<VenueInfo/>
</Element>
<Element name = "Highlights">
<Highlights/>
</Element>
<Element name = "Pricing">
<Pricing/>
</Element>
<Element name = "Location">
<Location
lng = {this.state.items.resultsPage.results.event[0].venue.lng}
lat = {this.state.items.resultsPage.results.event[0].venue.lat}
desc = {this.state.items.resultsPage.results.event[0].venue.displayName}
locationDetails = {this.state.items.resultsPage.results.event[0].location.city}
/>
</Element>
<Footer/>
</div>
);
}
else {
return (
<div>Loading ... </div>
)
}
}
}
export default App;
Child component
class Header extends Component {
constructor(props) {
super(props);
this.state = {
drawerOpen: false,
headerShow: false,
fromChild: ''
};
}
componentDidMount(){
window.addEventListener('scroll',this.handleScroll);
}
handleScroll = () => {
if(window.scrollY > 0){
this.setState({
headerShow: true
})
} else {
this.setState({
headerShow: false
})
}
}
toggleDrawer = (value) => {
this.setState({
drawerOpen: value
})
}
handleChange = (e) => {
if(e.keyCode == 13){
this.setState({query: e.target.value})
this.props.getArtistName(this.state.query)
} else {
this.setState({ query: e.target.value });
}
}
render() {
return (
<AppBar
position="fixed"
style={{
backgroundColor: this.state.headerShow ? '#2f2f2f' : 'transparent',
boxShadow: 'none',
padding: '10px 0px'
}}
>
<Toolbar>
<div className="header_logo">
<div className="font_righteous header_logo_venue">Musical Events</div>
<div className="header_logo_title">Countdown</div>
</div>
<div style = {searchStyle}>
<InputBase
placeholder= "Search…"
inputProps= {{
'aria-label': 'search',
style:{color: "white"}
}}
value = {this.state.query}
onChange = {this.handleChange}
onKeyDown = {this.handleChange}
>
</InputBase>
</div>
<IconButton
aria-label="Search"
color = "inherit"
onClick = {() => console.log(this.state.query)}
>
<SearchIcon/>
</IconButton>
<IconButton
aria-label="Menu"
color="inherit"
onClick={()=> this.toggleDrawer(true)}
>
<MenuIcon/>
</IconButton>
<SideDrawer
open={this.state.drawerOpen}
onClose={(value)=> this.toggleDrawer(value)}
/>
</Toolbar>
</AppBar>
);
}
}
export default Header;
For reference I am using materialUI in react to create the search bar. The query state is fulfilling its purpose but passing it into the parent component is proving quite challenging
You passed your getArtistName in q so you need to access through this.props.q
handleChange = (e) => {
if(e.keyCode == 13){
this.setState({query: e.target.value})
this.props.q(this.state.query)
} else {
this.setState({ query: e.target.value });
}
}

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(

Material UI Select component not picking input value

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 });
};

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

Resources