Formik-子组件意外重置整个表单状态

时间:2019-08-31 22:10:31

标签: javascript react-native formik

我有一个带有一些textInputs的表单和一个自定义CategoriesMultiselect组件,该组件弹出一个具有SelectMultiple的模式。当我从多重选择中选择一个选项时,它会分派一个redux操作,然后,我的父窗体将完全重置,除了我的类别。为什么要重置父表单?

父组件,格式为:

//@flow

import * as Yup from 'yup'
import { withFormik } from 'formik'
import { Container } from 'native-base'
import * as React from 'react'
import { ScrollView, View } from 'react-native'
import { connect } from 'react-redux'
import { Category as CategoryEnums } from 'src/enums'
import type { VepoState } from 'src/components/model'
import type { RecordOf } from 'immutable'
import type { Product } from 'src/model'
import VepoHeader from 'src/components/formControls/header/view'
import { selectIsAddFormValid } from './selector'
import { selectProduct } from './selector'
import { Button } from 'src/components/formControls'
import { ImagePicker } from 'src/components/formControls'
import LocationAutocomplete from 'src/components/formControls/locationAutocomplete/view'
import { uploadAddProduct, updateRerenderKey } from './action'
import { viewStyle } from './style'
import type { Dispatch } from 'redux'
import { updateAddProductImage } from './action'
import type { Place } from 'src/model/location'
import { Colors, Spacing } from 'src/styles'
import { Input } from 'src/components/formControls'
import { onPress } from './controller'
import { CategoriesMultiselect } from 'src/components/formControls'
import { isLocationValid } from 'src/components/product/add/groceryItem/selector'

import { getSelectedSubcategories } from 'src/components/formControls/categoriesMultiselect/controller'

const mapStateToProps = (
  state: RecordOf<VepoState>,
  ownProps: { rerenderKey: boolean }
) => ({
  locationListDisplayed: state.formControls.root.locationListDisplayed,
  // $FlowFixMe
  categories: getSelectedSubcategories(state.formControls.categories),
  image: state.product.add.image,
  rerenderKey: ownProps.rerenderKey,
  location: state.formControls.location,
  isLocationValid: isLocationValid(state)
})

// eslint-disable-next-line flowtype/no-weak-types
const mapDispatchToProps = (dispatch: Dispatch<*>): Object => ({
  updateAddProductImage: (value): void => {
    dispatch(updateAddProductImage({ value }))
  },
  uploadAddProduct: (product: Product): void => {
    dispatch(uploadAddProduct(product))
  },
  updateRerenderKey: () => {
    dispatch(updateRerenderKey())
  }
})

export const getLocationIsValid = (place: Place): boolean => {
  return Object.keys(place).length > 0 ? true : false
}
type AddGroceryStoreState = {
  name: string,
  brand: string,
  description: string,
  price?: number
}

class AddGroceryItemView extends React.Component {
  render() {
    const {
      values,
      handleSubmit,
      setFieldValue,
      errors,
      touched,
      setFieldTouched,
      isValid,
      isSubmitting
    } = this.props

    console.log(errors)
    console.log(this.props)
    return (
      <Container>
        <VepoHeader title={'Add Vegan Grocery Product'} />
        <Container style={container}>
          <ScrollView
            keyboardShouldPersistTaps="always"
            style={viewStyle(this.props.locationListDisplayed).scrollView}>
            <View>
              <LocationAutocomplete
                label={'Grocery Store'}
                placeHolder={'Enter Grocery Store'}
              />
            </View>
            <View style={viewStyle().detailsContainer}>
              <ImagePicker
                label={'Product Image (optional)'}
                image={this.props.image.image}
                updateAddProductImage={this.props.updateAddProductImage}
                updateRerenderKey={this.props.updateRerenderKey}
              />
              <Input
                label={'Product Name'}
                onTouch={setFieldTouched}
                value={values.name}
                placeholder="Enter Name"
                name="name"
                required
                error={touched.name && errors.name}
                deleteText={setFieldValue}
                onChange={setFieldValue}
              />
              <Input
                label={'Product Brand'}
                value={values.brand}
                onTouch={setFieldTouched}
                error={touched.brand && errors.brand}
                placeholder="Enter Brand"
                name="brand"
                required
                onChange={setFieldValue}
                deleteText={setFieldValue}
              />
              <View>
                <Input
                  label={'Product Description'}
                  value={values.description}
                  placeholder="Enter Description"
                  multiline={true}
                  required
                  onTouch={setFieldTouched}
                  error={touched.description && errors.description}
                  numberOfLines={4}
                  name="description"
                  deleteText={setFieldValue}
                  onChange={setFieldValue}
                />
                <Input
                  isValid={true}
                  isPrice={true}
                  label={'Product Price'}
                  value={values.price}
                  onTouch={setFieldTouched}
                  error={touched.price && errors.price}
                  placeholder="Enter Price"
                  name="price"
                  deleteText={setFieldValue}
                  onChange={setFieldValue}
                />
                <View>
                  <CategoriesMultiselect.View
                    value={values.categories}
                    values={values}
                    error={errors.categories}
                    name="categories"
                    label="Product Categories"
                    categoryCodes={[CategoryEnums.CategoryCodes.Grocery]}
                  />
                </View>
              </View>
            </View>
          </ScrollView>
        </Container>
        <Button.View
          title="submit"
          onPress={handleSubmit}
          label={'GO!'}
          disabled={!isValid || isSubmitting}
          loading={isSubmitting}
        />
        {/* <Button.View onSub={this._handleSubmit} onPress={this._handleSubmit} label={'GO!'} /> */}
      </Container>
    )
  }
}

const container = {
  flex: 1,
  ...Spacing.horizontalPaddingLarge,
  backgroundColor: Colors.greyLight,
  flexDirection: 'column'
}

const formikEnhancer = withFormik({
  validationSchema: Yup.object().shape({
    name: Yup.string().required(),
    brand: Yup.string().required(),
    categories: Yup.array().required(),
    description: Yup.string()
      .min(9)
      .required(),
    price: Yup.number()
      .typeError('price must be a number')
      .required()
  }),
  enableReinitialize: true,
  mapPropsToValues: (props) => ({
    name: '',
    brand: '',
    description: '',
    price: '',
    categories: props.categories
  }),
  handleSubmit: (values, { props }) => {
    props.updateRerenderKey()
  },
  displayName: 'AddGroceryItemView'
})(AddGroceryItemView)

// $FlowFixMe
const AddGroceryItemViewComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(formikEnhancer)

export default AddGroceryItemViewComponent

重置表单的子组件:

//@flow
import type { Node } from 'react'
import { selectSelectedCategory } from 'src/components/product/add/groceryItem/selector'
import type { VepoState } from 'src/components/model'
import type { RecordOf } from 'immutable'
import { connect } from 'react-redux'
import * as React from 'react'
import { View } from 'react-native'
import {
  List,
  ListItem,
  Text,
  Left,
  Body,
  Right,
  Button,
  Container,
  Label,
  Title,
  Content
} from 'native-base'
import Icon from 'react-native-vector-icons/FontAwesome'
import Eicon from 'react-native-vector-icons/EvilIcons'
import Modal from 'react-native-modal'
import SelectMultiple from 'react-native-select-multiple'
import {
  updateAlertModalIsOpen,
  updateAlertModalHasYesNo,
  updateAlertModalMessage,
  updateAlertModalTitle
} from 'src/components/formControls/alertModal/action'
import * as C from './model'
import type { Subcategory } from 'src/model/category'

import * as controller from './controller'
import { getIsCategoriesValid } from './controller'
import { styles } from 'src/components/style'
import {
  Colors,
  Corners,
  Distances,
  Modals,
  Spacing,
  Typography,
  ZIndexes
} from 'src/styles'
import { Containers } from '../../../styles'
import {
  toggleSubcategory,
  setAllShowSubcategoriesToFalse,
  toggleShowSubcategories
} from './action'
import type { Dispatch } from 'redux'

const mapStateToProps = (state: RecordOf<VepoState>) => ({
  vepo: state,
  // $FlowFixMe
  selectedCategory: selectSelectedCategory(state),
  categories: state.formControls.categories
})

// eslint-disable-next-line flowtype/no-weak-types
const mapDispatchToProps = (dispatch: Dispatch<*>): Object => ({
  setAllShowSubcategoriesToFalse: (): void => {
    dispatch(setAllShowSubcategoriesToFalse())
  },
  toggleSubcategory: (sc): void => {
    dispatch(toggleSubcategory(sc))
  },
  toggleShowSubcategories: (c): void => {
    dispatch(toggleShowSubcategories(c))
  },
  updateAlertModalIsOpen: (isOpen: boolean): void => {
    dispatch(updateAlertModalIsOpen(isOpen))
  },
  updateAlertModalMessage: (message: string): void => {
    dispatch(updateAlertModalMessage(message))
  },
  updateAlertModalHasYesNo: (hasYesNo: boolean): void => {
    dispatch(updateAlertModalHasYesNo(hasYesNo))
  },
  updateAlertModalTitle: (title: string): void => {
    dispatch(updateAlertModalTitle(title))
  }
})
const handleClick = (props, item) => {
  controller.categoryClicked(props, item)
}
const renderCategoryRow = (props: C.CategoriesViewProps, item: C.Category) => {
  return (
    <View>
      <ListItem style={listItem} icon onPress={() => handleClick(props, item)}>
        <Left>
          <Icon
            style={styles.icon}
            name={item.icon}
            size={20}
            color={item.iconColor}
          />
        </Left>
        <Body style={[styles.formElementHeight, border(item)]}>
          <Text style={Typography.brownLabel}>{item.label}</Text>
        </Body>
        <Right style={[styles.formElementHeight, border(item)]}>
          <Eicon style={catStyle.arrow} name="chevron-right" size={30} />
        </Right>
      </ListItem>
    </View>
  )
}
const getCategoriesToDisplay = (props) => {
  const y = props.categories.filter((x) => props.categoryCodes.includes(x.code))
  return y
}

let CategoriesMultiselect: React.ComponentType<C.CategoriesViewProps> = (
  props: C.CategoriesViewProps
): Node => {
  const categoriesToDisplay = getCategoriesToDisplay(props)
  return (
    <View>
      <View style={{ ...Containers.fullWidthRow }}>
        <Label disabled={false} style={Typography.formLabel}>
          {props.label}
        </Label>
        <View style={{ ...Containers.fullWidthRow }} />
        <Label disabled={false} style={Typography.formLabel}>
          {controller.getNumberOfSelectedSubcategories(props.categories)}{' '}
          Selected
        </Label>
      </View>
      <View style={catStyle.categoriesViewStyle(props, categoriesToDisplay)}>
        {props.categories && props.categories.length > 0 && (
          <List
            listBorderColor={'white'}
            style={categoriesListStyle}
            dataArray={categoriesToDisplay}
            renderRow={(item: C.Category) => {
              return renderCategoryRow(props, item)
            }}
          />
        )}
        <View style={catStyle.modalConatinerStyle} />
        <Modal
          style={catStyle.modal}
          isVisible={
            props.categories
              ? props.categories.some((cat: C.Category) =>
                  controller.showModal(cat)
                )
              : false
          }>
          <Container style={catStyle.modalView}>
            <View style={Modals.modalHeader}>
              <Title style={catStyle.categoriesTitleStyle}>
                {controller.getDisplayedCategoryLabel(props.categories)}
              </Title>
              <Right>
                <Button
                  transparent
                  icon
                  onPress={props.setAllShowSubcategoriesToFalse}>
                  <Eicon name="close-o" size={25} color="#FFFFFF" />
                </Button>
              </Right>
            </View>
            <Content style={catStyle.categoryStyle.modalContent}>
              <SelectMultiple
                checkboxSource={require('../../../images/unchecked.png')}
                selectedCheckboxSource={require('../../../images/checked.png')}
                labelStyle={[
                  styles.label,
                  styles.formElementHeight,
                  styles.modalListItem
                ]}
                items={controller.getDisplayedSubcategories(props.categories)}
                selectedItems={controller.getSelectedSubcategories(
                  props.categories
                )}
                onSelectionsChange={(selections, item: Subcategory) =>
                  props.toggleSubcategory({ subcategory: item })
                }
              />
            </Content>
          </Container>
        </Modal>
      </View>
      {props.error && (
        <Label
          disabled={false}
          style={[
            Typography.formLabel,
            { color: 'red' },
            { marginBottom: Spacing.medium }
          ]}>
          {props.error}
        </Label>
      )}
    </View>
  )
}

const catStyle = {
  // eslint-disable-next-line no-undef
  getBorderBottomWidth: (item: C.Category): number => {
    if (item.icon === 'shopping-basket') {
      return Spacing.none
    }
    return Spacing.none
  },
  // eslint-disable-next-line no-undef
  categoriesViewStyle: (props: C.CategoriesViewProps, categoriesToDisplay) => {
    return {
      backgroundColor: Colors.borderLeftColor(
        getIsCategoriesValid(props.categories)
      ),
      ...Corners.rounded,
      paddingLeft: Spacing.medium,
      height: Distances.FormElementHeights.Medium * categoriesToDisplay.length,
      overflow: 'hidden',
      borderBottomWidth: Spacing.none
    }
  },
  arrow: {
    color: Colors.brownDark,
    borderBottomColor: Colors.brownDark
  },
  icon: { height: Distances.FormElementHeights.Medium },
  // eslint-disable-next-line no-undef
  categoriesTitleStyle: {
    ...styles.title,
    ...Typography.titleLeftAlign
  },
  categoryStyle: {
    modalContent: {
      ...Corners.rounded
    }
  },
  modal: {
    flex: 0.7,
    height: 20,
    marginTop: Spacing.auto,
    marginBottom: Spacing.auto
  },
  modalView: {
    backgroundColor: Colors.white,
    height: 500,
    ...Corners.rounded
  },
  modalConatinerStyle: {
    marginBottom: Spacing.medium,
    color: Colors.brownDark,
    backgroundColor: Colors.brownLight,
    position: 'absolute',
    zIndex: ZIndexes.Layers.Negative,
    right: Spacing.none,
    height: Distances.Distances.Full,
    width: Distances.Distances.Full,
    ...Corners.rounded
  }
}

const categoriesListStyle = {
  flex: Distances.FlexDistances.Full,
  color: Colors.brownDark,
  backgroundColor: Colors.brownLight,
  height: Distances.FormElementHeights.Double,
  ...Corners.notRounded,
  marginRight: Spacing.medium
}

const border = (item: C.Category) => {
  return {
    borderBottomWidth: catStyle.getBorderBottomWidth(item),
    borderBottomColor: Colors.brownMedium
  }
}

const listItem = {
  height: Distances.FormElementHeights.Medium
}

// $FlowFixMe
CategoriesMultiselect = connect(
  mapStateToProps,
  mapDispatchToProps
)(CategoriesMultiselect)

export default CategoriesMultiselect

这部分似乎重置了父表单:

        onSelectionsChange={(selections, item: Subcategory) =>
          props.toggleSubcategory({ subcategory: item })
        }

1 个答案:

答案 0 :(得分:0)

这是因为categories发生了变化,因为它是Redux存储区的一部分,该存储区以mapPropsToValues以外的所有内容为空字符串的初始值激发了Formik的categories。解决方法是停止使用Formik的mapPropsToValues中的redux存储类别,该存储变为:

  mapPropsToValues: () => ({
    name: '',
    brand: '',
    description: '',
    price: '',
    categories: []
  }),

然后更新categories的Formik表单值,并将Formik的setFieldValue传递给子组件并使用它。

在父母中:

          <CategoriesMultiselect.View
            error={errors.categories}
            setFieldValue={setFieldValue}
            name="categories"
            label="Product Categories"
            categoryCodes={[CategoryEnums.CategoryCodes.Grocery]}
          />

CategoriesMultiselect.View中:

class CategoriesMultiselectView extends React.Component {
  setFormCategories = () => {
    if (this.props && this.props.setFieldValue) {
      this.props.setFieldValue(
        'categories',
        controller.getSelectedSubcategories(this.props.categories)
      )
    }
  }

      render(): React.Node {
        return (
          <View>
              <Modal
                style={catStyle.modal}
                onModalHide={this.setFormCategories}
                isVisible={
                ...
         </View>