> ## Content Index
> Fetch the complete content index at: https://hybridheroes.de/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Using Redux Toolkit in React Native: Getting started and usage guide
- URL: https://hybridheroes.de/blog/2021-01-08-redux-toolkit-react-native/
- Published: 2021-01-08T09:00:00.000Z
- Updated: 2024-04-15T11:11:38.000Z
- Author: Simonas Juška
- Tags: Entwicklung, React Native, #Import 2026-07-29 15:59

Redux Toolkit is an official package from the Redux Team that helps configuring Redux store and reduces boilerplate. It also includes many convenience features that help supercharge your state management. Using Redux Toolkit in React Native is straightforward, so let's setup a simple application and see it in action! You can follow along the article or if you prefer you can also find all of the example code on our [GitHub repo](https://github.com/hybridheroes/redux-toolkit-example?ref=hybridheroes.de).

---

## Project setup

To get started, I'm going to create a new [React Native project](https://hybridheroes.de/react-native/?ref=hybridheroes.de):

```bash
$ npx react-native init ReduxToolkitExample

```

Next, let's install all the necessary dependencies:

```bash
$ npm install react-redux @reduxjs/toolkit
```

---

## Basic state setup

### Create state slice

Let's start by creating a simple state slice that holds message state. Redux Toolkit provides a convenience function `createSlice` that helps setting up and managing a state slice. It requires providing a **name** for the slice, **initial state** values and **reducer functions** that manage state updates.

```typescript
import { createSlice } from "@reduxjs/toolkit"

const messageSlice = createSlice({
  name: "message",
  initialState: {
    message: "Initial message"
  },
  reducers: {}
})

export default messageSlice.reducer

```

Normally in Redux we would have to setup a slice reducer function with action types and action creators, but `createSlice` simplifies this by auto-generating action types and action creators based on the names of reducer functions. In addition, thanks to the included [Immer library](https://github.com/immerjs/immer?ref=hybridheroes.de) we can write immutable state updates with mutable code. With that in mind, let's create and export our reducer function that changes message state.

```typescript
import { createSlice, PayloadAction } from "@reduxjs/toolkit"

const messageSlice = createSlice({
  name: "message",
  initialState: {
    message: "Initial message"
  },
  reducers: {
    setMessage(state, action: PayloadAction<string>) {
      state.message = action.payload
    }
  }
})

export const { setMessage } = messageSlice.actions
export default messageSlice.reducer
```

---

### Export store

Now that we have our state slice, we need to create a store and add our `message` slice reducer function. Redux Toolkit provides a `configureStore` helper function that has good default configuration options and automatically combines reducers. Additionally, you'll want to export `AppDispatch` and `RootState` types that infer from the store itself, updating them as we make changes to the store. Here's how everything should look:

```typescript
import { configureStore } from '@reduxjs/toolkit';
import messageReducer from './message';

export const store = configureStore({
  reducer: {
    message: messageReducer
  }
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
```

It's also recommended to create a separate file that exports typed dispatch and selector hooks, which will save you from importing `AppDispatch` and `RootState` every time you want to use them:

```typescript
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import { RootState, AppDispatch } from './store';

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
```

Lastly, wrap your app in Redux *Provider* component and pass down that `store` export we created earlier:

```typescript
import { Provider } from 'react-redux';
import { store } from './store';
...
const App = () => {
  return (
    <Provider store={store}>
      ...
    </Provider>
  );
};
...
```

### Setup Message component

Finally, let's create a simple component that will select our message, display it and perform a state update after we click a button.

```typescript
import { useAppDispatch, useAppSelector } from '../hooks';
...

const Message = () => {
  const dispatch = useAppDispatch();
  const { message } = useAppSelector(state => state.message);

  const handlePress = () => {
    dispatch(setMessage('Message from Component'));
  };

  return (
    <View style={styles.container}>
      <Text style={styles.text}>{message}</Text>
      <Button title={'Set Message'} onPress={handlePress} />
    </View>
  );
};
...
```

Here's how it looks so far:

0:00 

/0:04 

1× 

---

## Using Thunk Actions

### Create users state slice

That was fun! But let's step it up a notch and take a look at asynchronous operations. I would like to fetch and store user data from [https://reqres.in/](https://reqres.in/?ref=hybridheroes.de) API, so let's setup a new `users` state slice that will contain an array of users. I'm also going to create a `UserData` interface that models to the data returned by the API.

```typescript
import { createSlice } from "@reduxjs/toolkit"

interface UserData {
  id: number
  email: string
  first_name: string
  last_name: string
  avatar: string
}

const usersSlice = createSlice({
  name: "users",
  initialState: {
    users: [] as UserData[],
    loading: false
  },
  reducers: {}
})

export default usersSlice.reducer

```

### Setup Async Thunk

Async logic in Redux generally relies on [redux-thunk](https://github.com/reduxjs/redux-thunk?ref=hybridheroes.de) middleware and Redux Toolkit's `configureStore` automatically sets that up for us. To use it, let's import `createAsyncThunk` and create our user data fetching thunk function in `users` state slice that returns parsed data.

```typescript
import { createAsyncThunk } from '@reduxjs/toolkit';
...
export const fetchUsers = createAsyncThunk('users/fetchUsers', async () => {
  const response = await fetch('https://reqres.in/api/users?delay=1');
  return (await response.json()).data as UserData[];
});
...

```

`createAsyncThunk` will then generate three action creators: *pending*, *fulfilled*, and *rejected*. Each of these action creators will be attached to our thunk action and we can reference their action types in reducer and respond to them when they are dispatched.

Redux Toolkit also provides `extraReducers` function that can respond to other action types beside the ones generated by createSlice. Let's use that here to setup our reducers that will update our `users` state:

```typescript
const usersSlice = createSlice({
  name: "users",
  initialState: {
    users: [] as UserData[],
    loading: false
  },
  reducers: {},
  extraReducers: builder => {
    builder.addCase(fetchUsers.pending, state => {
      state.loading = true
    })
    builder.addCase(fetchUsers.fulfilled, (state, action) => {
      state.users = action.payload
      state.loading = false
    })
    builder.addCase(fetchUsers.rejected, state => {
      state.loading = false
    })
  }
})

```

### Export users reducer

Now we only need to add our new `users` state slice reducer to the store and the setup is complete.

```typescript
...
import usersReducer from './users';

export const store = configureStore({
  reducer: {
    message: messageReducer,
    users: usersReducer
  }
});
...
```

### Setup Users component

Lastly, let's create a new component that will fetch and display our user data when it first renders. I'm also going to add a reload button so the data could be re-fetched whenever.

```jsx
...
import { fetchUsers } from '../store/users';

const Users = () => {
  const dispatch = useAppDispatch();
  const { users, loading } = useAppSelector(state => state.users);

  useEffect(() => {
    dispatch(fetchUsers());
  }, []);

  if (loading) {
    return <ActivityIndicator size="large" style={styles.loader} />;
  }

  return (
    <View>
      <Button title={'Reload'} onPress={() => dispatch(fetchUsers())} />
      {users.map((user) => {
        return (
          <View style={styles.container} key={user.id}>
            <View>
              <View style={styles.dataContainer}>
                <Text>
                  {user.first_name} {user.last_name}
                </Text>
              </View>
              <View style={styles.dataContainer}>
                <Text>{user.email}</Text>
              </View>
            </View>
          </View>
        );
      })}
    </View>
  );
};
...
```

And this is how our *User* component looks in action:

0:00 

/0:06 

1× 

Full code example for this part can be found [here](https://github.com/hybridheroes/redux-toolkit-example/tree/3d8828efa358a80d9da02b3288d25590ba94203d?ref=hybridheroes.de).

---

## Advanced setup: Entity Adapter

### Setting up initial state

That already looks really good, but there is one more improvement we can do. Redux Toolkit also provides `createEntityAdapter` function that generates a set of prebuilt reducers and selectors for performing CRUD operations on a normalized state structure. Our user data is one of such structures, so we can make use of it.

Let's add entity adapter to our `users` state. First create `usersAdapter` that infers `UserData` type definitions, then replace `initialState` with adapter's `getInitialState` function that will return a new entity state in the following form: `{ ids: [], entities: {} }`

To keep our loading state we can pass it to `getInitialState`, which accepts an optional object as an argument that will be merged into the returned initial state value.

```typescript
import { createEntityAdapter } from '@reduxjs/toolkit';
...

export const usersAdapter = createEntityAdapter<UserData>();

const usersSlice = createSlice({
  name: 'users',
  initialState: usersAdapter.getInitialState({
    loading: false
  }),
...

```

### Replacing state update

Entity adapter provides many [CRUD functions](https://redux-toolkit.js.org/api/createEntityAdapter?ref=hybridheroes.de#crud-functions) that help with adding, updating, and removing entity instances from an entity state object. Let's update our reducer function that references *fulfilled* action type to use `setAll` when updating our user data.

```typescript
...
builder.addCase(fetchUsers.fulfilled, (state, action) => {
  usersAdapter.setAll(state, action.payload);
  state.loading = false;
});
...

```

### Adding prebuilt selectors

Now that we have our entity adapter set up, we can make use of [prebuilt selectors](https://redux-toolkit.js.org/api/createEntityAdapter?ref=hybridheroes.de#selector-functions) to help with selecting the content we want, which in our case is `selectAll` to select an array of all users. Add and export this selector in our `users` state file.

```typescript
import { RootState } from '.';
...
const usersSelectors = usersAdapter.getSelectors(
  (state: RootState) => state.users,
);

export const selectAllUsers = usersSelectors.selectAll;
...

```

### Updating Users component

Finally, we can make use of this new selector in our *User* component. Let's select all of our users so we can keep displaying them.

```jsx
...
import { fetchUsers, selectAllUsers } from '../store/users';

const Users = () => {
  const dispatch = useAppDispatch();
  const { loading } = useAppSelector(state => state.users);
  const users = useAppSelector(selectAllUsers);
...

```

If you need to see full example code, you can find it [on our repository](https://github.com/hybridheroes/redux-toolkit-example?ref=hybridheroes.de).

---

## Data fetching and caching with RTK Query

### Creating API slice and adding queries

RTK Query is an additional optional tool that can simplify common data fetching patterns and caching logic. Built on top of other Redux Toolkit's APIs it helps with request's state tracking, optimistic UI updates, and cache lifetime management. Let's try making use of it in our application.

Similarly as before, we'll create a new state slice, however this time we'll use RTK Query's `createApi` to make it an "API slice" that will handle data fetching. For querying we can use a new endpoint from [https://reqres.in/](https://reqres.in/?ref=hybridheroes.de) that returns data about colors. Define `ColorData` interface and `colorApi` service that includes `baseQuery` and `endpoints` parameters to fetch all our data. Then, export auto-generated hooks that we'll use to fetch the data in our component. Here's how it looks:

```typescript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

interface ColorData {
  id: number;
  name: string;
  year: number;
  color: string;
  pantone_value: string;
}

export const colorsApi = createApi({
  reducerPath: 'colorsApi',
  baseQuery: fetchBaseQuery({ baseUrl: 'https://reqres.in/api/' }),
  endpoints: builder => ({
    getColors: builder.query<{ data: ColorData[] }, void>({
      query: () => 'colors?per_page=12',
    }),
    getColorById: builder.query<{ data: ColorData }, number>({
      query: id => `colors/${id}?delay=1`,
    }),
  }),
});

export const { useGetColorsQuery, useGetColorByIdQuery } = colorsApi;

```

After that, we need to configure our store by adding a slice reducer that was auto-generated by our new API slice. Additionally, we need to include a custom middleware that enables caching and manages subscription lifetimes.

```typescript
import { colorsApi } from './colors';
...
export const store = configureStore({
  reducer: {
	...
    [colorsApi.reducerPath]: colorsApi.reducer,
  },
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware().concat(colorsApi.middleware),
});
...
```

And with that we're ready to call our hooks and fetch the data. I've setup a new `Colors` component that uses one of the auto-generated hooks to fetch all colors as soon as it's rendered and displays appropriate UI elements throughout the lifecycle of the request, such as loading spinner or error message. Upon successfully fetching the data it will display all colors in a list with a name and a preview of the color. I also added an additional component that will render based on expanded state that is set by pressing a toggle next to each color name. Once expanded, it will fetch detailed information about the selected color using the other hook that we exported previously. Take a look below:

```typescript
...
import { useGetColorsQuery } from '../../store/colors';
import ColorDetails from '../components/ColorDetails';

const Colors = () => {
  const [selectedColorId, setSelectedColorId] = useState<number | null>(null);
  const { data: colors, error, isFetching } = useGetColorsQuery();

  if (isFetching) {
    return <ActivityIndicator size="large" style={styles.loader} />;
  }

  if (error) {
    return <Text>Error while loading data</Text>;
  }

  if (!colors) {
    return <Text>No data</Text>;
  }

  return (
    <View>
      {colors.data.map(({ id, name, color }) => {
        const isSelected = id === selectedColorId;

        return (
          <View style={styles.container} key={id}>
            <View style={styles.colorHeaderContainer}>
              <View style={styles.colorLabel}>
                <View
                  style={[styles.colorCircle, { backgroundColor: color }]}
                />
                <Text style={styles.capitalizedText}>{name}</Text>
              </View>
              <Button
                title={isSelected ? 'Collapse' : 'Expand'}
                onPress={() => setSelectedColorId(isSelected ? null : id)}
              />
            </View>
            {isSelected && (
              <View style={styles.colorDetailsContainer}>
                <ColorDetails id={selectedColorId} />
              </View>
            )}
          </View>
        );
      })}
    </View>
  );
};
...
```

And here is the `ColorDetails` component that renders based on expanded toggle mentioned before. Upon the first render of this component RTK Query will fetch color details from the API and start a subscription to this data in the cache. Any subsequent render of this component that subscribes to the same data will fetch the cached data instead. Furthermore, additional components that subscribe to the same endpoint and parameter combination will use cached data and won't make another identical request.

```typescript
...
import { useGetColorByIdQuery } from '../../store/colors';

const ColorDetails = (props: { id: number }) => {
  const { data: color, isFetching } = useGetColorByIdQuery(props.id);

  if (isFetching) {
    return <ActivityIndicator size="large" style={styles.loader} />;
  }

  if (!color) {
    return <Text>No data</Text>;
  }

  const { name, id, year, color: hex, pantone_value } = color.data;

  const renderDetails = (label: string, value: string | number) => (
    <View style={styles.detailsContainer}>
      <Text style={styles.label}>{label}: </Text>
      <Text style={styles.capitalizedText}>{value}</Text>
    </View>
  );

  return (
    <>
      {renderDetails('ID', id)}
      {renderDetails('Year', year)}
      {renderDetails('Name', name)}
      {renderDetails('Hex Code', hex)}
      {renderDetails('Pantone Value', pantone_value)}
    </>
  );
};
...
```

Full example code is available [on our repository](https://github.com/hybridheroes/redux-toolkit-example?ref=hybridheroes.de).

### Cache behaviour

By default, data is kept in the cache until 60 seconds after the last active subscription for a particular endpoint and parameter combination ends. That means if we toggle the same color detail component within 60 seconds it will use cached data and won't refetch. We can see this behaviour more clearly if we reduce the subscription time with `keepUnusedDataFor` in `getColorById` endpoint.

```typescript
export const colorsApi = createApi({
  ...
  endpoints: builder => ({
    ...
    getColorById: builder.query<{ data: ColorData }, number>({
      query: id => `colors/${id}?delay=1`,
      keepUnusedDataFor: 5,
    }),
  }),
});
```

Following video demonstrates adjusted subscription time behaviour. Notice how after first colors' details are fetched and afterwards collapsed for more than 5 seconds it will unsubscribe from the cache and re-fetch when details are expanded again towards the end of the video. However, toggling fetched colors within the unused data window will not perform the re-fetch.

0:00 

/0:21 

1× 

There are [many other](https://redux-toolkit.js.org/rtk-query/usage/cache-behavior#manipulating-cache-behavior) options that allow manipulating cache behaviour and tailoring them to your needs. Making use of RTK Query's caching is a great way to improve your app's performance without hand-writing such logic.

---

## Conclusion

Redux Toolkit provides many advantages when building state management solution, especially when combined with asynchronous requests and normalized data. Using it in React Native is very easy and should definitely be considered over regular Redux workflow. Hope you found this informative and if you would like to learn more about Redux Toolkit, I encourage you to check out the [official documentation](https://redux-toolkit.js.org/introduction/quick-start?ref=hybridheroes.de).

If you want to know more about using Redux Toolkit in React Native read this two-part blogpost:  
Using Redux in Flutter Part 1 - Redux setup and basic state management: [flutter redux example](https://hybridheroes.de/blog/2019-06-03-flutter-redux/)  
Using Redux in Flutter Part 2 - Thunk actions: [flutter redux middleware](https://hybridheroes.de/blog/2019-09-02-flutter-redux-pt2/)

---

*This blogpost has been reviewed and updated as of 17.01.2024! The following changes have been made:*

- *Updated example project dependencies*
- *Updated video previews*
- *Added RTK Query section*
- *Various smaller text changes*