react-select createable option - adding group label - javascript

I've created this cool sandbox that utilises react-select and the creatable feature. It allows you to select from a prepopulated dropdown and at the same time create a custom option by typing into the select field. Once you have typed into the field your option becomes available in the select list.
I have added in options to be grouped - pre-existing fields are fine, but new options I would like to be grouped by a default value e.g. "new group".
Any help would be appreciated.
https://codesandbox.io/s/p5x7m478rm
import React from "react";
import { Field, reduxForm, FieldArray } from "redux-form";
import TextField from "material-ui/TextField";
import { RadioButton, RadioButtonGroup } from "material-ui/RadioButton";
import Checkbox from "material-ui/Checkbox";
import SelectField from "material-ui/SelectField";
import MenuItem from "material-ui/MenuItem";
import asyncValidate from "./asyncValidate";
import validate from "./validate";
import CreatableSelect from "react-select/lib/Creatable";
const CustomStyle = {
option: (base, state) => ({
...base,
display: "inline",
marginRight: "10px",
backgroundColor: state.isSelected ? "#00285C" : "#eee",
cursor: "pointer"
}),
menuList: () => ({
padding: 10,
display: "inline-flex"
}),
menu: () => ({
position: "relative"
})
};
const createOption = (label: string) => ({
label,
value: label.toLowerCase().replace(/\W/g, "")
});
const formatGroupLabel = data => (
<div>
<span>{data.label}</span>
</div>
);
class LastNameSelectInput extends React.Component {
constructor(props) {
super(props);
}
state = {
value: this.props.options[0].options,
options: this.props.options
};
handleCreate = input => (inputValue: any) => {
this.setState({ isLoading: true });
setTimeout(() => {
const { options, value } = this.state;
const newOption = createOption(inputValue);
this.setState({
isLoading: false,
options: [...options, newOption],
value: newOption,
formatGroupLabel: "new label"
});
input.onChange(newOption);
}, 1000);
};
isValidNewOption = (inputValue, selectValue, selectOptions) => {
if (
inputValue.trim().length === 0 ||
selectOptions.find(option => option.name === inputValue)
) {
return false;
}
return true;
};
render() {
const { input, options } = this.props;
return (
<div>
<style>
{`.react-select__dropdown-indicator,.react-select__indicator-separator {
display: none;
}`}
</style>
<CreatableSelect
classNamePrefix="react-select"
options={this.state.options}
menuIsOpen={true}
onChange={value => {
let newValue = input.onChange(value);
this.setState({ value: newValue });
}}
onBlur={() => input.onBlur(input.value)}
onCreateOption={this.handleCreate(input)}
value={this.state.value}
styles={CustomStyle}
isClearable
isValidNewOption={this.isValidNewOption}
formatGroupLabel={formatGroupLabel}
/>
</div>
);
}
}
const MaterialUiForm = props => {
const { handleSubmit, options, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<div>
<Field name="option" component={LastNameSelectInput} {...props} />
</div>
</form>
);
};
export default reduxForm({
form: "MaterialUiForm", // a unique identifier for this form
validate,
asyncValidate
})(MaterialUiForm);

To achieve your goal I have changed the function handleCreate you have provided and the options props. You can see a live example here.
In MaterialUiForm.js
handleCreate = input => (inputValue: any) => {
this.setState({ isLoading: true });
setTimeout(() => {
const { options } = this.state;
const newOption = createOption(inputValue);
options.map(option => {
if (option.label === "New group") {
return {
label: option.label,
options: option.options.push(newOption)
};
}
return option;
});
this.setState({
isLoading: false,
options: [...options],
value: newOption,
formatGroupLabel: "new label"
});
input.onChange(newOption);
}, 1000);
In index.js
<MaterialUiForm
onSubmit={showResults}
initialValues={{
option: colourOptions,
option: flavourOptions
}}
options={[
{
label: "New group",
options: []
},
{
label: "Colours",
options: colourOptions
},
{
label: "Flavours",
options: flavourOptions
}
]}
/>
There is different and probably smarter way to do it but the logic is the good one.

Related

Reset table using reset button

I'm new React developer(mainly with hooks but did not find good example with hooks), here i have antd table with search functionality, my question is when user writes something in search then user gets different result, how to cancel that search by clicking 'Reset' button ?
my code:
https://codesandbox.io/s/antd-table-filter-search-forked-mqhcn?file=/src/EventsSection/EventsSection.js
You can add an id to your input into TitleSearch.js:
<Search
id='IDYOUWANT'
placeholder="Enter Title"
onSearch={onSearch}
onChange={onChange}
style={{ width: 200 }}
/>
And add event into EventsSection.js
ResetInput = () => {
const input = document.getElementById('IDYOUWANT');
input.value = '';
this.handleSearch('');
}
....
<button
onClick={this.ResetInput}
>Reset</button>
Change IDYOUWANT with your id
run this code
Created a new function for reset value and trigger it from reset button.
function:
resetValue = () =>{
this.setState({
eventsData: eventsData
});
}
And trigger from button
<button onClick={this.resetValue}>Reset</button>
all code::
import React, { Component } from "react";
import styles from "./style.module.css";
import { EventsTable } from "../EventsTable";
import { StatusFilter } from "../StatusFilter";
import { TitleSearch } from "../TitleSearch";
const eventsData = [
{
key: 1,
title: "Bulletproof EP1",
fileType: "Atmos",
process: "match media",
performedBy: "Denise Etridge",
operationNote: "-",
updatedAt: "26/09/2018 17:21",
status: "complete"
},
{
key: 2,
title: "Dexter EP2",
fileType: "Video",
process: "Compliance",
performedBy: "Dane Gill",
operationNote: "passed",
updatedAt: "21/09/2018 12:21",
status: "inProgress"
}
];
class EventsSection extends Component {
constructor(props) {
super(props);
this.state = {
eventsData
};
}
handleFilter = (key) => {
const selected = parseInt(key);
if (selected === 3) {
return this.setState({
eventsData
});
}
const statusMap = {
1: "complete",
2: "inProgress"
};
const selectedStatus = statusMap[selected];
const filteredEvents = eventsData.filter(
({ status }) => status === selectedStatus
);
this.setState({
eventsData: filteredEvents
});
};
handleSearch = (searchText) => {
const filteredEvents = eventsData.filter(({ title }) => {
title = title.toLowerCase();
return title.includes(searchText);
});
this.setState({
eventsData: filteredEvents
});
};
handleChange = (e) => {
const searchText = e.target.value;
const filteredEvents = eventsData.filter(({ title }) => {
title = title.toLowerCase();
return title.includes(searchText);
});
this.setState({
eventsData: filteredEvents
});
};
resetValue = () =>{
this.setState({
eventsData: eventsData
});
}
render() {
return (
<section className={styles.container}>
<header className={styles.header}>
<h1 className={styles.title}>Events</h1>
<button onClick={this.resetValue}>Reset</button>
<TitleSearch
onSearch={this.handleSearch}
onChange={this.handleChange}
className={styles.action}
/>
</header>
<EventsTable eventsData={this.state.eventsData} />
</section>
);
}
}
export { EventsSection };
Here is what i did in order to solve it:
i added onClick on the button
<button onClick={this.resetSearch}>Reset</button>
Then in the function i put handleSearch to '', by doing this it reset the table:
resetSearch = () =>{
this.handleSearch('')
}

Getting value of SetState not updated in react

I am having a callback function which updates my array in the SetState and it works fine as I can see in the console log in the handleDaysValueChange all is good. But when I try to access it in another function i.e handleValuesChange the values are not up to date. I am missing a async or something.
import React from 'react';
import PropTypes from 'prop-types';
import AppService from 'services/app-service';
import Enum from 'enum';
import { ApiData } from 'data';
import { isEmpty, boolValue } from 'core/type-check';
import { notifySuccess } from 'core/errors';
import { withDrawerForm } from 'components/_hoc';
import { ExtendedAvatar } from 'components/extended-antd';
import { Tabs } from 'antd';
import { FormTemplateAudit } from '../templates';
import ShiftRosterFormDetails from './shiftroster-form-details';
import Item from 'antd/lib/list/Item';
const { TabPane } = Tabs;
class ShiftRosterForm extends React.Component {
constructor(props) {
super(props);
this.formInputRef = React.createRef();
this.state = {
daysValue:[]
};
this.handleDaysValueChange = this.handleDaysValueChange.bind(this);
}
handleDaysValueChange(daysValues) {
this.setState({ daysValue: daysValues }, () => {
console.log("Data" + this.props.customData);
console.log("Hello World " + JSON.stringify(this.state.daysValue));
});
}
componentDidMount() {
// Update the parent form state with a reference to the
// formInputRef so we can call this later for validation
// before saving the data to the server.
const { onFormStateChange } = this.props;
onFormStateChange({ formInputRef: this.formInputRef.current });
}
//Shift Roster Detail
handleValuesChange = (props, changedValues, allValues) => {
// Update the parent form state with the changed item
// details and mark the form as dirty
const { itemData, onFormStateChange } = this.props;
console.log("Hey" + this.state.daysValue);
onFormStateChange({
isFormDirty: true,
itemData: {
...itemData,
...changedValues,
...this.state.daysValue
}
});
}
render() {
const { itemData, customData } = this.props;
const isSR = (!isEmpty(itemData) && itemData.id > 0);
return (
<Tabs className="bhp-tabs" defaultActiveKey="1" animated={false}>
<TabPane key="1" tab="Details">
<ShiftRosterFormDetails
ref={this.formInputRef}
dataSource={itemData}
onValuesChange={this.handleValuesChange}
handleDaysValueChange={this.handleDaysValueChange}
/>
</TabPane>
<TabPane key="2" tab="Audit" disabled={!isSR}>
<FormTemplateAudit
itemData={itemData}
/>
</TabPane>
</Tabs>
);
}
}
ShiftRosterForm.propTypes = {
itemId: PropTypes.number, // Passed in by the HOC. The loaded Shift Roster id
itemData: PropTypes.object, // Passed in by the HOC. The loaded Shift Roster data
customData: PropTypes.object, // Temporary store to hold the changed Shift Roster
isFormDirty: PropTypes.bool, // Passed in by the HOC. Flags if the parent form is dirty
isLoading: PropTypes.bool, // Passed in by the HOC. Flags if the parent form is loading
daysValue: PropTypes.object,
onFormStateChange: PropTypes.func // Passed in by the HOC. Callback to update the parent form state.
};
ShiftRosterForm.defaultProps = {
itemId: -1,
itemData: {},
customData: {},
isFormDirty: false,
isLoading: false,
daysValue: {},
onFormStateChange() { }
};
const ShiftRosterFormTitle = ({ data }) => {
const name = (!isEmpty(data) && data.id > 0) ? `${data.name}` : 'New Shift Roster';//`${data.name}`
return isEmpty(data)
? <ExtendedAvatar type="icon" size="large" />
: <span><ExtendedAvatar name={name} type="letter" size="large" />{name}</span>
}
const saveShiftRoster = (shiftrosterId, shiftroster, rosterdays) => {
return ApiData.saveShiftRoster(shiftrosterId, shiftroster, rosterdays)
.then(response => {
notifySuccess('Save Successful', 'Site Work Package saved successfully');
return response;
})
.catch(error => {
throw error;
});
}
const saveForm = (formState, setFormState) => {
const { isFormDirty, itemData, customData, formInputRef } = formState;
const typeName = "Dynamic";
const actualType = itemData.type;
let rosterdays = [];
if (actualType !== typeName) {
rosterdays = GetDaysForRoster(itemData);
console.log("My Values" + JSON.stringify(rosterdays));
}
const shiftRosterId = itemData.id;
const isExistingShiftRoster = shiftRosterId > 0;
return new Promise((resolve, reject) => {
if (isExistingShiftRoster && !isFormDirty) {
// No Changes
notifySuccess('Save Successful', 'Site Work Package saved successfully');
resolve(itemData);
}
else {
// Validate and Save
formInputRef.validateFields((error, values) => {
if (!error) {
// Form validated successfully.
// Save form changes
const shiftrosterRecord = saveShiftRoster(shiftRosterId, values, rosterdays);
resolve(shiftrosterRecord);
}
else {
// Form validation error.
// Return data as is.
resolve(itemData);
}
});
}
});
}
const GetDaysForRoster = (itemsData) => {
const result = [];
const keys = Object.keys(itemsData);
for (const k in keys) {
if (Number(k) == k) {
result[k] = itemsData[k]
}
}
return result.filter(function (el) { return el != null });
}
const WrappedShiftRosterForm = withDrawerForm({
containerClassName: 'bhp-equipment-type-form',
title: (record) => <ShiftRosterFormTitle data={record} />,
onLoad: (itemId, setFormState) => ApiData.getShiftRoster(itemId),
onSave: (formState, setFormState) => { return saveForm(formState, setFormState); },
canView: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Read] }),
canCreate: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Create] }),
canUpdate: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Update] })
})(ShiftRosterForm);
WrappedShiftRosterForm.propTypes = {
containerClassName: PropTypes.string,
itemId: PropTypes.number,
visible: PropTypes.bool,
onSave: PropTypes.func,
onClose: PropTypes.func
};
WrappedShiftRosterForm.defaultProps = {
containerClassName: null,
itemId: -1,
visible: false,
onSave() { },
onClose() { }
};
export default WrappedShiftRosterForm;
//ShiftRosterFormDetails
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ApiData } from 'data';
import { Form, Input, Select, Button, space, InputNumber } from 'antd';
import ShiftDays from './shiftdays'
const ShiftRosterFormDetails = ({ form, dataSource, onValueChange, handleDaysValueChange }) => {
const { getFieldDecorator } = form;
const formLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
},
};
console.log("My datasource" + dataSource.shiftRosterDays);
//const daysRoster = dataSource.shiftRosterDays || [{ daysIn: 1, daysOut: 1, category: "Day Shift" }];
const [inputList, setInputList] = useState([{ daysIn: 1, daysOut: 1, category: "Day Shift" }]);
const [selectedType, setSelectedType] = useState(dataSource.type || 'Fixed Single');
const [isTotalDaysRequired, SetTotalDaysRequired] = useState(dataSource.type === 'Dynamic' ? true : false);
const [isTotalDaysRequiredMessage, setIsTotalDaysRequiredMessage] = useState(dataSource.type === 'Dynamic' ? 'Please enter the Total Days' : '');
const handleTypeChanged = (value, e) => {
setSelectedType(value);
if (value === "Dynamic") {
SetTotalDaysRequired(true);
setIsTotalDaysRequiredMessage('Please enter the Total Days');
}
if (value === "Fixed Single") {
if (inputList.length > 1) {
const list = [...inputList];
console.log("Total" + inputList.length);
list.splice(1, inputList.length);
setInputList(list);
console.log("My List" + JSON.stringify(list));
console.log("Input List" + JSON.stringify(inputList));
handleDaysValueChange(list);
}
}
else {
SetTotalDaysRequired(false);
setIsTotalDaysRequiredMessage('');
}
};
return (
<div className='bhp-equipment-type-form-details bhp-content-box-shadow'>
<Form {...formLayout}>
<Form.Item label='Name' hasFeedback>
{getFieldDecorator('name', {
initialValue: dataSource.name,
rules: [{
required: true,
message: 'Please enter the Name'
}],
})(
//disabled={dataSource.id > 0}
<Input placeholder='Name' />
)}
</Form.Item>
<Form.Item label='Status' hasFeedback>
{getFieldDecorator('status', {
initialValue: dataSource.status,
rules: [{
required: true,
message: 'Please enter the Status'
}],
})(
<Select>
<Select.Option value="Active">Active</Select.Option>
<Select.Option value="InActive">InActive</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item label='Type' hasFeedback>
{getFieldDecorator('type', {
initialValue: dataSource.type || 'Fixed Single',
rules: [{
required: true,
message: 'Please select the Type'
}],
})(
<Select onChange={handleTypeChanged}>
<Select.Option value="Fixed Single">Fixed Single</Select.Option>
<Select.Option value="Fixed Multiple">Fixed Multiple</Select.Option>
<Select.Option value="Dynamic">Dynamic</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item label='Total Days' hasFeedback style={selectedType === 'Dynamic' ? { display: '' } : { display: 'none' }}>
{getFieldDecorator('totaldays', {
initialValue: dataSource.totalDays,
rules: [{
required: isTotalDaysRequired,
message: isTotalDaysRequiredMessage
}],
})(
<InputNumber min={1} max={365} />
)}
</Form.Item>
<ShiftDays inputList={inputList} setInputList={setInputList} selectedType={selectedType} handleDaysValueChange={handleDaysValueChange} getFieldDecorator={getFieldDecorator} />
</Form>
</div>
)};
const onFieldsChange = (props, changedFields, allFields) => {
if (props.onFieldsChange) {
props.onFieldsChange(props, changedFields, allFields);
}
};
const onValuesChange = (props, changedValues, allValues) => {
if (props.onValuesChange) {
props.onValuesChange(props, changedValues, allValues);
}
};
ShiftRosterFormDetails.propTypes = {
form: PropTypes.object,
dataSource: PropTypes.object,
onFieldsChange: PropTypes.func,
onValuesChange: PropTypes.func
};
ShiftRosterFormDetails.defaultProps = {
form: {},
dataSource: {},
onFieldsChange() { },
onValuesChange() { }
};
export default Form.create({
onValuesChange,
onFieldsChange
})(ShiftRosterFormDetails);

autocomplete not updating options but new array is available in hits property

import { Highlight, connectAutoComplete } from 'react-instantsearch-dom';
import AutoSuggest from 'react-autosuggest';
import Autocomplete, { createFilterOptions } from '#material-ui/lab/Autocomplete';
class AutoCompleteuix extends Component {
static propTypes = {
hits: PropTypes.arrayOf(PropTypes.object).isRequired,
currentRefinement: PropTypes.string.isRequired, };
state = {
value: this.props.currentRefinement,
};
onChange = (_, { newValue }) => {
if (!newValue) {
this.props.onSuggestionCleared();
}
this.setState({
value: newValue,
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.props.refine(value);
};
onSuggestionsClearRequested = () => {
this.props.refine();
};
render() {
const { hits, onSuggestionSelected } = this.props;
const { value } = this.state;
const inputProps = {
onChange: this.onChange,
};
return (
<Autocomplete
options={hits}
getOptionLabel={(hits) => hits.ROMAN}
onInputChange = {(ev,obj,val) => this.props.refine(obj) }
renderInput={(params) => <TextField {...params} label="search for a Word" variant="outlined"/> }
value
/>
);
}
}
export default connectAutoComplete(AutoCompleteuix);
hits has the options with are being passed into options but my problem is the dropdown is not updated as per the latest value but the hits have all the updated values.
im tryign to use the algolia search which give the values of hit coming from another component

Hide/show elements with react-select - elements not hiding

I am using the react-select library with a list of states, and then using that to hide/show my elements
The elements are showing when I select them from the list, but then don't hide when they're removed. Here is the code:
import React from 'react';
import Select from 'react-select';
const options = [
{ value: 'ny', label: 'NY' },
{ value: 'ca', label: 'California' },
{ value: 'ak', label: 'Arkansas' }
];
export default class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
handleChange = (selectedOption) => {
if (!selectedOption) {
this.setState({})
}
else {
const result = {}
selectedOption.map((option) => {
result[option.value] = true
})
this.setState(result)
}
}
render() {
return (
<div>
<Select
isMulti
onChange={this.handleChange}
options={options}
/>
{this.state.ny && (
<div>NY State Images</div>
)}
{this.state.ca && (
<div>CA State Images</div>
)}
{this.state.ak && (
<div>AK State Images</div>
)}
</div>
)
}
}
Something is strange with that React.Component.
Try to use functional component:
export default function HomePage() {
const [state, setState] = useState({});
const handleChange = selectedOption => {
console.log("CHANGE_HAPPEN: ", selectedOption);
if (!selectedOption) {
setState({});
} else {
const result = {};
selectedOption.forEach(option => {
result[option.value] = true;
});
console.log("RESULT: ", result);
setState(prev => result);
}
};
return (
<div>
<Select isMulti onChange={handleChange} options={options} />
{state.ny && <div>NY State Images</div>}
{state.ca && <div>CA State Images</div>}
{state.ak && <div>AK State Images</div>}
</div>
);
}

HowTo: update the value of an element in a property

So, I have a property (fields), within which I wish to change the value of an element (countries). Alerting the value of countries currently displays the value 2, but I want to change the value to 100, so that re-alerting fields.countries.value, after the change, displays the new value.
How do I do this?
import type { State } from '../../common/types';
import DynamicField from './DynamicField';
import R from 'ramda';
import React from 'react';
import buttonsMessages from '../../common/app/buttonsMessages';
import linksMessages from '../../common/app/linksMessages';
import { FormattedMessage } from 'react-intl';
import { ValidationError } from '../../common/lib/validation';
import { connect } from 'react-redux';
import { fields } from '../../common/lib/redux-fields';
import {
Block,
Box,
Button,
Checkbox,
FieldError,
Flex,
Form,
Heading,
Input,
PageHeader,
Pre,
Radio,
Select,
Space,
Title,
View,
} from '../app/components';
// The example of dynamically loaded editable data.
// cato.org/publications/commentary/key-concepts-libertarianism
const keyConceptsOfLibertarianism = [
'Individualism',
'Individual Rights',
'Spontaneous Order',
'The Rule of Law',
'Limited Government',
'Free Markets',
'The Virtue of Production',
'Natural Harmony of Interests',
'Peace',
].map((concept, index) => ({
id: index,
name: concept,
}));
// Proof of concept. Country list will be read from firebase
const countryArray = [
{ label: 'Select Country', value: 0 },
{ label: 'France', value: 2 },
{ label: 'England', value: 4 },
{ label: 'Swizterland', value: 8 },
{ label: 'Germany', value: 16 },
{ label: 'Lithuania', value: 32 },
{ label: 'Romania', value: 64 },
].map((countryName, index) => ({
id: index,
name: countryName,
}));
// Dynamically create select list
const countryOptions = [];
countryArray.map(countryItem =>
countryOptions.push({ label: countryItem.name.label, value: countryItem.name.value }),
);
// Proof of concept. Country list will be read from firebase
const cityArray = [
{ label: 'Select City', value: 0 },
{ label: 'London', value: 50 },
{ label: 'Paris', value: 75 },
].map((cityName, index) => ({
id: index,
name: cityName,
}));
// Dynamically create select list
const cityOptions = [];
cityArray.map(cityItem =>
cityOptions.push({ label: cityItem.name.label, value: cityItem.name.value }),
);
// Proof of concept. Country list will be read from firebase
const gymArray = [
{ label: 'Select Gym', value: 0 },
{ label: 'Virgin Sport', value: 23 },
{ label: 'Sports Direct', value: 45 },
].map((gymName, index) => ({
id: index,
name: gymName,
}));
// Dynamically create select list
const gymOptions = [];
gymArray.map(gymItem =>
gymOptions.push({ label: gymItem.name.label, value: gymItem.name.value }),
);
type LocalState = {
disabled: boolean,
error: ?Object,
submittedValues: ?Object,
};
class FieldsPage extends React.Component {
static propTypes = {
fields: React.PropTypes.object.isRequired,
dynamicFields: React.PropTypes.object,
// getCities: React.PropTypes.object,
};
state: LocalState = {
disabled: false,
error: null,
submittedValues: null,
};
onFormSubmit = () => {
const { dynamicFields, fields } = this.props;
const values = {
...fields.$values(),
concepts: {
...dynamicFields,
},
};
// This is just a demo. This code belongs to Redux action creator.
// Disable form.
this.setState({ disabled: true });
// Simulate async action.
setTimeout(() => {
this.setState({ disabled: false });
const isValid = values.name.trim();
if (!isValid) {
const error = new ValidationError('required', { prop: 'name' });
this.setState({ error, submittedValues: null });
return;
}
this.setState({ error: null, submittedValues: values });
fields.$reset();
}, 500);
};
handleSelectedCountryChange = () => {
// Pass in the selected country value to get associated cites
const { fields, getCities } = this.props;
getCities('country', fields.$values());
};
/*
handleSelectedCityChange = (event => {
// Pass in the selected city value to get associated gyms
this.setState({secondLevel: event.target.value});
});
*/
render() {
const { fields } = this.props;
const { disabled, error, submittedValues } = this.state;
return (
<View>
<Title message={linksMessages.fields} />
<PageHeader
description="New clients enter their gym details here."
heading="New user entry form."
/>
<Form onSubmit={this.onFormSubmit}>
<Input
{...fields.name}
aria-invalid={ValidationError.isInvalid(error, 'name')}
disabled={disabled}
label="Your Name"
maxLength={100}
type="text"
/>
<FieldError error={error} prop="name" />
<Heading alt>Key Concepts of Libertarianism</Heading>
<Block>
<Flex wrap>
{keyConceptsOfLibertarianism.map(item =>
<Box mr={1} key={item.id}>
<DynamicField
disabled={disabled}
item={item}
path={['fieldsPage', 'dynamic', item]}
/>
</Box>,
)}
</Flex>
</Block>
<Block>
<Checkbox
{...fields.isLibertarian}
checked={fields.isLibertarian.value}
disabled={disabled}
label="I'm libertarian"
/>
<Checkbox
{...fields.isAnarchist}
checked={fields.isAnarchist.value}
disabled={disabled}
label="I'm anarchist"
/>
</Block>
<Block>
<Flex>
<Radio
{...fields.gender}
checked={fields.gender.value === 'male'}
disabled={disabled}
label="Male"
value="male"
/>
<Space x={2} />
<Radio
{...fields.gender}
checked={fields.gender.value === 'female'}
disabled={disabled}
label="Female"
value="female"
/>
<Space x={2} />
<Radio
{...fields.gender}
checked={fields.gender.value === 'other'}
disabled={disabled}
label="Other"
value="other"
/>
</Flex>
</Block>
<Block>
<Select
{...fields.countries}
disabled={disabled}
label="Countries"
onChange={this.handleSelectedCountryChange}
options={countryOptions}
/>
</Block>
<Block>
<Select
{...fields.cities}
disabled={disabled}
label="Cities"
// onChange={this.handleSelectedCityChange}
options={cityOptions}
/>
</Block>
<Block>
<Select
{...fields.gyms}
disabled={disabled}
label="Gyms"
// onChange={this.handleSelectedCityChange}
options={gymOptions}
/>
</Block>
{/*
Why no multiple select? Because users are not familiar with that.
Use checkboxes or custom checkable dynamic fields instead.
*/}
<Button disabled={disabled} type="submit">
<FormattedMessage {...buttonsMessages.submit} />
</Button>
{submittedValues &&
<Pre>
{JSON.stringify(submittedValues, null, 2)}
</Pre>
}
</Form>
</View>
);
}
}
FieldsPage = fields({
path: 'fieldsPage',
fields: [
'countries',
'cities',
'gyms',
'gender',
'isAnarchist',
'isLibertarian',
'name',
],
getInitialState: () => ({
countries: '0',
cities: '0',
gyms: '0',
gender: 'male',
isAnarchist: false,
isLibertarian: false,
}),
})(FieldsPage);
export default connect(
(state: State) => ({
dynamicFields: R.path(['fieldsPage', 'dynamic'], state.fields),
}),
)(FieldsPage);
=====================================================================
fields.js
/* #flow weak */
import R from 'ramda';
import React from 'react';
import invariant from 'invariant';
import { resetFields, setField } from './actions';
type Path = string | Array<string> | (props: Object) => Array<string>;
type Options = {
path: Path,
fields: Array<string>,
getInitialState?: (props: Object) => Object,
};
const isReactNative =
typeof navigator === 'object' &&
navigator.product === 'ReactNative'; // eslint-disable-line no-undef
// Higher order component for huge fast dynamic deeply nested universal forms.
const fields = (options: Options) => (WrappedComponent) => {
const {
path = '',
fields = [],
getInitialState,
} = options;
invariant(Array.isArray(fields), 'Fields must be an array.');
invariant(
(typeof path === 'string') ||
(typeof path === 'function') ||
Array.isArray(path)
, 'Path must be a string, function, or an array.');
return class Fields extends React.Component {
static contextTypes = {
store: React.PropTypes.object, // Redux store.
};
static getNormalizePath(props) {
switch (typeof path) {
case 'function': return path(props);
case 'string': return [path];
default: return path;
}
}
static getFieldValue(field, model, initialState) {
if (model && {}.hasOwnProperty.call(model, field)) {
return model[field];
}
if (initialState && {}.hasOwnProperty.call(initialState, field)) {
return initialState[field];
}
return '';
}
static lazyJsonValuesOf(model, props) {
const initialState = getInitialState && getInitialState(props);
// http://www.devthought.com/2012/01/18/an-object-is-not-a-hash
return options.fields.reduce((fields, field) => ({
...fields,
[field]: Fields.getFieldValue(field, model, initialState),
}), Object.create(null));
}
static createFieldObject(field, onChange) {
return isReactNative ? {
onChangeText: (text) => {
onChange(field, text);
},
} : {
name: field,
onChange: (event) => {
// Some custom components like react-select pass the target directly.
const target = event.target || event;
const { type, checked, value } = target;
const isCheckbox = type && type.toLowerCase() === 'checkbox';
onChange(field, isCheckbox ? checked : value);
},
};
}
state = {
model: null,
};
fields: Object;
values: any;
unsubscribe: () => void;
onFieldChange = (field, value) => {
const normalizedPath = Fields.getNormalizePath(this.props).concat(field);
this.context.store.dispatch(setField(normalizedPath, value));
};
createFields() {
const formFields = options.fields.reduce((fields, field) => ({
...fields,
[field]: Fields.createFieldObject(field, this.onFieldChange),
}), {});
this.fields = {
...formFields,
$values: () => this.values,
$setValue: (field, value) => this.onFieldChange(field, value),
$reset: () => {
const normalizedPath = Fields.getNormalizePath(this.props);
this.context.store.dispatch(resetFields(normalizedPath));
},
};
}
getModelFromState() {
const normalizedPath = Fields.getNormalizePath(this.props);
return R.path(normalizedPath, this.context.store.getState().fields);
}
setModel(model) {
this.values = Fields.lazyJsonValuesOf(model, this.props);
options.fields.forEach((field) => {
this.fields[field].value = this.values[field];
});
this.fields = { ...this.fields }; // Ensure rerender for pure components.
this.setState({ model });
}
componentWillMount() {
this.createFields();
this.setModel(this.getModelFromState());
}
componentDidMount() {
const { store } = this.context;
this.unsubscribe = store.subscribe(() => {
const newModel = this.getModelFromState();
if (newModel === this.state.model) return;
this.setModel(newModel);
});
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<WrappedComponent {...this.props} fields={this.fields} />
);
}
};
};
export default fields;

Categories

Resources