Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 121 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<br />

# RTK Persist [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) ![GitHub package.json version](https://img.shields.io/github/package-json/v/FancyPixel/rtk-persist?color=%2332C553)
# RTK Persist

**`rtk-persist`** is a lightweight, zero-dependency library that enhances Redux Toolkit's state management by adding seamless, persistent storage. It allows specified slices or reducers of your Redux state to be saved to a storage medium of your choice (like `localStorage` or `AsyncStorage`) and rehydrated on app startup.

Expand All @@ -17,13 +17,23 @@ The library works by wrapping standard Redux Toolkit functions, adding persisten
## ✨ Features

* **Effortless Persistence**: Persist any Redux Toolkit slice or reducer with minimal configuration.

* **Asynchronous Rehydration**: Store creation is now asynchronous, ensuring that your app only renders after the state has been fully rehydrated.

* **Seamless Integration**: Designed as a drop-in replacement for RTK functions. Adding or removing persistence is as simple as changing an import.

* **React Redux Integration**: Comes with a `<PersistedProvider />` and a `usePersistedStore` hook for easy integration with React applications.

* **Flexible API**: Choose between a `createPersistedSlice` utility or a `createPersistedReducer` builder syntax.

* **Nested State Support**: Easily persist slices or reducers that are deeply nested within your root state using a simple `nestedPath` option.

* **Custom Serialization**: Use `onPersist` and `onRehydrate` to transform your state before saving and after loading.

* **Storage Agnostic**: Works with any storage provider that implements a simple `getItem`, `setItem`, and `removeItem` interface.

* **TypeScript Support**: Fully typed to ensure a great developer experience with path validation.

* **Minimal Footprint**: Extremely lightweight with a production size under 15 KB.

<br />
Expand Down Expand Up @@ -83,6 +93,7 @@ export const counterSlice = createPersistedSlice({

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

```

### Option 2: Using `createPersistedReducer`
Expand Down Expand Up @@ -114,6 +125,7 @@ export const counterReducer = createPersistedReducer(
});
}
);

```

### 2. Configure the Store
Expand Down Expand Up @@ -147,6 +159,7 @@ export const store = configurePersistedStore(
export type Store = Awaited<typeof store>;
export type RootState = ReturnType<Store['getState']>;
export type AppDispatch = Store['dispatch'];

```

<br />
Expand All @@ -163,7 +176,7 @@ This component replaces the standard `Provider` from `react-redux`. It waits for

In your application's entry point (e.g., `main.tsx` or `index.js`), wrap your `App` component with `PersistedProvider`.

```tsx
```typescript
// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
Expand All @@ -178,62 +191,38 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
</PersistedProvider>
</React.StrictMode>,
);

```

The `PersistedProvider` accepts two props:

* `store`: The promise returned by `configurePersistedStore`.

* `loader` (optional): A React node to display while the store is rehydrating.

### `usePersistedStore`

A custom hook that provides access to the rehydrated store instance. This is useful for dispatching actions or accessing store methods like `flush`.
A custom hook that provides access to the rehydrated store instance. This is useful for dispatching actions or accessing store methods.

#### Usage

```tsx
```typescript
import React from 'react';
import { usePersistedStore } from 'rtk-persist';

const MyComponent = () => {
const { store } = usePersistedStore();

const handleSaveNow = () => {
// Manually forces the store to save its current state to storage.
store.flush();
const handleClear = () => {
// Manually clears the persisted state from storage.
store.clearPersistedState();
};

return <button onClick={handleSaveNow}>Save Now</button>;
return <button onClick={handleClear}>Clear Persisted State</button>;
};
```

<br />

## ↔️ Seamless Integration

A core design principle of `rtk-persist` is that it should be easy to add or remove. The API is intentionally designed to mirror Redux Toolkit's, so enabling or disabling persistence is as simple as changing an import.

**From this:**

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

export const counterSlice = createSlice({
/* ... */
});
```

**To this:**

```typescript
import { createPersistedSlice } from 'rtk-persist';

export const counterSlice = createPersistedSlice({
/* ... */
});
```

No other code changes are needed in your slice file.

<br />

## 🌳 Handling Nested State
Expand All @@ -256,7 +245,9 @@ export const counterSlice = createPersistedSlice(
/* ... */
},
},
'features.counter' // The nestedPath to the slice's state
{
nestedPath: 'features.counter' // The nestedPath to the slice's state
}
);

// app/store.ts
Expand All @@ -277,10 +268,74 @@ export const store = configurePersistedStore(
'my-app-id',
localStorage
);

```

<br />

## 🔬 Advanced Usage: Custom Serialization

Sometimes, you may need to transform a slice's state before it's saved to storage or after it's rehydrated. For example, you might want to store a `Date` object as an ISO string, or omit certain transient properties.

`rtk-persist` supports this through the `onPersist` and `onRehydrate` options.

### Example with `onPersist` and `onRehydrate`

Here's how you can persist a slice that contains a non-serializable value like a `Date` object.

```typescript
// features/session/sessionSlice.ts
import { createPersistedSlice } from 'rtk-persist';

interface SessionState {
lastLogin: Date | null;
token: string | null;
}

const initialState: SessionState = {
lastLogin: null,
token: null,
};

export const sessionSlice = createPersistedSlice(
{
name: 'session',
initialState,
reducers: {
login: (state, action) => {
state.token = action.payload.token;
state.lastLogin = new Date();
},
logout: (state) => {
state.token = null;
state.lastLogin = null;
},
},
},
{
// Transform state before saving
onPersist: (state) => ({
...state,
lastLogin: state.lastLogin ? state.lastLogin.toISOString() : null,
}),
// Transform state after rehydrating
onRehydrate: (state) => ({
...state,
lastLogin: state.lastLogin ? new Date(state.lastLogin) : null,
}),
}
);

```

In this example:

* `onPersist` converts the `lastLogin` `Date` object into an ISO string before it's written to `localStorage`.

* `onRehydrate` parses the ISO string and converts it back into a `Date` object when the state is loaded from storage.

<br />

## 🛠️ API

### `createPersistedSlice`
Expand All @@ -290,13 +345,18 @@ A wrapper around RTK's `createSlice` that adds persistence.
#### Takes

* **`sliceOptions`**: The standard `CreateSliceOptions` object from Redux Toolkit.
* **`nestedPath`** (optional, `string`): A dot-notation string representing the path to the slice's state from the root. Required if the slice is not at the root level.

#### Returns
* **`persistenceOptions`** (optional, `object`): Configuration for persistence behavior.

* `nestedPath` (optional, `string`): A dot-notation string for the slice's state if it's nested.

* `onPersist` (optional, `function`): A function to transform state *before* it's saved.

* `onRehydrate` (optional, `function`): A function to transform state *after* it's rehydrated.

* A standard `Slice` object, enhanced with a `nestedPath` property.
#### Returns

---
* A `PersistedSlice` object, which is a standard `Slice` object enhanced with persistence properties.

### `createPersistedReducer`

Expand All @@ -305,15 +365,22 @@ A wrapper around RTK's `createReducer` that adds persistence.
#### Takes

* **`name`**: A unique string to identify this reducer in storage.

* **`initialState`**: The initial state for the reducer.

* **`builderCallback`**: A callback that receives a `builder` object to define case reducers.
* **`nestedPath`** (optional, `string`): A dot-notation string representing the path to the reducer's state. An empty string (`''`) signifies that this reducer is the root state.

#### Returns
* **`persistenceOptions`** (optional, `object`): Configuration for persistence behavior.

* `nestedPath` (optional, `string`): A dot-notation string for the reducer's state. An empty string (`''`) signifies the root state.

* A standard `Reducer` function, enhanced with `reducerName` and `nestedPath` properties.
* `onPersist` (optional, `function`): A function to transform state *before* it's saved.

---
* `onRehydrate` (optional, `function`): A function to transform state *after* it's rehydrated.

#### Returns

* A `PersistedReducer` function, which is a standard `Reducer` enhanced with persistence properties.

### `configurePersistedStore`

Expand All @@ -322,16 +389,22 @@ A wrapper around RTK's `configureStore`.
#### Takes

* **`storeOptions`**: The standard `ConfigureStoreOptions` object.

* **`applicationId`**: A unique string that identifies the application to namespace storage keys.

* **`storageHandler`**: A storage object that implements `getItem`, `setItem`, and `removeItem`.

* **`persistenceOptions`** (optional): An object to control the persistence behavior:
* `rehydrationTimeout` (optional, `number`): Max time in ms to wait for rehydration. Defaults to `5000`.

* `rehydrationTimeout` (optional, `number`): Max time in ms to wait for rehydration. Defaults to `5000`.

#### Returns

* A `Promise<PersistedStore>` object, which resolves to a standard Redux store enhanced with the following methods:
* **`rehydrate()`**: A function to manually trigger rehydration from storage.
* **`clearPersistedState()`**: A function that clears all persisted data for the application from storage.

* **`rehydrate()`**: A function to manually trigger rehydration from storage.

* **`clearPersistedState()`**: A function that clears all persisted data for the application from storage.

<br />

Expand All @@ -346,5 +419,5 @@ This library was crafted from our daily experiences building modern web and mobi
## 📄 License

This project is licensed under the MIT License.

Library icon freely created from a [iconsax](https://iconsax.io/) icon and the [redux](https://redux.js.org/img/redux.svg) logo.
Loading
Loading