I am working on an older project. As part of a form the user puts in a text - this text might be longer then the input field is wide. Therefore this field should be stretching over multiple lines to show all its content. It is no problem to make the field bigger, but the content is still only displayed in one line and then cut off.
<div className="small-12 medium-12 large-12">{this.renderCloze()}</div>
<FormikInput
label="Zusatzinformation"
name="checklist.additionalInfo"
readOnly={!!checklist.completed}
rows="2"
type="text"
size="large"
/>
Thats the part where the input field is created. This is the FormikInput:
import { ErrorMessage, Field } from 'formik';
import * as PropTypes from 'prop-types';
import React from 'react';
import FormInput from './FormInput';
export default class FormikInput extends React.Component {
static propTypes = {
component: PropTypes.oneOfType([
PropTypes.element,
PropTypes.elementType,
PropTypes.string,
]),
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
size: PropTypes.string,
};
handleOnChange = (event, onChange) => {
if (!event || !event.target) {
console.warn('FormikInput: empty event', event);
}
onChange && onChange(event);
};
render() {
const { name, component, ...childProps } = this.props;
return (
<Field name={name}>
{({ field }) => (
<FormInput
{...field}
component={component}
errorMessage={<ErrorMessage name={this.props.name} component="div" />}
{...childProps}
onChange={(event) => this.handleOnChange(event, field.onChange)}
/>
)}
</Field>
);
}
}
FormInput.js:
import * as PropTypes from 'prop-types';
import React from 'react';
export default class FormInput extends React.Component {
static propTypes = {
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
component: PropTypes.oneOfType([
PropTypes.element,
PropTypes.elementType,
PropTypes.node,
PropTypes.string,
]),
errorMessage: PropTypes.oneOfType([PropTypes.element, PropTypes.node, PropTypes.string]),
size: PropTypes.string,
value: PropTypes.any,
};
className = () => {
if (this.props.size === 'large') {
return 'small-12 medium-12 large-12';
}
if (this.props.size === 'medium') {
return 'small-12 medium-6 large-6';
}
if (this.props.size === 'third') {
return 'small-12 medium-6 large-4';
}
return this.props.size ? this.props.size : '';
};
id = () => {
if (this.props.id) {
return this.props.id;
}
if (this.props.name) {
return 'FormField__' + this.props.name;
}
return 'FormField__' + Math.random().toString(36);
};
render() {
let { label, errorMessage, component, size, ...childProps } = this.props;
childProps.id = this.id();
if (!component) {
component = 'input';
childProps.type = 'text';
}
const input = React.isValidElement(component)
? React.cloneElement(component, childProps)
: React.createElement(component, childProps);
return (
<div className={this.className()}>
<div className="group">
{input}
<label htmlFor={this.id()}>{this.props.label}</label>
</div>
{typeof this.props.errorMessage === 'string' ? (
<div>{this.props.errorMessage}</div>
) : (
this.props.errorMessage
)}
</div>
);
}
}
Related
I have 2 components OptinPage (parent) and TermsOfServices (child). Optin Page is only used for rendering the TermsOfServices component, which can be reused elsewhere in the application. I want to use my onSucceeded () function from my child component to my parent component. I don't see how to do it at all. Currently the result is such that when I click on the button that validates the TermsOfServices it seems to be an infinite loop, it goes on and on without closing my popup. Before I split my TermsOfServices component into a reusable component it worked fine. Before, all content was gathered in OptinPage. Any ideas? Thanks in advance
my TermsOfServices component:
import API from 'api';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import {
Block,
BlockTitle,
Col,
Fab,
Icon,
Preloader,
} from 'framework7-react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-refetch';
import ReactHtmlParser from 'react-html-parser';
class TermsOfServices extends PureComponent {
static propTypes = {
agreeTosFunc: PropTypes.func.isRequired,
agreeTos: PropTypes.object,
onSucceeded: PropTypes.func,
tos: PropTypes.object.isRequired,
};
static contextTypes = {
apiURL: PropTypes.string,
loginToken: PropTypes.string,
userId: PropTypes.string,
};
static defaultProps = {
agreeTos: {},
onSucceeded: () => {},
};
state = {
currentTos: -1,
};
componentDidUpdate(prevProps) {
const {
agreeTos,
onSucceeded,
tos,
} = this.props;
const { currentTos } = this.state;
/* Prepare for first tos after receiving all of them */
if (
prevProps.tos.pending &&
tos.fulfilled &&
tos.value.length &&
currentTos < 0
) {
this.setState({ currentTos: 0 });
}
/* When sending ToS agreement is done */
if (
prevProps.agreeTos.pending &&
agreeTos.fulfilled
) {
onSucceeded();
}
}
handleNext = () => {
const { agreeTosFunc, tos } = this.props;
const { currentTos: currentTosId } = this.state;
const termsOfServices = tos.value;
const done = currentTosId + 1 === termsOfServices.length;
this.setState({ currentTos: currentTosId + 1 });
if (done) {
agreeTosFunc(termsOfServices.map((v) => v._id));
}
};
render() {
const { tos } = this.props;
const { currentTos: currentTosId } = this.state;
const termsOfServices = tos.value;
const currentTermsOfServices = termsOfServices && termsOfServices[currentTosId];
const loaded = termsOfServices && !tos.pending && tos.fulfilled;
const htmlTransformCallback = (node) => {
if (node.type === 'tag' && node.name === 'a') {
// eslint-disable-next-line no-param-reassign
node.attribs.class = 'external';
}
return undefined;
};
return (
<div>
{ (!loaded || !currentTermsOfServices) && (
<div id="
optin_page_content" className="text-align-center">
<Block className="row align-items-stretch text-align-center">
<Col><Preloader size={50} /></Col>
</Block>
</div>
)}
{ loaded && currentTermsOfServices && (
<div id="optin_page_content" className="text-align-center">
<h1>
<FormattedMessage id="press_yui_tos_subtitle" values={{ from: currentTosId + 1, to: termsOfServices.length }} />
</h1>
<BlockTitle>
{ReactHtmlParser(
currentTermsOfServices.title,
{ transform: htmlTransformCallback },
)}
</BlockTitle>
<Block strong inset>
<div className="tos_content">
{ReactHtmlParser(
currentTermsOfServices.html,
{ transform: htmlTransformCallback },
)}
</div>
</Block>
<Fab position="right-bottom" slot="fixed" color="pink" onClick={() => this.handleNext()}>
{currentTosId + 1 === termsOfServices.length &&
<Icon ios="f7:check" aurora="f7:check" md="material:check" />}
{currentTosId !== termsOfServices.length &&
<Icon ios="f7:chevron_right" aurora="f7:chevron_right" md="material:chevron_right" />}
</Fab>
{currentTosId > 0 && (
<Fab position="left-bottom" slot="fixed" color="pink" onClick={() => this.setState({ currentTos: currentTosId - 1 })}>
<Icon ios="f7:chevron_left" aurora="f7:chevron_left" md="material:chevron_left" />
</Fab>
)}
</div>
)}
</div>
);
}
}
export default connect.defaults(new API())((props, context) => {
const { apiURL, userId } = context;
return {
tos: {
url: new URL(`${apiURL}/tos?outdated=false&required=true`),
},
agreeTosFunc: (tos) => ({
agreeTos: {
body: JSON.stringify({ optIn: tos }),
context,
force: true,
method: 'PUT',
url: new URL(`${apiURL}/users/${userId}/optin`),
},
}),
};
})(TermsOfServices);
My OptIn Page :
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import {
Link,
NavRight,
Navbar,
Page,
Popup,
} from 'framework7-react';
import { FormattedMessage, intlShape } from 'react-intl';
import './OptInPage.scss';
import TermsOfServices from '../components/TermsOfServices';
class OptinPage extends PureComponent {
static propTypes = {
logout: PropTypes.func.isRequired,
opened: PropTypes.bool.isRequired,
};
static contextTypes = {
intl: intlShape,
logout: PropTypes.func,
};
render() {
const { opened, logout } = this.props;
const { intl } = this.context;
const { formatMessage } = intl;
return (
<Popup opened={opened} className="demo-popup-swipe" tabletFullscreen>
<Page id="optin_page">
<Navbar title={formatMessage({ id: 'press_yui_tos_title' })}>
<NavRight>
<Link onClick={() => logout()}>
<FormattedMessage id="press_yui_comments_popup_edit_close" />
</Link>
</NavRight>
</Navbar>
</Page>
<TermsOfServices onSucceeded={this.onSuceeded} />
</Popup>
);
}
}
export default OptinPage;
Just add the data you want the parent to be supplied with in the child component (when it is hit) and then handle the data passed to the parent in the function that you pass in onSuccess.
This will roughly look like this:
const {useState, useEffect} = React;
function App(){
return <Child onSuccess={(data)=>{console.log(data)}}/>;
}
function Child({onSuccess}){
return <div>
<button
onClick={()=>onSuccess("this is the data from the child component")}>
Click to pass data to parent
</button>
</div>;
}
ReactDOM.render(<App/>,document.getElementById('app'));
#element {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id='app'></div>
<div id="element">
<div>node 1</div>
<div>node 2</div>
</div>
to access to parent method or attribute you should use super,
for call to the parent constructor
super([arguments]);
for call parent method
super.parentMethod(arguments);
I recommend create a method on child class and then call the parent method, not directly
for more information take a look on this
https://www.w3schools.com/jsref/jsref_class_super.asp
I have a form that uses a child component with input fields. I have used props to get the data from the parent component, but it's not adding the prop value to the input field. There is no errors in the console.
Parent component:
const {
address,
errors
} = this.state;
return (
<form noValidate autoComplete="off" onSubmit={this.onSubmit.bind(this)}>
<LocationInputGroup
errors={errors}
address={address}
/>
<Button
type="submit"
style={{ marginRight: 10, marginTop: 20 }}
variant="callToAction"
> Submit</Button>
</form>
);
Child component:
constructor(props) {
super(props);
this.state = {
address: "",
errors: {}
};
}
componentDidMount() {
this.setState({
errors: this.props.errors,
address: this.props.address
});
}
render() {
const {
address,
errors
} = this.state;
return (
<div>
<InputGroup
value={address}
error={errors.address}
label="Address"
name={"address"}
onChange={e => this.setState({ address: e.target.value })}
placeholder={"Address"}
/>
</div>
);
}
InputGroup component:
class InputGroup extends Component {
constructor(props) {
super(props);
}
//TODO check if scrolling still changes with number inputs
//Bug was in Chrome 73 https://www.chromestatus.com/features/6662647093133312
//If it's no longer a bug these listeners can be removed and the component changed back to a stateless component
handleWheel = e => e.preventDefault();
componentDidMount() {
if (this.props.type === "number") {
ReactDOM.findDOMNode(this).addEventListener("wheel", this.handleWheel);
}
}
componentWillUnmount() {
if (this.props.type === "number") {
ReactDOM.findDOMNode(this).removeEventListener("wheel", this.handleWheel);
}
}
render() {
const {
disabled,
classes,
error,
value,
name,
label,
placeholder,
type,
isSearch,
onChange,
onBlur,
onFocus,
multiline,
autoFocus,
InputProps = {},
autoComplete,
allowNegative,
labelProps
} = this.props;
if (type === "phone") {
InputProps.inputComponent = PhoneNumberInputMask;
}
let onChangeEvent = onChange;
//Stop them from entering negative numbers unless they explicitly allow them
if (type === "number" && !allowNegative) {
onChangeEvent = e => {
const numberString = e.target.value;
if (!isNaN(numberString) && Number(numberString) >= 0) {
onChange(e);
}
};
}
return (
<FormControl
className={classes.formControl}
error
aria-describedby={`%${name}-error-text`}
>
<FormatInputLabel {...labelProps}>{label}</FormatInputLabel>
<TextField
error={!!error}
id={name}
type={type}
value={value}
onChange={onChangeEvent}
margin="normal"
onBlur={onBlur}
onFocus={onFocus}
InputProps={{
...InputProps,
classes: {
input: classnames({
[classes.input]: true,
[classes.searchInput]: isSearch
})
}
}}
placeholder={placeholder}
multiline={multiline}
autoFocus={autoFocus}
disabled={disabled}
autoComplete={autoComplete}
onWheel={e => e.preventDefault()}
/>
<FormHelperText
className={classes.errorHelperText}
id={`${name}-error-text`}
>
{error}
</FormHelperText>
</FormControl>
);
}
}
InputGroup.defaultProps = {
value: "",
type: "text",
labelProps: {}
};
InputGroup.propTypes = {
error: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
name: PropTypes.string.isRequired,
label: PropTypes.string,
placeholder: PropTypes.string,
type: PropTypes.string,
isSearch: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
multiline: PropTypes.bool,
autoFocus: PropTypes.bool,
InputProps: PropTypes.object,
disabled: PropTypes.bool,
autoComplete: PropTypes.string,
allowNegative: PropTypes.bool,
labelProps: PropTypes.object
};
export default withStyles(styles)(InputGroup);
I hope someone can advise what the issue is and how to overcome it.
Normally when we pass data from parent, we consider it to be Immutable,
(i.e) It should not be changed directly from the child components.
So, what one could do is use the prop value directly from the parent and use a method to mutate or change from the parent.
Below code is self explanatory.
Parent
class Parent {
// parent immutable state
public parentObject: object = {
location: "",
phoneNo: ""
};
constructor() {
this.state = {
parentObject: this.parentObject
}
}
public onParentObjectChangeCallBack(key: string, value: string | number) {
this.state.parentObject[key] = value;
// to rerender
this.setState();
}
public render () {
return <ChildComponent parentObject={this.state.parentObject}
onChange={this.onParentObjectChangeCallBack} />
}
}
Child
class ChildComponent {
#Prop
public parentObject: object;
#Prop
public onChange: Function;
public render() {
<div>
{
this.parentObject.keys.map((key) => {
return <div>
<span> {{key}}</span>
// calls parent method to change the immutable object
<input value={{this.parentObject[key]}} onChange=(val) =>
this.onChange(key, val) />
</div>
})
}
</div>
}
}
I am newbie to react, redux-saga, I have a dropdown in page Display, when I select it move to respective component (eg. policy, score), In Component Pages, I have a button Add New, on clicking it will navigate to a page as mentioned in link url , which is a page having cancel button, on cancelling it returns to the Display.js but no dropdown selected,
I would like to keep the state articleMode, when navigating to a page and returning back to same page,
articleMode returns to state -1 instead of selected component Policy or Score
actions.js
export const updateArticleMode = data => {
console.log(data.body);
return {
type: CONSTANTS.UPDATE_ARTICLE_MODE,
data: data.body
};
};
queryReducer.js
import * as CONSTANTS from "../constants/constants";
const initialState = {
articleMode: ""
}
case CONSTANTS.UPDATE_ARTICLE_MODE: {
return {
...state,
articleMode: data.mode
};
}
export default queryReducer;
constants.js
export const UPDATE_ARTICLE_MODE = "UPDATE_ARTICLE_MODE";
Display.js
import React from "react";
import { connect } from "react-redux";
import Policy from "../policy";
import Score from "./../score";
import { updateArticleMode } from "../../../../actions/actions";
const articleMode = [
{ name: "Select", id: "-1" },
{ name: "Score", id: "Score" },
{ name: "Policy", id: "Policy" }
]
class Display extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
articleMode: "-1"
};
}
componentWillMount = () => {
this.setState({ articleMode: this.props.queryData.articleMode || "-1" });
};
_getComponent = () => {
const { articleMode } = this.state;
if (articleMode === "Policy") {
return <DisplayPolicy></DisplayPolicy>;
}
if (articleMode === "Score") {
return <DisplayScore></DisplayScore>;
}
}
render() {
return (
<React.Fragment>
<select name="example"
className="simpleSearchSelect1"
value={this.state.articleMode}
onChange={event => {
this.setState({ articleMode: event.target.value });
this.props.dispatch(
updateArticleMode({ body: { mode: event.target.value } })
);
}}
style={{ marginLeft: "2px" }}
>
{articleMode.length != 0 &&
articleMode.map((option, index) => {
const { name, id } = option;
return (
<option key={index} value={id}>
{name}
</option>
);
})}
</select>
{this.state.articleMode === "-1"
? this._renderNoData()
: this._getComponent()}
</React.Fragment>
)}
const mapStateToProps = state => {
return {
queryData: state.queryData
};
};
export default connect(mapStateToProps)(Display);
}
DisplayPolicy.js
import React from "react";
class DisplayPrivacyPolicy extends React.Component {
constructor(props) {
super(props);
}<Link
to={{
pathname: "/ui/privacy-policy/addNew",
state: {
language: "en"
}
}}
>
<button className="page-header-btn icon_btn display-inline">
<img
title="edit"
className="tableImage"
src={`${process.env.PUBLIC_URL}/assets/icons/ic_addstore.svg`}
/>
{`Add New`}
</button>
</Link>
AddNew.js
<Link
to =pathname: "/ui/display",
className="btn btn-themes btn-rounded btn-sec link-sec-btn"
>
Cancel
</Link>
I have a array and I want to render the this array into a few redux forms. I found out that all the forms are rerendered. the code looks like the following:
Form.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link, Events, scrollSpy } from 'react-scroll';
import styles from './Form.css';
import MultipleForm from './MultipleForm';
class Form extends Component {
constructor(props) {
super(props);
const {
workflows,
} = this.props;
this.state = {
curTab: workflows.length > 0 ? workflows[0] : '',
curForm: '',
};
}
componentDidMount() {
Events.scrollEvent.register('begin');
Events.scrollEvent.register('end');
scrollSpy.update();
}
componentWillReceiveProps(nextProps) {
const {
workflows,
} = nextProps;
if (workflows && workflows.length > this.props.workflows) {
this.setState({
curTab: workflows[0],
});
}
}
componentWillUnmount() {
Events.scrollEvent.remove('begin');
Events.scrollEvent.remove('end');
}
handleChangeTab = (value) => {
this.setState({
curTab: value,
});
}
handleActiveTab = (workflow) => {
console.log(workflow);
}
render() {
const {
workflows,
schemaNames,
...rest
} = this.props;
return (
<div className={styles.container}>
<header>
<PerspectiveBar
value={this.state.curTab}
onChange={this.handleChangeTab}
style={{
position: 'fixed',
left: '0',
top: '48px',
width: '100vw',
zIndex: '1380',
}}
>
{workflows.map(wf => (
<PerspectiveTab
key={wf}
label={wf}
value={wf}
onActive={() => this.handleActiveTab(wf)}
/>
))}
</PerspectiveBar>
</header>
<div className={styles.formContainer}>
<Paper className={styles.paperContainer}>
<MultipleForm
workflow={this.state.curTab}
schemaNames={schemaNames}
{...rest}
/>
</Paper>
</div>
<Drawer className={styles.drawer} containerStyle={{ height: 'calc(100% - 104px)', top: '104px' }}>
<div className={styles.drawerContainer}>
{schemaNames.map(schemaName => (
<Link
onSetActive={(to) => {
this.setState({
curForm: to,
});
}}
to={schemaName}
duration={500}
offset={-104}
spy
smooth
>
<MenuItem
checked={this.state.curForm === schemaName}
>
{schemaName}
</MenuItem>
</Link>
))}
</div>
</Drawer>
</div>
);
}
}
Form.propTypes = {
schemaNames: PropTypes.arrayOf(PropTypes.string),
workflows: PropTypes.arrayOf(PropTypes.string),
fetchSchemaNames: PropTypes.func.isRequired,
};
Form.defaultProps = {
schemaNames: [],
workflows: [],
};
export default Form;
MultipleForm.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import FlatButton from 'material-ui/FlatButton';
import { Element } from 'react-scroll';
import SchemaForm from './SchemaForm';
class MultipleForm extends Component {
componentDidMount() {
console.log('MultipleForm Mounted');
const {
workflow,
fetchSchemaNames,
} = this.props;
if (workflow) fetchSchemaNames(workflow);
}
componentWillReceiveProps(nextProps) {
const {
workflow,
fetchSchemaNames,
} = nextProps;
if (workflow && this.props.workflow !== workflow) fetchSchemaNames(workflow);
}
componentDidUpdate() {
const {
schemaNames,
schemas,
initialValues,
fetchSchemas,
fetchInitialValues,
} = this.props;
const schemasNeedToFetch = this.remainingSchemas(schemaNames, schemas);
if (schemasNeedToFetch.length !== 0) fetchSchemas(schemasNeedToFetch);
const initialValuesNeedToFetch = this.remainingInitialValues(schemaNames, initialValues);
if (initialValuesNeedToFetch.lenght !== 0) fetchInitialValues(initialValuesNeedToFetch, 1);
}
remainingSchemas = (schemaNames, schemas) =>
schemaNames.filter(schemaName => schemaName in schemas === false).sort();
remainingInitialValues = (schemaNames, initialValues) =>
schemaNames.filter(schemaName => schemaName in initialValues === false).sort();
handleSubmitAll = (event) => {
event.preventDefault();
const {
submit,
schemas,
schemaNames,
} = this.props;
schemaNames
.map(schemaName => schemas[schemaName].title)
.forEach((title) => {
submit(title);
});
}
render() {
const {
schemaNames,
schemas,
initialValues,
postForm,
} = this.props;
schemaNames.sort((a, b) => a.localeCompare(b));
return (
<div>
{schemaNames.map(schemaName => (
<Element name={schemaName}>
<SchemaForm
key={schemaName}
schema={schemas[schemaName]}
initialValue={initialValues[schemaName]}
schemaName={schemaName}
postForm={postForm}
/>
</Element>
))}
<div>
<FlatButton
label="Submit"
/>
<FlatButton label="Deploy" />
</div>
</div>);
}
}
MultipleForm.propTypes = {
workflow: PropTypes.string.isRequired,
submit: PropTypes.func.isRequired,
fetchSchemaNames: PropTypes.func.isRequired,
schemas: PropTypes.object,
schemaNames: PropTypes.arrayOf(PropTypes.string),
initialValues: PropTypes.object,
fetchSchemas: PropTypes.func.isRequired,
fetchInitialValues: PropTypes.func.isRequired,
postForm: PropTypes.func.isRequired,
};
MultipleForm.defaultProps = {
schemaNames: [],
};
export default MultipleForm;
SchemaForm
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Liform from 'liform-react';
import theme from './NokiaTheme';
import styles from './Form.css';
class SchemaForm extends Component {
componentDidMount() {
console.log('schema mounted');
}
shouldComponentUpdate() {
return false;
}
handleSubmit = (value) => {
const {
postForm,
schemaName,
} = this.props;
postForm(value, schemaName, 1);
}
render() {
const {
schema,
initialValue,
} = this.props;
console.log('props', this.props);
return (
<div>
<h3 id={schema.$id} className={styles.formTitle}>
{schema.title}
</h3>
<Liform
schema={schema}
onSubmit={value => this.handleSubmit(value)}
destroyOnUnmount={false}
theme={theme}
initialValues={initialValue}
/>
</div>
);
}
}
SchemaForm.propTypes = {
schema: PropTypes.shape({
$id: PropTypes.string,
}),
initialValue: PropTypes.object,
schemaName: PropTypes.string,
postForm: PropTypes.func.isRequired,
};
SchemaForm.defaultProps = {
schema: {},
initialValue: null,
schemaName: '',
};
export default SchemaForm;
the schemaNames will be changed only by adding or deleting some element. for example: the schemaNames will change from ['A', 'B', 'C'] to ['A', 'B', 'D']. I get the schemaNames from the redux. which I fetch online.
But when I check the ConnectedReduxForm, when I change the schemaNames, the SchemaForm will be unmounted and the react will mount the form again. I have tried with setting the ConnectedReduxForm to be PureComponent. It is not helpful.
Could someone help me with that? I have spent a lot of time of this and nothing helps.
Update: I have found the problem, the reason of it is that for each time that I for each time I update the workflow, I need to fetch the schemaNames from the server. But I still do not know why this happended. Could someone explain that?
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,
};