Since setState changes the value of the slider in the state and happens after the actual slider movement, the slider lags quite a bit. I've seen people use debouncing to fix this, but it doesn't work so well and I feel like there must be a more pragmatic solution. Debouncing simply makes the issue less apparent, it doesn't fix it at the root.
Any ideas?
<!-- language: lang-js -->
<Slider
value={this.state.someValue}
maximumValue={this.state.sliderMaximumValue}
minimumValue={0}
onValueChange={(someValue) => this.setState({someValue})}
/>
Since setState changes the value of the slider in the state and happens after the actual slider movement, the slider lags quite a bit. I've seen people use debouncing to fix this, but it doesn't work so well and I feel like there must be a more pragmatic solution. Debouncing simply makes the issue less apparent, it doesn't fix it at the root.
Any ideas?
<!-- language: lang-js -->
<Slider
value={this.state.someValue}
maximumValue={this.state.sliderMaximumValue}
minimumValue={0}
onValueChange={(someValue) => this.setState({someValue})}
/>
Share
Improve this question
edited Feb 19, 2019 at 9:45
underscore
asked Feb 19, 2019 at 9:42
underscoreunderscore
2854 silver badges10 bronze badges
6 Answers
Reset to default 8You can achieve what you want by simply not passing in the value
prop to the <Slider />
. So, like this:
<!-- language: lang-js -->
<Slider
// value={this.state.someValue} // commented out :D
maximumValue={this.state.sliderMaximumValue}
minimumValue={0}
onValueChange={(someValue) => this.setState({someValue})}
/>
That's it! :D
But! If you need to constantly display a changing value of the slider, (as I needed) you can do this:
Create 2 states in your component for one actual piece of state. One to display, and one to be the actual value to be passed to the <Slider />
(and in this case you don't need to comment out the value
prop).
I had a component where I had to display the constantly changing slider value, without the slider lagging. So I did this:
const [displayTotalQuantity, setDisplayTotalQuantity] = useState(FUEL_AMOUNT_MINIMUM);
const [totalQuantity, setTotalQuantity] = useState(FUEL_AMOUNT_MINIMUM);
<Text> {displayTotalQuantity} </Text>
<Slider
style={slider.baseStyles}
step={STEP}
value={totalQuantity}
minimumValue={MINIMUM}
maximumValue={MAXIMUM}
minimumTrackTintColor={mainColors.irisBlue}
maximumTrackTintColor={mainColors.sliderMaxTintGray}
thumbTintColor={mainColors.irisBlue}
onSlidingComplete={value => setTotalQuantity(value)}
onValueChange={value => setDisplayTotalQuantity(value)}
/>
So, you need to pass the actual state through the value
prop, but update it only onSlidingComplete
. On the other hand, update the display
state on each change, i.e. onValueChange
and display it in some text component.
I hope I made myself clear. If not, ask and I will elaborate. :)
With your posted code it's hard to say what's going on if you are rendering bunch of other stuff along with your slider in your component then calling setState on every value change is a bad thing it's triggering way to many renders that's why it's lagging because it has to much to re-render.
solutions:
1) if you don't need to reflect any thing on UI you can do onValueChange={(someValue) => this.state.sliderValue = someValue} this won't trigger render but will preserve slider value on state.
2) extract slider to it's own pure component and keep slider state to that component this way when you change slider set state will re-render only slider component part instead of whole screen.
hope this helps you.
I suggest to use onSlidingComplete
instead of onValueChange
as it will set the state (and refresh the view) only when user action is ended.
EDIT This will not affect your update on the numeric shown value of the slider as DOCS states:
This is not a controlled component, you don't need to update the value during dragging.
we can use onTouchStart and onTouchEnd like below:
import React, { useState } from 'react';
import { View } from 'react-native';
import Slider from '@react-native-community/slider';
const MySlider = () => {
const [value, setValue] = useState(0);
const [isTouchEnded, setIsTouchEnded] = useState(false);
return (
<View>
<Slider
minimumValue={0}
maximumValue={10}
value={isTouchEnded ? value : 0}
onValueChange={(e) => setValue(e)}
onTouchEnd={() => setIsTouchEnded(true)}
onTouchStart={() => setIsTouchEnded(false)}
/>
</View>
);
};
export default MySlider;
You should use setNativeProps
instead. As said on this answer.
More about nativeProps.
You should get something like this:
<>
<TextInput
value={value.toLocaleString('pt-BR', {maximumFractionDigits: 2, minimumFractionDigits: 2})}
onChangeText={(value) => {
setValue(value);
}}
ref={inputRef}
/>
<Slider
value={value}
onValueChange={value => {
inputRef.current?.setNativeProps({text: value});
}}
/>
</>;
I used useRef instead of useState and it's working fine, without any lagging