picker icon indicating copy to clipboard operation
picker copied to clipboard

iOS Slider Jumps back to previous value before changing

Open BasselAshi opened this issue 11 months ago • 8 comments

I noticed that the iOS version of the picker isn't smooth. When a user changes the value, it jumps back to the initial value until the react state is updated. Am I misusing the library?

My implementation is very straightforward. It looks something like this:

const [font, setFont] = useState<string>()

return (
<Picker
  selectedValue={font}
  prompt="Quran Font"
  mode="dropdown"
  style={
      Platform.OS === "ios" && {
      flex: 1,
      justifyContent: "center",
      overflow: "hidden",
    }
   }
  onValueChange={(f) => setFont(f)}
>
  <Picker.Item label="Mushaf Al Madinah" value="Madinah" />
  <Picker.Item label="Diyanet Mushaf" value="Diyanet" />
</Picker>
)

https://github.com/user-attachments/assets/34fe903c-0bf5-4ae4-a0c7-9a7b1e3d92e7

BasselAshi avatar Feb 26 '25 16:02 BasselAshi

seeing the same issue. I think it's because the Picker is in a "controlled" mode, where you have to pass it selectedValue, when that value change, it triggers the animation automatically. where if it's uncontrolled, it should handle it's value by itself, so we don't have to pass selectedValue to it, then we don't trigger an extra animation, instead we can just pass something like initialValue.

or the component could be smart enough to tell the selectedValue and it's current (internal) value is the same, and skip animation.

xiaoxinghu avatar Mar 09 '25 00:03 xiaoxinghu

I don't manage to make it work without this jump. Any solution ?

QQizi avatar Mar 13 '25 15:03 QQizi

I was using the picker in a tamagui sheet, which had a modal flag. The flag may have been affecting the rendering of the picker and removing the flag resolved the case for me. I believe this can be generalized for those who are facing a similar problem - take a few steps back, try to render the picker component on its own until you pin the culprit. Hope that helps!

BasselAshi avatar Mar 13 '25 17:03 BasselAshi

I hade this issue before. it was because I was using a modal. and rendering the picker directly in it. try to make the picker a separate component, or pass it as a child.

Mokhtar25 avatar Mar 15 '25 16:03 Mokhtar25

Same issue

huynamboz avatar Mar 24 '25 09:03 huynamboz

So... Just making a note (maybe to myself or whoever find this useful).

I am currently seeing this issue when the Picker is rendered via a Portal while the state is controlled by a component outside the Portal (I believe tamagui has an implementation for Portal for modals too.)

For example:

const MyPicker = () => {
  const state = usePickerState()
  return (
    <MyPortal>
      <Picker {...state} />
    </MyPortal>
  )
}

The issue went away once I moved the state inside the Picker:

const PickerPortalContent = () => {
  const state = usePickerState()
  return <Picker {...state} />
}
const MyPicker = () => {
  return (
    <MyPortal>
      <PickerPortalContent />
    </MyPortal>
  )
}

Please note that the behaviour aren't really equivilant. If MyPortal was something like a modal component that mount and unmount PickerPortalContent, the state in PickerPortalContent gets reset. It happens that's the more intended behaviour for me so it was fine.

Hope this helps my future self or someone else 😄 .

ycmjason avatar May 02 '25 17:05 ycmjason

I'm facing the same issue. I'm using gorhom's bottomsheet and placing the Picker directly inside it, but even when I pass the Picker as a child, it doesn't work and still triggers the second animation.

Any solution?

lenonazzi avatar May 28 '25 20:05 lenonazzi

@lenonazzi you just should bind initital value to Picker component and handle it inside this component, this will not trigger animation two times, i resolved same problem by this way. Hope this will help you.

<BottomSheetModal
  ref={bottomSheetModalRef}
  backdropComponent={renderBackdrop}
  enablePanDownToClose={false}
  enableHandlePanningGesture={false}
  enableOverDrag={false}
>
  <BottomSheetView>
    <View>
        <Picker
          options={timers}
          initialValue={timerSelected}
          onValueChange={(val) => setValue(val)}
        />
    </View>
  </BottomSheetView>
</BottomSheetModal>

Picker.tsx

import { Picker as RNPicker } from '@react-native-picker/picker';
import { ItemValue } from '@react-native-picker/picker/typings/Picker';
import React from 'react';

const Picker = ({ initialValue, onValueChange = () => {}, options }) => {
  const [localValue, setLocalValue] = React.useState(initialValue);

  function handleValueChange(value: string) {
    const selectedOption = options.find((option) => option.value === value);
    if (!selectedOption) return;
    setLocalValue(selectedOption.value);
    onValueChange(selectedOption);
  }

  return (
    <RNPicker selectedValue={localValue} onValueChange={handleValueChange}>
      {options.map((option, index) => (
        <RNPicker.Item
          key={index}
          label={option.label}
          value={option.value}
        />
      ))}
    </RNPicker>
  );
};

export { Picker };

huynamboz avatar May 29 '25 00:05 huynamboz