-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* New: Allow the <input> element to be controlled (fixes #21) This commit implements functionality that allows the <input> element rendered by the MUI <TextField> component to be controlled externally. This is done by passing the value of the <input> element as the 'inputValue' prop on the <Downshift> component we render. This commit satisfies the work of: #21 modified: README.md modified: demo/Demo.jsx copied: demo/Demo.jsx -> demo/DemoBasic.jsx new file: demo/DemoControlledInput.jsx new file: demo/rootReducer.js modified: package.json modified: src/MUIPlacesAutocomplete.jsx modified: test/test.jsx modified: yarn.lock * New: Allow the <input> element to be controlled, part deux (fixes #21) PR #22 sought to introduce new functionality that would allow the <input> element rendered by the MUI <TextField> component to be controlled externally. After some initial feedback and talking to Matt about it some it was obvious that I went about implementing this functionality incorrectly in commit e27d465. After re-reading the docs for Downshift and MUI <TextField> this commit re-implements the functionality in a more consistent fashion with how Downshift/MUI are documented/expected to be used. modified: README.md modified: demo/DemoControlledInput.jsx modified: src/MUIPlacesAutocomplete.jsx modified: test/test.jsx
- Loading branch information
Showing
9 changed files
with
392 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,45 +1,88 @@ | ||
import React from 'react' | ||
import Snackbar from 'material-ui/Snackbar' | ||
import MUIPlacesAutocomplete from './../dist' | ||
// ESLint rule config for demo file | ||
/* eslint import/no-extraneous-dependencies: 0 */ | ||
// Our demo files showcase how to integrates with other 3rd party libraries often. The 3rd party | ||
// library dependencies are added to the 'devDependencies' of 'package.json' so disable the | ||
// 'import/no-extraneous-dependencies' rule in this demo file so we don't have to hear about them | ||
// not being in 'dependencies' all the time. | ||
import React, { createElement } from 'react' | ||
import PropTypes from 'prop-types' | ||
import { createStore } from 'redux' | ||
import { Provider } from 'react-redux' | ||
import { withStyles } from 'material-ui/styles' | ||
import Grid from 'material-ui/Grid' | ||
import { MenuItem } from 'material-ui/Menu' | ||
import Select from 'material-ui/Select' | ||
import Typography from 'material-ui/Typography' | ||
|
||
import rootReducer from './rootReducer' | ||
import DemoBasic from './DemoBasic' | ||
import DemoControlledInput from './DemoControlledInput' | ||
|
||
// Map of demos that one can select to view | ||
const demos = { | ||
[DemoBasic.name]: { description: DemoBasic.description, component: DemoBasic }, | ||
[DemoControlledInput.name]: { | ||
description: DemoControlledInput.description, | ||
component: DemoControlledInput, | ||
}, | ||
} | ||
|
||
const store = createStore(rootReducer) | ||
|
||
const demoStyles = { | ||
container: { | ||
marginTop: 32, | ||
}, | ||
} | ||
|
||
class Demo extends React.Component { | ||
constructor() { | ||
super() | ||
|
||
this.state = { open: false, suggestion: null } | ||
|
||
this.onClose = this.onClose.bind(this) | ||
this.onSuggestionSelected = this.onSuggestionSelected.bind(this) | ||
} | ||
this.state = { selectedDemo: DemoBasic } | ||
|
||
onClose() { | ||
this.setState({ open: false, suggestion: null }) | ||
this.onChange = this.onChange.bind(this) | ||
} | ||
|
||
onSuggestionSelected(suggestion) { | ||
// Add your business logic here. In this case we simply set our state to show our <Snackbar>. | ||
this.setState({ open: true, suggestion }) | ||
onChange(event) { | ||
this.setState({ selectedDemo: demos[event.target.value].component }) | ||
} | ||
|
||
render() { | ||
const { open, suggestion } = this.state | ||
const { selectedDemo } = this.state | ||
const { classes: { container } } = this.props | ||
|
||
return ( | ||
<div> | ||
<MUIPlacesAutocomplete | ||
onSuggestionSelected={this.onSuggestionSelected} | ||
renderTarget={() => (<div />)} | ||
/> | ||
<Snackbar | ||
onRequestClose={this.onClose} | ||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} | ||
autoHideDuration={5000} | ||
open={open} | ||
message={suggestion ? (<span>Selected suggestion: {suggestion.description}</span>) : ''} | ||
/> | ||
</div> | ||
<Provider store={store}> | ||
<div> | ||
<Grid container className={container}> | ||
<Grid item xs={3} /> | ||
<Grid item xs={6}> | ||
<Typography type="display1" align="center">Select a demo</Typography> | ||
<Select | ||
fullWidth | ||
value={selectedDemo.name} | ||
onChange={this.onChange} | ||
> | ||
{Object.entries(demos).map(kvp => | ||
<MenuItem key={kvp[0]} value={kvp[0]}>{kvp[1].description}</MenuItem>)} | ||
</Select> | ||
</Grid> | ||
<Grid item xs={3} /> | ||
</Grid> | ||
<div className={container}> | ||
{createElement(selectedDemo)} | ||
</div> | ||
</div> | ||
</Provider> | ||
) | ||
} | ||
} | ||
|
||
export default Demo | ||
Demo.propTypes = { | ||
classes: PropTypes.shape({ | ||
container: PropTypes.string, | ||
}).isRequired, | ||
} | ||
|
||
export default withStyles(demoStyles)(Demo) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import React from 'react' | ||
import Snackbar from 'material-ui/Snackbar' | ||
import MUIPlacesAutocomplete from './../dist' | ||
|
||
class DemoBasic extends React.Component { | ||
constructor() { | ||
super() | ||
|
||
this.state = { open: false, suggestion: null } | ||
|
||
this.onClose = this.onClose.bind(this) | ||
this.onSuggestionSelected = this.onSuggestionSelected.bind(this) | ||
} | ||
|
||
onClose() { | ||
this.setState({ open: false }) | ||
} | ||
|
||
onSuggestionSelected(suggestion) { | ||
// Add your business logic here. In this case we simply set our state to show our <Snackbar>. | ||
this.setState({ open: true, suggestion }) | ||
} | ||
|
||
render() { | ||
const { open, suggestion } = this.state | ||
|
||
return ( | ||
<div> | ||
<MUIPlacesAutocomplete | ||
onSuggestionSelected={this.onSuggestionSelected} | ||
renderTarget={() => (<div />)} | ||
/> | ||
<Snackbar | ||
onRequestClose={this.onClose} | ||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} | ||
autoHideDuration={5000} | ||
open={open} | ||
message={suggestion ? (<span>Selected suggestion: {suggestion.description}</span>) : ''} | ||
style={{ width: '70vw' }} | ||
/> | ||
</div> | ||
) | ||
} | ||
} | ||
|
||
DemoBasic.description = 'Basic usage' | ||
|
||
export default DemoBasic |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// ESLint rule config for demo file | ||
/* eslint import/no-extraneous-dependencies: 0 */ | ||
// Our demo files showcase how to integrates with other 3rd party libraries often. The 3rd party | ||
// library dependencies are added to the 'devDependencies' of 'package.json' so disable the | ||
// 'import/no-extraneous-dependencies/ rule in this demo file so we don't have to hear about them | ||
// not being in 'dependencies' all the time. | ||
import React from 'react' | ||
import PropTypes from 'prop-types' | ||
import { Field, reduxForm } from 'redux-form' | ||
import Button from 'material-ui/Button' | ||
import Snackbar from 'material-ui/Snackbar' | ||
import MUIPlacesAutocomplete from './../dist' | ||
|
||
// Stateless function that we pass to the 'component' prop of the <Field> to render | ||
// <MUIPlacesAutocomplete>. By passing the props that <Field> passes our stateless function as the | ||
// 'textFieldProps' on <MUIPlacesAutocomplete> we are essentially stating that the <Field> would | ||
// like to control the state of the resulting <input> element that gets rendered. This is due to the | ||
// addition of the 'value' property that gets spread onto the object passed to the 'textFieldProps' | ||
// prop. | ||
// | ||
// It is important to define this stateless function outside of the method that renders the actual | ||
// <Field> to avoid causing it to re-render. For more info please refer to the following docs: | ||
// https://redux-form.com/7.2.0/docs/api/field.md/#2-a-stateless-function | ||
const renderMUIPlacesAutocomplete = ({ onSuggestionSelected, ...other }) => ( | ||
<MUIPlacesAutocomplete | ||
onSuggestionSelected={onSuggestionSelected} | ||
renderTarget={() => ( | ||
<div | ||
style={{ | ||
display: 'flex', | ||
flexDirection: 'column', | ||
alignItems: 'center', | ||
marginTop: 16, | ||
}} | ||
> | ||
<Button raised color="primary" type="submit">Submit</Button> | ||
</div> | ||
)} | ||
textFieldProps={{ ...other }} | ||
/> | ||
) | ||
|
||
renderMUIPlacesAutocomplete.propTypes = { | ||
onSuggestionSelected: PropTypes.func.isRequired, | ||
input: PropTypes.object.isRequired, | ||
} | ||
|
||
const DemoControlledInput = (props) => { | ||
const { change, handleSubmit } = props | ||
|
||
// Since we are controlling the state of the <input> element via Redux Form we want to ensure that | ||
// the <input> elements state is consistent with any suggestions a user may select. To do so we | ||
// dispatch an action to Redux Form to update the <Field> with a name of 'demoField'. For more | ||
// info see: https://redux-form.com/7.2.1/docs/api/actioncreators.md/ | ||
const onSuggestionSelected = (suggestion) => { | ||
change('demoField', suggestion.description) | ||
} | ||
|
||
return ( | ||
<form onSubmit={handleSubmit}> | ||
<Field | ||
fullWidth | ||
name="demoField" | ||
autoFocus={false} | ||
placeholder="Search for a place" | ||
onSuggestionSelected={onSuggestionSelected} | ||
component={renderMUIPlacesAutocomplete} | ||
/> | ||
</form> | ||
) | ||
} | ||
|
||
DemoControlledInput.propTypes = { | ||
// Injected by Redux Form | ||
change: PropTypes.func.isRequired, | ||
handleSubmit: PropTypes.func.isRequired, | ||
} | ||
|
||
const ConnectedDemoControlledInput = reduxForm({ | ||
form: 'DemoControlledInput', | ||
})(DemoControlledInput) | ||
|
||
class DemoControlledInputContainer extends React.Component { | ||
constructor() { | ||
super() | ||
|
||
this.state = { open: false, formValues: { } } | ||
|
||
this.onClose = this.onClose.bind(this) | ||
this.onSubmit = this.onSubmit.bind(this) | ||
} | ||
|
||
onClose() { | ||
this.setState({ open: false }) | ||
} | ||
|
||
onSubmit(formValues) { | ||
this.setState({ open: true, formValues }) | ||
} | ||
|
||
render() { | ||
const { open, formValues } = this.state | ||
|
||
return ( | ||
<div> | ||
<ConnectedDemoControlledInput onSubmit={this.onSubmit} /> | ||
<Snackbar | ||
onRequestClose={this.onClose} | ||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} | ||
autoHideDuration={5000} | ||
open={open} | ||
message={`You submitted: ${JSON.stringify(formValues, null, 2)}`} | ||
style={{ width: '70vw' }} | ||
/> | ||
</div> | ||
) | ||
} | ||
} | ||
|
||
DemoControlledInputContainer.description = '"Controlled" input state via Redux Form' | ||
|
||
export default DemoControlledInputContainer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// ESLint rule config for demo file | ||
/* eslint import/no-extraneous-dependencies: 0 */ | ||
// Our demo files showcase how to integrates with other 3rd party libraries often. The 3rd party | ||
// library dependencies are added to the 'devDependencies' of 'package.json' so disable the | ||
// 'import/no-extraneous-dependencies/ rule in this demo file so we don't have to hear about them | ||
// not being in 'dependencies' all the time. | ||
import { combineReducers } from 'redux' | ||
import { reducer as FormReducer } from 'redux-form' | ||
|
||
// If any demos need reducers they ought to add them in here. Having a separate file for our | ||
// reducers seems like a bit much, but at the same time having them in the main <Demo> component | ||
// seems wrong as well... | ||
const rootReducer = combineReducers({ | ||
form: FormReducer, | ||
}) | ||
|
||
export default rootReducer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.