Skip to main content

ProgressStepper

ProgressStepper in itself gets all the necessary details from ProgressStepperProvider. This helps to avoid passing same props to each screen.

In terms of single page usage, it doesn't provide much benefit but for multi page usage, this keeps the codebase clean.

Use this to simply indicate where the ProgressStepper should appear in a screen.

Example Usage

function HomeScreen({ navigation }) {
return (
<View style={styles.container}>
<ProgressStepper />
<Button onPress={() => navigation.navigate("Cart")} title="Go To cart" />
</View>
);
}

ProgressStepperProvider

Progress stepper provider is a Context Provider that holds all of the styles and states of ProgressStepper.

This makes it possible to declare styles in one place and have it applied in every screen progress stepper is used.

Single Page

Usage

Declare ProgressStepperProvider in root level to use ProgressStepper and access useProgressStepperContext hooks

App.js

import { ProgressStepperProvider } from "react-native-reanimated-progress-steps";
import ProgressStepperExample from "./ProgressStepperExample";

export default function App() {
return (
<ProgressStepperProvider>
<ProgressStepperExample />
</ProgressStepperProvider>
);
}

ProgressStepperExample.js

import {
ProgressStepper,
useProgressStepperContext,
} from "react-native-reanimated-progress-steps";

const ProgressStepperExample = () => {
const { goToNext, goToPrevious } = useProgressStepperContext();

return (
<View style={styles.container}>
<ProgressStepper />
<Button title="Previous" onPress={goToPrevious} />
<Button title="Next" onPress={goToNext} />
</View>
);
};

export default ProgressStepperExample;

Default Styles

extended mode and width set to full screen

<ProgressStepperProvider width={Dimensions.get('window').width} extended>

Round progress steps with stepStyle and innerLabelStyle

In the default diamond shapes, the inner label styles are rotated to -45 deg to match the style. So to override, simply use stepStyle and innerLabelStyle together.

<ProgressStepperProvider
width={Dimensions.get('window').width}
extended
stepStyle={{
height: 30,
width: 30,
borderRadius: 15,
justifyContent: 'center',
alignItems: 'center',
}}
innerLabelStyle={{
color: 'white',
fontWeight: 'bold',
}}
trackActiveColor="green"
activeColor="green"
>

renderInnerStep to render any valid component as steps

In the default diamond shapes, the inner step styles are rotated to match the style. So to override, simply use stepStyle and renderInnerStep together.

<ProgressStepperProvider
width={Dimensions.get('window').width}
extended
renderInnerStep={() => {
return <Image source={require('./checkbox.png')} />;
}}
stepStyle={{
width: 30,
height: 30,
borderRadius: 15,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#E44949',
}}
activeColor="#E44949"
trackActiveColor="#E44949"
trackHeight={2}
>

Multi Page

Setup

Let's assume we have a simple stack navigator in our app with Home, Cart and Checkout Screens.

Each screen has a button that navigates to the next screen. Our goal is to animate the ProgressStepper steps automatically as we navigate to or away between these pages.

First, let's wrap our Stack Navigator with ProgressStepperProvider so that we can use ProgressStepper in each screen.

App.js

import React from 'react';
import { View, StyleSheet, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import {
createNativeStackNavigator
} from '@react-navigation/native-stack';

import {
ProgressStepperProvider,
ProgressStepper
} from 'react-native-reanimated-progress-steps';


function HomeScreen({ navigation }) {

return (
<View style={styles.container}>
<ProgressStepper />
<Button onPress={() => navigation.navigate('Cart')} title="Go To cart" />
</View>
);
}

function CartScreen({ navigation }) {

return (
<View style={styles.container}>
<ProgressStepper />
<Button
onPress={() => navigation.navigate('Checkout')}
title="Go To checkout"
/>
</View>
);
}

function CheckoutScreen({ navigation }) {
return (
<View style={styles.container}>
<ProgressStepper />
<Button onPress={() => navigation.navigate('Home')} title="Go To Home" />
</View>
);
}


const Stack = createNativeStackNavigator<RootStackParamList>();

function App() {
return (
<NavigationContainer>
<ProgressStepperProvider>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Cart" component={CartScreen} />
<Stack.Screen name="Checkout" component={CheckoutScreen} />
</Stack.Navigator>
</ProgressStepperProvider>
</NavigationContainer>
);
}

export default App;

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
},
});


Now you may notice that we can navigate between these pages and our ProgressStepper component shows up but the steps do not change!

In order for the steps to change automatically, let's write a hook that tells ProgressStepper to change steps whenever a new screen is in focus.

useFocusedPosition.js

import React, { useEffect } from "react";
import { useProgressStepperContext } from "react-native-reanimated-progress-steps";
import { useIsFocused } from "@react-navigation/native";

export default function useFocusedPosition(position) {
const isFocused = useIsFocused();
const { setCurrentPosition } = useProgressStepperContext();
useEffect(() => {
if (isFocused) {
setCurrentPosition(position);
}
}, [isFocused, setCurrentPosition, position]);
}

Note that, this hook takes a position as a number, and sets the current step position to that number whenever that screen is in focus.

Now, all that we need to do is call this hook with our desired position on each screen.

For example, Home screen should represent Step 1 so we will call useFocusedPosition(1) inside HomeScreen

function HomeScreen({ navigation }) {
useFocusedPosition(1);
return (
<View style={styles.container}>
<ProgressStepper />
<Button onPress={() => navigation.navigate("Cart")} title="Go To cart" />
</View>
);
}

Similarly, for other screens we will call this hook with their respective step numbers.

function CartScreen({ navigation }) {
useFocusedPosition(2);
return (
<View style={styles.container}>
<ProgressStepper />
<Button
onPress={() => navigation.navigate("Checkout")}
title="Go To checkout"
/>
</View>
);
}

function CheckoutScreen({ navigation }) {
useFocusedPosition(3);
return (
<View style={styles.container}>
<ProgressStepper />
<Button onPress={() => navigation.navigate("Home")} title="Go To Home" />
</View>
);
}

Summary

In summary, to go from a simple single page setup, where we could change the steps manually or via button clicks, to an automatically changing multi page stepper, we had to do the following -

  • Declare a custom hook to change the position dynamically
  • Call the hook with appropriate position on each screen

Boilerplate

To create this same setup. Create App.js and useFocusedPostion.js with the following code to get started quickly.

App.js

import React from 'react';
import { View, StyleSheet, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import {
createNativeStackNavigator
} from '@react-navigation/native-stack';

import {
ProgressStepperProvider,
ProgressStepper
} from 'react-native-reanimated-progress-steps';
import useFocusedPosition from './useFocusedPosition';

function HomeScreen({ navigation }) {
useFocusedPosition(1);
return (
<View style={styles.container}>
<ProgressStepper />
<Button onPress={() => navigation.navigate("Cart")} title="Go To cart" />
</View>
);
}

function CartScreen({ navigation }) {
useFocusedPosition(2);
return (
<View style={styles.container}>
<ProgressStepper />
<Button
onPress={() => navigation.navigate("Checkout")}
title="Go To checkout"
/>
</View>
);
}

function CheckoutScreen({ navigation }) {
useFocusedPosition(3);
return (
<View style={styles.container}>
<ProgressStepper />
<Button onPress={() => navigation.navigate("Home")} title="Go To Home" />
</View>
);
}

const Stack = createNativeStackNavigator<RootStackParamList>();

function App() {
return (
<NavigationContainer>
<ProgressStepperProvider>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Cart" component={CartScreen} />
<Stack.Screen name="Checkout" component={CheckoutScreen} />
</Stack.Navigator>
</ProgressStepperProvider>
</NavigationContainer>
);
}

export default App;

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
},
});

useFocusedPosition.js

import React, { useEffect } from "react";
import { useProgressStepperContext } from "react-native-reanimated-progress-steps";
import { useIsFocused } from "@react-navigation/native";

export default function useFocusedPosition(position) {
const isFocused = useIsFocused();
const { setCurrentPosition } = useProgressStepperContext();
useEffect(() => {
if (isFocused) {
setCurrentPosition(position);
}
}, [isFocused, setCurrentPosition, position]);
}

This should produce the following behavior

Props

NameOptionalTypeDescriptionDefault Value
children-React.ReactNodeReact components to be rendered as children.-
widthnumberWidth of the progress stepper.windowWidth - 100. Assuming windowWidth = Dimensions.get('window').width;
stepsstring[]Array of step labels.['Menu', 'Cart', 'Checkout']
initialPositionnumberInitial position of the active step.0
animationDurationnumberDuration of the animation for step transitions.300
animationDelaynumberDelay before starting the animation.700
stepWidthnumberWidth of each step.60
stepStyleViewStyleCustom styles for each step.{ width: 30, height: 30, transform: [{ rotate: '45deg' }], borderRadius: 4, justifyContent: 'center', alignItems: 'center' }
trackHeightnumberHeight of the track.6
containerHeightnumberHeight of the container.60
activeColorstringColor of the active step.'#FF0000'
inactiveColorstringColor of inactive steps.'#DEDEDE'
showLabelsbooleanWhether to display step labels.true
trackActiveColorstringColor of the active track.activeColor
trackInactiveColorstringColor of inactive track.inactiveColor
labelOffsetnumberOffset for step labels.-15
labelStyleTextStyleCustom styles for step labels.{ color: 'black', fontSize: 12, fontWeight: 'bold' }
innerLabelStyleTextStyleCustom styles for inner step labels (if extended is true).{ color: 'white', fontWeight: 'bold', transform: [{ rotate: '-45deg' }] }
extendedbooleanIndicates if the progress stepper is in extended mode.false
renderInnerStep((stepLabel: string, stepNumber: number) => React.ReactNode)Custom function to render inner step content.null

Hooks

useProgressStepperContext()

PropertyTypeDescription
currentPositionnumberThe current position of the active step within the progress stepper.
setCurrentPositionReact.Dispatch<React.SetStateAction<number>>A function to update the current position of the active step.
goToNext() => voidA function to move to the next step in the progress stepper.
goToPrevious() => voidA function to move to the previous step in the progress stepper.
stepsstring[]Array of step labels.
widthnumberWidth of the progress stepper.
initialPositionnumberInitial position of the active step.
animationDurationnumberDuration of the animation for step transitions.
animationDelaynumberDelay before starting the animation.
stepStyleViewStyleCustom styles for each step.
showLabelsbooleanWhether to display step labels.
activeColorstringColor of the active step.
inactiveColorstringColor of inactive steps.
trackHeightnumberHeight of the track.
containerHeightnumberHeight of the container.
stepWidthnumberWidth of each step.
trackActiveColorstringColor of the active track.
trackInactiveColorstringColor of inactive track.
labelOffsetnumberOffset for step labels.
labelStyleTextStyleCustom styles for step labels.
innerLabelStyleTextStyleCustom styles for inner step labels (if extended is true).
extendedbooleanIndicates if the progress stepper is in extended mode.