underscore
underscore

Reputation: 285

React Native slider onValueChange calling setState makes slider lag

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})}
/>

Upvotes: 10

Views: 7134

Answers (6)

Amit Kumar
Amit Kumar

Reputation: 1

I used useRef instead of useState and it's working fine, without any lagging

Upvotes: 0

Isaac Batista
Isaac Batista

Reputation: 107

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});
    }}
  />
</>;

Upvotes: 1

Milad Haji Shafiee
Milad Haji Shafiee

Reputation: 11

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;

Upvotes: 1

Filip Savic
Filip Savic

Reputation: 3238

You 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. :)

Upvotes: 8

ArkaneKhan
ArkaneKhan

Reputation: 476

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.

Upvotes: 3

Mos&#232; Raguzzini
Mos&#232; Raguzzini

Reputation: 15851

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.

Upvotes: 2

Related Questions