iOS Slider Jumps back to previous value before changing
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
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.
I don't manage to make it work without this jump. Any solution ?
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!
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.
Same issue
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 😄 .
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 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 };