How to get input value of TextField from Material UI? - javascript

So, before using material UI, my code was like this. To implement edit feature for ToDo app I used ref from textarea for get current (default) value in there, and then save updated value in save () method (don't worry about editItem method, it is in another component, and I believe there is no need to post him, because the problem is not there)
import React, {Component} from 'react';
import './ToDoItem.css';
import EditButton from './EditButton';
import DeleteButton from './DeleteButton';
import SaveButton from './SaveButton';
import EditToDoField from './EditToDoField';
class ToDoItem extends Component {
constructor(props) {
super(props);
this.state = {
editMode: false,
}
};
edit = () => {
this.setState ({editMode: true});
};
save = () => {
let updToDo = this.refs.newToDo.value;
this.props.editItem (updToDo);
this.setState ({
editMode: false})
};
renderNormal = () => {
return (
<div className="ToDoItem">
<p className="ToDoItem-Text">{this.props.todo}</p>
<EditButton editHandler={this.edit} />
</div>
);
};
renderEdit = () => {
return (
<div className="ToDoItem">
<textarea ref="newToDo" defaultValue={this.props.todo}></textarea>
<SaveButton saveHandler={this.save} />
</div>
);
};
render() {
if (this.state.editMode) {
return this.renderEdit ();
} else {
return this.renderNormal ();
}
}
}
export default ToDoItem;
So, now I trying to implement beautiful TextField from material UI, texarea tag was deleted and here is the respective additions to code:
<EditToDoField
ref="newToDo"
defaultToDoValue={this.props.todo}
/>
and EditToDoField component:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
const styles = theme => ({
container: {
display: 'flex',
flexWrap: 'wrap',
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: "61px",
},
});
class OutlinedTextFields extends React.Component {
render() {
const { classes } = this.props;
return (
<form className={classes.container} noValidate autoComplete="off">
<TextField
id="outlined-editToDO"
label="Edit ToDo"
defaultValue={this.props.defaultToDoValue}
className={classes.textField}
multiline
margin="normal"
variant="outlined"
/>
</form>
);
}
}
OutlinedTextFields.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(OutlinedTextFields);
So I pass current (default) value to EditToDoField, but when I'm trying to save updated ToDo text, I got empty field in result. I understand that most probably I just missed something, but still don't get what. I also tried to use "innerRef" and "inputRef" instead of "ref", but no luck. Can you please help me with this stuff ?

Add a simple event handler when the user enters text, you can then use a callback to move the input from the text field to whatever component you want, here's the full example
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
const styles = theme => ({
container: {
display: 'flex',
flexWrap: 'wrap'
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: '61px'
}
});
class OutlinedTextFields extends React.Component {
handleOnChange = event => {
console.log('Click');
console.log(event.target.value);
};
render() {
const { classes } = this.props;
return (
<form className={classes.container} noValidate autoComplete="off">
<TextField
id="outlined-editToDO"
label="Edit ToDo"
defaultValue={this.props.defaultToDoValue}
className={classes.textField}
multiline
margin="normal"
variant="outlined"
onChange={this.handleOnChange}
/>
</form>
);
}
}
OutlinedTextFields.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(OutlinedTextFields);

Related

onClick function fires after clicking twice

For some reason, the onClick will not fire once, but if clicked twice. The this.props.PostLike(id) action will be fired, What should i do to make it work only on one click.
Also, the heart state works fine, in just one click. Its just the this.props.postLike needs to be clicked twice for it work.
And using e.preventDefault() does not solve the issue.
....
clickLike = (id) => {
this.props.postLike(id);
// toggles between css class
this.setState({
heart: !this.state.heart
})
}
render(){
return(
<div style={{float:'right', fontSize: '1.5em', color:'tomato'}} >
<i style={{ marginRight: '140px'}} className={this.state.heart ? 'fa fa-heart':'fa fa-heart-o' }>
<span style={{ marginLeft: '6px'}}>
<a href="#" onClick={() =>this.clickLike(this.props.like)}>Like</a>
</span>
{/* gets the like counts */}
<span style={{ marginLeft: '7px'}} >{this.props.likes} </span>
</i>
</div>
)
}
}
const mapStateToProps = (state) => ({
})
const mapDispatchToProps = (dispatch) => ({
postLike: (id) => dispatch( postLike(id))
// Pass id to the DeletePost functions.
});
export default connect(null, mapDispatchToProps)(Like);
PostItem.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import Editable from './Editable';
import {connect} from 'react-redux';
import {UpdatePost, postLike, getCount} from '../actions/';
import Like from './Like';
import Axios from '../Axios';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
},
button:{
marginRight:'30px'
}
}
class PostItem extends Component{
constructor(props){
super(props);
this.state = {
disabled: false,
myId: 0,
likes:0
}
}
onUpdate = (id, title) => () => {
// we need the id so expres knows what post to update, and the title being that only editing the title.
if(this.props.myTitle !== null){
const creds = {
id, title
}
this.props.UpdatePost(creds);
}
}
render(){
const {title, id, userId, removePost, createdAt, post_content, username, editForm, isEditing, editChange, myTitle, postUpdate, Likes, clickLike, myLikes} = this.props
return(
......
{/* likes get like counts */}
<Like like={id} likes={myLikes} />
.......
Posts.js
render() {
const {loading} = this.state;
const {myPosts} = this.props
console.log(this.state.posts);
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn'/>);
}
if (loading) {
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1>Posts</h1>
{/* <PostList posts={this.state.posts}/> */}
<div>
{this.state.posts.map(post => (
<Paper key={post.id} style={Styles.myPaper}>
<PostItem myLikes={post.Likes.length} // right here
myTitle={this.state.title} editChange={this.onChange} editForm={this.formEditing} isEditing={this.props.isEditingId === post.id} removePost={this.removePost} {...post}/>
</Paper>
))}
</div>
</div>
);
}
}
PostList.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {DeletePost, postLike, UpdatePost,EditChange, GetPosts, getCount, DisableButton} from '../actions/';
import PostItem from './PostItem';
import _ from 'lodash';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
title: '',
posts:[],
loading:true
}
}
componentWillMount() {
this.props.GetPosts();
}
componentWillReceiveProps(nextProps, prevState) {
let hasNewLike = true;
if (prevState.posts && prevState.posts.length) {
for (let index = 0; index < nextProps.myPosts.length; index++) {
if (nextProps.myPosts[index].Likes.length !== prevState.posts[index].Likes.length) {
hasNewLike = true;
}
}
}
if (hasNewLike) {
this.setState({posts: nextProps.myPosts, loading: false}); // here we are updating the posts state if redux state has updated value of likes
}
}
// Return a new function. Otherwise the DeletePost action will be dispatch each
// time the Component rerenders.
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
this.setState({
title: e.target.value
})
}
formEditing = (id) => ()=> {;
this.props.EditChange(id);
}
render(){
const { posts, loading} = this.state;
// console.log(this.props.posts)
// console.log(this.props.ourLikes);
if(loading){
return "loading..."
}
return (
<div>
{this.state.posts.map(post => (
<Paper key={post.id} style={Styles.myPaper}>
<PostItem
myLikes={post.Likes.length} // right here
myTitle={this.state.title}
editChange={this.onChange}
editForm={this.formEditing}
isEditing={this.props.isEditingId === post.id}
removePost={this.removePost}
{...post}
/>
</Paper>
))}
</div>
);
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
myPosts: state.post.posts,
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
GetPosts: () => dispatch(GetPosts()),
EditChange: (id) => dispatch(EditChange(id)),
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
postLike: (id) => dispatch( postLike(id)),
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
// without withRouter componentWillReceiveProps will not work like its supposed too.
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(PostList));
Try doing like that:
clickLike = (id) => () => {
this.props.postLike(id);
// toggles between css class
this.setState({
heart: !this.state.heart
})
}
<a href="#" onClick={this.clickLike(this.props.like)}>Like</a>
This likely has little to do with the logic you have set up and more about the html markup itself. If you were to take in the event and console.log it, you'll probably find that you are actually not hitting the tag on the first click, but one of its outer elements. Try wrapping the tag and everything else inside of it in a tag then putting the onClick logic in the button instead and you'll yield better results.
<div style={{float:'right', fontSize: '1.5em', color:'tomato'}} >
<button onClick={() =>this.clickLike(this.props.like)}>
<i style={{ marginRight: '140px'}} className={this.state.heart ? 'fa fa-heart':'fa fa-heart-o' }>
<span style={{ marginLeft: '6px'}}>
Like
</span>
{/* gets the like counts */}
<span style={{ marginLeft: '7px'}} >{this.props.likes} </span>
</i>
</button>
div>
You have to be careful when using arrow functions with the this keyword as it does not refer to the current object, but its parent object instead.
clickLike = async(id) => {
await this.props.postLike(id);
// toggles between css class
this.setState({
heart: !this.state.heart
})
}
try using the async await
<span style={{ marginLeft: '6px'}} onClick=
{()=>this.clickLike(this.props.like)}>
<a href="#" >Like</a>
</span>

Close MUI Snackbar notification and perform action on keypress

We have created a notification system that uses the material ui Snackbar with an action button and close button. I'm trying to add a listener event for enter so that specific notification's action will fire and close the Snackbar. I attempted to do this when the component is mounted, but the components mount when the application loads they are just not shown until their state is set to open. This resulted in all the actions attached to the Snackbars firing at once. I then attempted to use a ref but had no success. Below I will show the code for the button that calls the notifications and the notification component itself. I'm looking for suggestions on how to close the active Snackbar and fire off its action with enter without activating the other mounted notifications.
UPDATE: I changed the key from enter to spacebar and it works as desired. It seems the issue lies with the enter key itself.
https://material-ui.com/api/root-ref/#__next
import React from 'react';
import { connect } from 'react-redux';
import { withStyles } from '#material-ui/core/styles';
import IconButton from '#material-ui/core/IconButton';
import DeleteIcon from '#material-ui/icons/Delete';
import Tooltip from '#material-ui/core/Tooltip';
import { NotifierConfirm, enqueueInfo } from '#paragon/notification-tools';
import { deleteDocument } from '../../actions/documents';
import { getSelectedDocument } from '../../selectors/documents';
import { jobIsLocked } from '../../modules/jobLocking'; // eslint-disable-line
const styles = ({
border: {
borderRadius: 0,
},
});
class DeleteDocument extends React.Component {
state = {
deleteDocumentOpen: false,
}
onDeleteFile = () => {
if (jobIsLocked()) {
return;
}
this.setState({ deleteDocumentOpen: true });
}
closeDeleteDocument = () => {
this.setState({ deleteDocumentOpen: false });
};
onConfirmDelete = () => {
this.props.onDeleteFile(this.props.selectedDocument.id);
this.setState({ deleteDocumentOpen: false });
}
render() {
const { classes } = this.props;
return (
<div>
<Tooltip disableFocusListener id="delete-tooltip" title="Delete Document">
<div>
<IconButton
className={`${classes.border} deleteDocumentButton`}
disabled={(this.props.selectedDocument == null)}
onClick={this.onDeleteFile}
>
<DeleteIcon />
</IconButton>
</div>
</Tooltip>
<NotifierConfirm
open={this.state.deleteDocumentOpen}
onClose={this.closeDeleteDocument}
onClick={this.onConfirmDelete}
message="Are you sure you want to DELETE this document?"
buttonText="Delete"
/>
</div>
);
}
}
const mapStateToProps = (state) => {
const selectedDocument = getSelectedDocument(state);
return {
selectedDocument,
};
};
function mapDispatchToProps(dispatch) {
return {
onDeleteFile: (documentId) => {
dispatch(deleteDocument(documentId));
},
enqueueInfo,
};
}
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(DeleteDocument));
import React from 'react';
import { withStyles, WithStyles, StyleRulesCallback } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import Snackbar from '#material-ui/core/Snackbar';
import IconButton from '#material-ui/core/IconButton';
import CloseIcon from '#material-ui/icons/Close';
import RootRef from '#material-ui/core/RootRef';
interface NotifierConfirmProps {
open: boolean;
onClose: any;
onClick: () => void;
message: string;
messageSecondary?: any;
buttonText: string;
}
type OwnProps = NotifierConfirmProps & WithStyles<typeof styles>;
const styles: StyleRulesCallback = () => ({
snackbar: {
marginTop: 85,
zIndex: 10000000,
'& div:first-child': {
'& div:first-child': {
width: '100%',
},
},
},
close: {
padding: 8,
marginLeft: 8,
},
buttonColor: {
backgroundColor: '#F3D06E',
},
messageDiv: {
width: '100%',
}
});
class NotifierConfirmComponent extends React.Component<OwnProps> {
notifierRef: React.RefObject<{}>;
constructor(props: OwnProps) {
super(props);
// create a ref to store the textInput DOM element
this.notifierRef = React.createRef();
this.focusNotifier = this.focusNotifier.bind(this);
}
keyPressHandler = (event: any) => {
if (!this.props.open) return;
if (event.keyCode === 27) {
this.props.onClose();
}
if (event.keyCode === 13) {
this.props.onClick();
}
}
focusNotifier() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
// this.notifierRef.current.focus(); this will not work
}
componentDidMount() {
document.addEventListener('keydown', this.keyPressHandler, false);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.keyPressHandler, false);
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<RootRef rootRef={this.notifierRef}>
<Snackbar
className={classes.snackbar}
anchorOrigin={{
vertical: 'top',
horizontal: 'center',
}}
open={this.props.open}
onClose={this.props.onClose}
ContentProps={{
'aria-describedby': 'message-id',
}}
message={
<div className={classes.messageDiv} id="message-id">
{this.props.message}<br />
{this.props.messageSecondary}
</div>}
action={[
<Button
className={`${classes.buttonColor} confirmActionButton`}
variant="contained"
key={this.props.buttonText}
size="small"
onClick={this.props.onClick}
>
{this.props.buttonText}
</Button>,
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={this.props.onClose}
>
<CloseIcon />
</IconButton>,
]}
/>
</RootRef>
</React.Fragment>
);
}
}
export const NotifierConfirm = withStyles(styles)(NotifierConfirmComponent);
The answer for this was changing the event listener to keyup instead of
keydown. Deduced this from this post. Why do Enter and Space keys behave differently for buttons?

How to combine Material-UI's snackbar and input components in react?

I'm using Material-UI components to build my website. I have a header component with a search field which uses mui InputBase under the hood. When user enters empty input (that is they don't enter anything and just click enter) I want to display a mui Snackbar which will warn the user the no meaningful input was entered.
I can't get the combination of the two components to work together. In addition because search field state doesn't really change when user enters nothing it doesn't reload so if user repeatedly presses Enter the snackbar won't appear. I use this.forceUpdate(); but is there a more elegant way to implement such logic?
This is my code:
for the search input field:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import InputBase from '#material-ui/core/InputBase';
import { withStyles } from '#material-ui/core/styles';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { getAppInfo } from '../../actions/appActions.js';
import constants from '../../constants.js';
import { AppSearchBarInputStyles } from '../styles/Material-UI/muiStyles.js';
import AppNotFound from './AppNotFound.js';
class AppSearchBarInput extends Component {
state = {
appId: ''
}
onChange = e => {
this.setState({ appId: e.target.value });
}
onKeyDown = e => {
const { appId } = this.state;
if (e.keyCode === constants.ENTER_KEY && appId !== '') {
this.props.getAppInfo({ appId });
this.setState({
appId: ''
});
}
this.props.history.push('/app/appInfo');
this.forceUpdate();
}
render() {
const { classes } = this.props;
const { appId } = this.state;
console.log(`appId from AppSearchInput=${appId === ''}`);
return (
<div>
<InputBase
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
value={this.state.appId} />
{ appId === '' ? <AppNotFound message={constants.MESSAGES.APP_BLANK()}/> : ''}
</div>
)
}
}
AppSearchBarInput.propTypes = {
classes: PropTypes.object.isRequired
}
const AppSearchBarWithStyles = withStyles(AppSearchBarInputStyles)(AppSearchBarInput);
const AppSearchBarWithStylesWithRouter = withRouter(AppSearchBarWithStyles);
export default connect(null, { getAppInfo })(AppSearchBarWithStylesWithRouter);
for the snackbar:
import React from 'react';
import Snackbar from '#material-ui/core/Snackbar';
import constants from '../../constants.js';
import SnackbarMessage from './SnackbarMessage.js';
class AppNotFound extends React.Component {
state = {
open: true,
};
handleClose = event => {
this.setState({ open: false });
};
render() {
const { message } = this.props;
return (
<Snackbar
anchorOrigin={{
vertical: 'top',
horizontal: 'center',
}}
open={this.state.open}
autoHideDuration={6000}
onClose={this.handleClose}
>
<SnackbarMessage
onClose={this.handleClose}
variant="warning"
message={message}
/>
</Snackbar>
);
}
}
export default AppNotFound;
I think the good way to achieve what You want is by adding another state property called snackBarOpen which will help You to determine if user has entered empty value or something meaningful:
AppSearchBarInput Component
state = {
appId: '',
snackBarOpen: false
}
handleKeyDown = (e) => {
if (e.keyCode === 13 && e.target.value === '') {
this.setState({
appId: e.target.value,
snackBarOpen: true
});
} else {
this.setState({
appId: e.target.value
})
}
}
handleCloseSnackBar = () => {
this.setState({
snackBarOpen: false
});
}
And then just render also <AppNotFound /> in render() method(it will be hidden by default because it will depend on open prop):
render() {
const { snackBarOpen } = this.state;
return(
<div>
/* <InputBase /> here */
<AppNotFound message={/* Your message here */} open={snackBarOpen} onClose={this.handleCloseSnackBar} />
</div>
)
}
AppNotFound Component
You can remove all methods now and leave only render() one which will look next:
render() {
const { message, open, onClose } = this.props;
return (
<Snackbar
// ...
open={open}
// ...
onClose={onClose}
>
<SnackbarMessage
onClose={onClose}
// ...
message={message}
/>
</Snackbar>
);
}
Hope that my answer will be useful :)

How to access main class function from material UI button component?

I want to call function handleKeyPress() from ToDo.js file in AddButton.js file to set action onClick() for a button from Material UI. May be I should set onClick action for button in some different way ? But any types of import/export declarations dos not work for me.
I want to understand some functions access rules, I'm newbie in JS )
Please tell me what is wrong here.
AddButton.js
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import AddIcon from '#material-ui/icons/Add';
import {handleKey} from '../ToDo';
const styles = theme => ({
button: {
margin: theme.spacing.unit,
},
});
function FloatingActionButtons(props) {
const { classes } = props;
return (
<div>
<Button variant="fab" color="primary" aria-label="Add" className={classes.button} onClick={() => (handleKey)}>
<AddIcon />
</Button>
</div>
);
}
FloatingActionButtons.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(FloatingActionButtons);
ToDo.js
import React, {Component} from 'react';
import './ToDo.css';
import ToDoItem from './components/ToDoItem';
import AppBar from './components/AppBar';
import AddButton from './components/AddButton';
import Logo from './assets/logo.png';
const appBar = <AppBar />
const addButton = <AddButton />
class ToDo extends Component {
constructor(props) {
super(props);
this.state = {
list: [
{
title: 'Cup cleaning',
todo: "Wash and take away the Kurzhiy's cup from WC"
},
{
title: 'Smoking rollton',
todo: 'Do some rollton and cigarettes'
},
{
title: 'Curious dream',
todo: 'Build a time machine'
}
],
title: '',
todo: ''
};
};
createNewToDoItem = () => {
this.setState(({ list, title, todo }) => ({
list: [
...list,
{
title,
todo
}
],
title: '',
todo: ''
}));
};
handleKeyPress = e => {
if (e.target.value !== '') {
if (e.key === 'Enter') {
this.createNewToDoItem();
}
}
};
handleTitleInput = e => {
this.setState({
title: e.target.value,
});
};
handleTodoInput = e => {
this.setState({
todo: e.target.value
});
};
deleteItem = indexToDelete => {
this.setState(({ list }) => ({
list: list.filter((toDo, index) => index !== indexToDelete)
}));
};
editItem = (i, updTitle, updToDo) => {
let arr = this.state.list;
arr[i].title = updTitle;
arr[i].todo = updToDo;
this.setState ({list: arr});
};
eachToDo = (item, i) => {
return <ToDoItem
key={i}
title={item.title}
todo={item.todo}
deleteItem={this.deleteItem.bind(this, i)}
editItem={this.editItem.bind(this, i)}
/>
};
render() {
return (
<div className="ToDo">
<img className="Logo" src={Logo} alt="React logo"/>
<h1 className="ToDo-Header">{appBar}</h1>
<div className="ToDo-Container">
<div className="ToDo-Content">
{this.state.list.map(this.eachToDo)}
</div>
<div>
<input type="text" placeholder="Enter new title" value={this.state.title} onChange={this.handleTitleInput} onKeyPress={this.handleKeyPress}/>
<input type="text" placeholder="Enter new todo" value={this.state.todo} onChange={this.handleTodoInput} onKeyPress={this.handleKeyPress}/>
<button className="ToDo-Add" onClick={this.createNewToDoItem}>+</button>
<p>{addButton}</p>
</div>
</div>
</div>
);
}
}
export default ToDo;
export const handleKey = this.handleKeyPress;
console.log(handleKey)
You refer with this to a property or a method not to a variable, so you shouldn't be using this here at all.
Secondly you will have to import handleKeyPress method to your file first to be able to access it, are you sure that the method is in const { classes } = props; here ? and if yes you should then point the onclick={handleKeyPress()} just to one function it is not necessary to create an arrow function which returns a function { classes } and not to the props, then it should be working

React-select clear value while keeping filter

I'm working on a permissions system to let users control who can access/comment/edit a resource, much like what you can find on Google Drive. I'm using a React-Select multi to let the owner of the resource pick users he wants to give access to the resource to.
When I click on an option displayed by react-select, I want this option to be added to my list of allowed users (a list that is handled by another component). This part works, I just used an onChange handler on the select (as you can see on the code below).
export default class AddUsersForm extends Component {
PropTypes = {
onSubmit: PropTypes.func.isRequired,
usersList: PropTypes.array.isRequired, // List of all users in the company
usersInPermissions: PropTypes.array.isRequired, // Users who already have access to the resource
}
handleChange(users){
// Adds new user to the list of allowed users, an updated value for this.props.usersInPermissions will be received
this.props.onSubmit(users);
}
render() {
return (
<form>
<Select
name="users"
multi={true}
options={this.props.usersList.filter(user => !this.props.usersInPermissions.includes(user.id))}
onChange={(users) => this.handleChange(users)}
/>
</form>
);
}
}
This is where I am stuck: once the option has been added, I would like to keep displaying the filter that the user was potentially using while searching for the first option in the text field. The way it works now, the filter is removed and all the options are shown in the dropdown.
Is there any simple way of achieving this with React-Select?
Many thanks!
This code is working. Maybe there are better ways.
// ManageUsers
import React, { PropTypes } from 'react';
import AddUserForm from './AddUserForm'
export default class NoMatch extends React.Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this);
let selectedUsers = [ { value: 3, label: 'Three' },
{ value: 4, label: 'Four' } ];
this.state = {
selectedUsers: selectedUsers
}
}
handleChange(selected) {
this.setState({ selectedUsers: selected })
}
render() {
let usersList = [
{ value: 1, label: 'One' },
{ value: 2, label: 'Two' }
];
return (
<div>Users
<AddUserForm usersList={usersList}
selectedUsers={this.state.selectedUsers}
handleChange={this.handleChange} />
</div>
);
}
}
// AddUsersForm
import React, { PropTypes } from 'react';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
export default class AddUsersForm extends React.Component {
PropTypes = {
usersList: PropTypes.array.isRequired,
selectedUsers: PropTypes.array.isRequired,
handleChange: PropTypes.func.isRequired
}
render() {
return (
<form>
<Select
multi={true}
options={this.props.usersList}
value={this.props.selectedUsers}
onChange={(users) => this.props.handleChange(users)}
/>
</form>
);
}
}
If you want to keep the typed text then you have to set the text of the input on the handleChange. There is no build in function to keep the typed text.
onChange={(users) => this.props.handleChange(users, event)}
handleChange(selected, event) {
let selectedFilter = event.target;
// then navigated to the input element with Javascript or jQuery
// and set the value of the input
this.setState({ selectedUsers: selected })
}
My way:
Replaced Option component with own component (several components from Material-UI library).
Overrided onClick event handler - here is some logic and call onChange handler from ReactSelect props. At the end of theonClick handler added event.stopPropagation()
import React from 'react';
import MenuItem from '#material-ui/core/MenuItem/MenuItem';
import Checkbox from '#material-ui/core/Checkbox/Checkbox';
import ListItemText from '#material-ui/core/ListItemText/ListItemText';
const MultiOption = props => (
<MenuItem
buttonRef={props.innerRef}
{...props.innerProps}
onClick={event => {
let values = [];
if (props.isSelected) {
values = props.selectProps.value.filter(
item => item.value !== props.value,
);
} else {
values = [props.data].concat(props.selectProps.value);
}
props.selectProps.onChange(values);
event.stopPropagation();
}}
style={{
overflow: 'initial',
padding: 0,
}}
>
<Checkbox
checked={props.isSelected}
style={{
padding: 4,
}}
/>
<ListItemText
primary={props.label}
classes={{
root: props.selectProps.classes.optionRoot,
}}
/>
</MenuItem>
);
export default MultiOption;
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
import { withStyles } from '#material-ui/core/styles';
import { getComponents } from './components';
import { styles, getSelectStyles } from './styles';
class Combobox extends React.Component {
handleChange = () => value => {
const { onChange } = this.props;
onChange(value);
};
render() {
const {
classes,
theme,
options,
label,
rootStyle,
value,
error,
isInner,
isMulti,
fullWidth,
...props
} = this.props;
return (
<div className={classes.root} style={{ ...rootStyle }}>
<Select
{...props}
isClearable
classes={classes}
styles={getSelectStyles({
theme,
fullWidth,
})}
options={options}
menuPortalTarget={document.body}
menuPlacement="auto"
value={value || null}
onChange={this.handleChange()}
components={getComponents({
isInner,
isMulti,
})}
textFieldProps={{
label,
error: !!error,
helperText: error,
InputLabelProps: { shrink: true },
}}
isMulti={isMulti}
hideSelectedOptions={!isMulti}
closeMenuOnSelect={!isMulti}
loadingMessage={() => 'Loading...'}
/>
</div>
);
}
}
Combobox.propTypes = {
options: PropTypes.arrayOf(PropTypes.shape({})),
label: PropTypes.string,
classes: PropTypes.shape({}).isRequired,
theme: PropTypes.shape({}).isRequired,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.arrayOf(PropTypes.any),
PropTypes.shape({}),
]),
error: PropTypes.string,
onChange: PropTypes.func.isRequired,
isInner: PropTypes.bool,
isMulti: PropTypes.bool,
fullWidth: PropTypes.bool,
};
Combobox.defaultProps = {
options: [],
label: '',
value: null,
error: '',
isInner: false,
isMulti: false,
fullWidth: false,
};
export default withStyles(styles, { withTheme: true })(({ ...props }) => (
<Combobox {...props} />
));
import Control from './Control';
import InnerControl from './InnerControl';
import InputComponent from './InputComponent';
import MenuList from './MenuList';
import Option from './Option';
import MultiOption from './MultiOption';
import SingleValue from './SingleValue';
import MultiValue from './MultiValue';
import NoOptionsMessage from './NoOptionsMessage';
import Placeholder from './Placeholder';
import ValueContainer from './ValueContainer';
const getComponents = ({ isInner, isMulti }) => ({
Control: isInner ? InnerControl : Control,
...(isMulti && { MenuList }),
MultiValue,
NoOptionsMessage,
Option: isMulti ? MultiOption : Option,
Placeholder,
SingleValue,
ValueContainer,
});
export {
Control,
InnerControl,
InputComponent,
MenuList,
Option,
MultiOption,
SingleValue,
MultiValue,
NoOptionsMessage,
Placeholder,
ValueContainer,
getComponents,
};

Categories