I’m working on a project where I need a slider. Originally, I was using a package, but the more custom aspects of the package that I needed weren’t working properly, so I decided to make my own. I’ve made reusable components before, but never to this level of customization. The problem that I’m having is I need my slider to have an onChange
function were I can get the updated slider values and set the state of variables in my parent component. I was able to get the value of the slider to update properly for onChange
was by using a useEffect
, which is probably not the best and causes an infinite loop when I try to change the state of the variables in the parent component.
Another way I was able to get the value to update properly was updating the onChange
value directly in the useMemo
where my panresponder
is, and while this doesn’t cause an infinite loop, it prevents the slider from working when I try to change the state of the variables in the parent component. Does anyone have any advice on how to get this to update and work properly?
Note: I’m using animated
from react-native
instead of reanimated
because I’ve already used animated in another part of my project, and I am trying to not have to use both.
Basic Version of the Slider:
type Props = {
length:number;
height:number;
cursorHeight:number;
cursorWidth:number;
cursorRadius:number;
cursorColor:string;
lineColor:string;
steps: number;
value: number[];
values: number[];
onValuesChange: (values: number[]) => void;
}
export default function Slider({
length,
height,
cursorHeight,
cursorWidth,
cursorRadius,
cursorColor,
lineColor,
steps,
value,
onValuesChange,
}: Props){
const [lineLength, setLineLength] = useState(0)
const [values1, setValues1] = useState(0)
const [values2, setValues2] = useState(0)
const Line = ({lineLength}) => {
return (
<View
style={{
left:0,
justifyContent:'center',
width:lineLength,
height:height,
position:'absolute',
}}
/>
)
}
const pan = useState(new Animated.ValueXY())[0];
const panResponder = React.useMemo(() => PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onStartShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
if (gestureState.moveX > 0 && gestureState.moveX.locationX < length && gestureState.vx){
pan.setOffset({
x: pan.x._value,
y: 0
})
}
},
onPanResponderMove:(evt, gestureState) => {
if (gestureState.moveX > 0 && gestureState.moveX < length) {
let moveX = gestureState.moveX > length ? length - 15: gestureState.moveX
pan.x.setValue(moveX)
setValues2(moveX/(length/(value[1]*steps)))
onValuesChange([values1, values2])
setLineLength(moveX)
} else if (gestureState.moveX > length) {
pan.x.setValue(length-15);
setLineLength(length)
setValues2(value[1])
onValuesChange([values1, values2])
}
else if (gestureState.moveX < 0 ) {
pan.x.setValue(0);
setLineLength(0)
setValues2(value[0])
onValuesChange([values1, values2])
}
} ,
onPanResponderRelease: () => {
pan.flattenOffset()
},
}), [pan, onValuesChange, values1, values2]);
return (
<View style={{height:height+cursorHeight+10, marginTop:10, width:length+50, alignSelf:'center', justifyContent:'center',}}>
<View
style={[mainLineStyles,{
justifyContent:'center',
alignSelf:'center',
width:length,
backgroundColor:lineColor,
height:height,
}]}
>
<Animated.View
style={{
width:cursorWidth,
height:cursorHeight,
borderRadius:cursorRadius,
backgroundColor:cursorColor,
justifyContent:'center',
position:'absolute',
transform:[{translateX: pan.x}]
}}
{...panResponder.panHandlers}
/>
<Line lineLength={lineLength}/>
</View>
</View>
);
}
Parent Component:
const [sliderData, setSliderData] = useState({})
const Parent = () => {
return (
<Slider
length={200}
height={5}
cursorHeight={20}
cursorWidth={20}
cursorRadius={20}
cursorColor={'hsl(210, 100%, 90%)'}
lineColor={'white'}
steps={1}
value={[0,20]}
onValuesChange={values => {setSliderData({...sliderData, slider1:values})}}
/>
)
}