react-native-picker / picker

Picker is a cross-platform UI component for selecting an item from a list of options.
MIT License
1.45k stars 273 forks source link

On iOS, the value selected in onValueChange comes in null #549

Closed Noma98 closed 4 months ago

Noma98 commented 4 months ago

I have been using the picker well for a long time with the code below. But suddenly at some point, I heard that the picker was not working on iOS, and when I checked it, I had a problem that when I selected a value for the picker, it didn't get selected, and it scrolled up to the first picker item. So when I took a log of the value coming into onValueChange, it was coming in null. I've been using this code well for a long time and I've never modified it, so what's wrong with me all of a sudden?

//🔴SelectCategory.tsx

interface IProps<ItemType> {
  category: ItemType;
  onCategoryChange: (value: ItemType) => void;
  isSelected: boolean;
  itemList: string[];
  itemLabelList?: string[];
  label: string;
  isRequired?: boolean;
  hideiOSModal?: boolean;
}

function SelectCategory<ItemType>({
  category,
  onCategoryChange,
  isSelected,
  itemList,
  itemLabelList,
  label,
  isRequired = false,
  hideiOSModal,
}: IProps<ItemType>) {
  const pickerRef = useRef<any>();
  const [isModalVisible, setIsModalVisible] = useState(false);
  useEffect(() => {
    hideiOSModal && setIsModalVisible(false);
  }, [hideiOSModal]);

  const toggleModal = useCallback(() => {
    Keyboard.dismiss();
    setIsModalVisible(prevState => !prevState);
  }, []);

  const modalStyle: ViewStyle = {
    flex: 1,
    position: 'relative',
  };

  const pickerStyle: TextStyle = isAndroid()
    ? {
        marginLeft: -8,
      }
    : {
        position: 'absolute',
        bottom: -20,
        left: 0,
        width: screenWidth,
        height: 216,
        backgroundColor: '#D5D8DD',
        marginLeft: -20,
      };
  return (
    <>
      <LabelWrapper>
        <Label>{label}</Label>
        {isRequired && <RequiredDot>*</RequiredDot>}
      </LabelWrapper>
      {isAndroid() ? (
        <PickerWrapper>
          <Picker

            onValueChange={onCategoryChange}
            selectedValue={category}
            style={pickerStyle}
            ref={pickerRef}>
            {itemList.map((item, i) => (
              <Picker.Item
                key={item}
                label={itemLabelList ? itemLabelList[i] : item}
                value={item}
              />
            ))}
          </Picker>
        </PickerWrapper>
      ) : (
        <>
          <CategoryModalButton onPress={toggleModal}>
            <ButtonText isSelected={isSelected}>{category}</ButtonText>
            <ArrowDownIcon source={ArrowIcon} />
          </CategoryModalButton>
          <Modal
            isVisible={isModalVisible}
            onBackdropPress={toggleModal}
            backdropColor="transparent"
            style={modalStyle}>
            <ModalCloseBtnWrapper>
              <ModalCloseBtn onPress={toggleModal}>
                <ModalCloseBtnText>완료</ModalCloseBtnText>
              </ModalCloseBtn>
            </ModalCloseBtnWrapper>
            <Picker
              onValueChange={onCategoryChange}
              selectedValue={category}
              style={pickerStyle}>
              {itemList.map((item, i) => (
                <Picker.Item
                  key={item}
                  label={itemLabelList ? itemLabelList[i] : item}
                />
              ))}
            </Picker>
          </Modal>
        </>
      )}
    </>
  );
}

export default SelectCategory;

//🔴EmailForm.tsx

interface IProps {
  email: string;
  onChangeEmail: (e: NativeSyntheticEvent<TextInputChangeEventData>) => void;
  category: EmailType;
  prevSuffix: string;
  onChange: (name: 'category' | 'suffix', value: EmailType | string) => void;
}

function EmailForm(props: IProps) {
  const { email, onChangeEmail, category, onChange, prevSuffix } = props;
  const onCategoryChange = (value: EmailType) => {
    onChange('category', value);
    value !== '직접입력' && formMethods.reset();
  };
  const formMethods = useForm({ mode: 'onTouched' });
  const suffix = formMethods.watch('suffix');

  useEffect(() => {
    onChange('suffix', suffix);
  }, [suffix]);

  return (
    <Container>
      <EmailWrapper>
        <InputWrapper>
          <Input
            isRequired
            label="이메일"
            placeholder="이메일 입력"
            defaultValue=""
            onChange={onChangeEmail}
            value={email}
            keyboardType="email-address"
            inputMarginBottom={0}
            autoCapitalize="none"
            returnKeyType="done"
          />
        </InputWrapper>
        <StyledText
          style={{ marginTop: isAndroid() ? 45 : 40 }}
          marginLeft={10}
          marginRight={10}
          marginBottom={16}
          lineHeight={18}>
          @
        </StyledText>
        <InputWrapper>
          <SelectCategory<EmailType>
            category={category}
            onCategoryChange={onCategoryChange}
            isSelected
            label=""
            itemList={emailList}
            {...(category === '직접입력' && { hideiOSModal: true })}
          />
        </InputWrapper>
      </EmailWrapper>
      {category === '직접입력' && (
        <>
          <FormProvider {...formMethods}>
            <FormInput
              rules={{
                pattern: {
                  value: /[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i,
                  message: '이메일 형식에 맞지 않습니다. 다시 입력해주세요.',
                },
              }}
              name="suffix"
              defaultValue={prevSuffix}
              placeholder="직접 입력"
              autoCapitalize="none"
              keyboardType="email-address"
              autoFocus={isAndroid()}
            />
          </FormProvider>
        </>
      )}
    </Container>
  );
}

export default EmailForm;

And it's my package.json.

{
  "name": "-",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint .",
    "storybook": "start-storybook -p 7007",
    "build-storybook": "build-storybook"
  },
  "dependencies": {
    "@actbase/react-daum-postcode": "^1.0.1",
    "@adrianso/react-native-device-brightness": "^1.2.7",
    "@react-native-async-storage/async-storage": "^1.15.5",
    "@react-native-community/masked-view": "^0.1.10",
    "@react-native-community/netinfo": "^8.3.0",
    "@react-native-firebase/app": "^14.7.0",
    "@react-native-firebase/messaging": "^14.7.0",
    "@react-native-picker/picker": "^2.1.0",
    "@react-navigation/bottom-tabs": "^6.0.9",
    "@react-navigation/devtools": "^6.0.4",
    "@react-navigation/native": "^6.0.6",
    "@react-navigation/stack": "^6.0.11",
    "@reduxjs/toolkit": "^1.6.2",
    "appcenter": "^4.4.4",
    "appcenter-analytics": "^4.4.4",
    "appcenter-crashes": "^4.4.4",
    "axios": "^0.21.4",
    "babel-plugin-root-import": "^6.6.0",
    "date-fns": "^2.23.0",
    "iamport-react-native": "^1.6.3",
    "lodash-es": "^4.17.21",
    "react": "17.0.2",
    "react-hook-form": "^7.0.4",
    "react-native": "^0.68.2",
    "react-native-calendars": "^1.1258.0",
    "react-native-code-push": "^7.0.4",
    "react-native-config": "^1.4.2",
    "react-native-dashed-line": "^1.1.0",
    "react-native-device-info": "^8.7.0",
    "react-native-dropdownalert": "^4.5.1",
    "react-native-flipper": "^0.122.0",
    "react-native-gesture-handler": "^1.10.3",
    "react-native-image-picker": "^4.7.1",
    "react-native-input-scroll-view": "^1.11.0",
    "react-native-iphone-x-helper": "^1.3.1",
    "react-native-keyboard-aware-scroll-view": "^0.9.5",
    "react-native-modal": "^13.0.0",
    "react-native-month-year-picker": "^1.8.1",
    "react-native-orientation-locker": "^1.4.0",
    "react-native-permissions": "^3.1.0",
    "react-native-print": "^0.11.0",
    "react-native-qrcode-svg": "^6.2.0",
    "react-native-reanimated": "^2.8.0",
    "react-native-safe-area-context": "^3.2.0",
    "react-native-scalable-image": "^1.1.0",
    "react-native-screens": "^3.10.1",
    "react-native-share": "^7.8.0",
    "react-native-signature-canvas": "^4.5.0",
    "react-native-sound": "^0.11.1",
    "react-native-splash-screen": "^3.2.0",
    "react-native-status-bar-height": "^2.6.0",
    "react-native-svg": "^13.10.0",
    "react-native-table-component": "^1.2.1",
    "react-native-version-check": "^3.4.3",
    "react-native-viewport-units": "0.0.5",
    "react-native-vision-camera": "^2.13.5",
    "react-native-walkthrough-tooltip": "^1.4.0",
    "react-native-webview": "^11.22.7",
    "react-query": "^3.31.0",
    "react-redux": "^7.2.4",
    "react-spring": "^9.3.2",
    "redux": "^4.1.0",
    "redux-actions": "^2.6.5",
    "redux-flipper": "^2.0.1",
    "redux-persist": "^6.0.0",
    "rn-fetch-blob": "^0.12.0",
    "styled-components": "^5.2.3",
    "victory-native": "^36.6.8",
    "vision-camera-code-scanner": "^0.2.0",
    "vision-camera-ocr": "^1.0.0"
  },
  "devDependencies": {
    "@babel/core": "^7.18.5",
    "@babel/runtime": "^7.18.3",
    "@react-native-community/eslint-config": "^3.0.2",
    "@types/iamport-react-native": "^1.5.2",
    "@types/jest": "^26.0.22",
    "@types/lodash-es": "^4.17.5",
    "@types/react": "^17.0.3",
    "@types/react-native": "^0.64.2",
    "@types/react-native-calendars": "^1.505.3",
    "@types/react-native-version-check": "^3.4.4",
    "@types/react-test-renderer": "^17.0.1",
    "@types/redux-actions": "^2.6.1",
    "@types/styled-components": "^5.1.14",
    "@types/styled-components-react-native": "^5.1.1",
    "babel-jest": "^28.1.1",
    "babel-loader": "^8.2.2",
    "babel-plugin-module-resolver": "^4.1.0",
    "eslint": "^8.17.0",
    "jest": "^28.1.1",
    "metro-react-native-babel-preset": "^0.71.1",
    "react-dom": "17.0.1",
    "react-native-codegen": "^0.0.8",
    "react-query-native-devtools": "^3.0.1",
    "react-test-renderer": "17.0.2",
    "typescript": "^4.2.4"
  },
  "jest": {
    "preset": "react-native"
  }
}