HowTo:更新属性

时间:2016-12-10 18:25:19

标签: javascript reactjs

所以,我有一个属性(字段),我希望在其中更改元素(国家/地区)的值。警告当前国家/地区的值显示值2,但我想将值更改为100,以便在更改后重新警告fields.countries.value显示新值。

我该怎么做?

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;

0 个答案:

没有答案