TypeScript icon, indicating that this package has built-in type declarations

0.2.0 • Public • Published

File Logger for Redux

This tool allows you to log Redux actions + state to files. It also provides a convenient API for file logging, so that you can add your own loggers (e.g. navigation state).

Use case: QA team can easily create independent logs for each issue, which makes it much easier to understand the root-cause.


3rd party libraries like react-native-fs allows you to write data to files, but each write operation opens & closes a new output stream.

The idea of this library is to use the standard output functions and redirect the output stream to a file, so that the stream remains open. In this case the logging process doesn't affect the app performance


npm install react-native-redux-file-logger
npx pod-install


Creating Redux file logger middleware

  1. Create a configurator for createReduxFileLoggerMiddleware() that returns middleware, so that later it can be injected to the store
import type { Action, AnyAction } from 'redux';
import type { ThunkMiddleware } from 'redux-thunk';
import type { LoggerOptions } from 'react-native-redux-file-logger';
import { Platform } from 'react-native';

export async function configureReduxFileLoggerMiddleware<
    State = any,
    BasicAction extends Action = AnyAction,
>(): Promise<ThunkMiddleware<State, BasicAction, LoggerOptions<State>> | null> {
    if (process.env.NODE_ENV === `development`) {
        const {
        } = require('react-native-redux-file-logger');

        try {
            const rootDir =
                Platform.OS === 'android' ? SupportedAndroidRootDirsEnum.Files : SupportedIosRootDirsEnum.Cache;
            return await createReduxFileLoggerMiddleware(
                    nestedDir: 'logs',
                    fileName: 'time-travel.json',
                    showDiff: true,
                    shouldLogPrevState: false,
                    shouldLogNextState: true,
        } catch (e) {
    return null;
  1. Create the store and a middleware injector. We can't just pass the middleware to configureStore(), because it's created asynchronously.
import { configureStore } from '@reduxjs/toolkit';
import { createMiddlewareInjector } from 'react-native-redux-file-logger';
import counterReducer from './features/counter/slice';

export const store = configureStore({
    reducer: { counter: counterReducer },
    middleware: (getDefaultMiddleware) => getDefaultMiddleware(),

export const middlewareInjector = createMiddlewareInjector<RootState, AppDispatch>(store);

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
  1. Create Redux file logger middleware and inject it to the store.
import * as React from 'react';
import { Provider } from 'react-redux';
import { store, middlewareInjector } from './store';
import { configureReduxFileLoggerMiddleware } from 'react-native-redux-file-logger';

export default function App() {
    useEffect(() => {
        (async () => {
            const rflMiddleware = await configureReduxFileLoggerMiddleware();
            if (rflMiddleware) {
    }, []);

  return (
    <Provider store={store}>

Creating file logger

Let's consider an example of a file logger for navigation state changes

  1. Create file logger for navigation
import { Platform } from 'react-native';
import { addFileLogger, getFileLogger, SupportedAndroidRootDirsEnum, SupportedIosRootDirsEnum } from 'react-native-redux-file-logger';

const rootDir = Platform.OS === 'android' ? SupportedAndroidRootDirsEnum.Files : SupportedIosRootDirsEnum.Cache
await addFileLogger('navigation-state', {
  nestedDir: 'logs',
  fileName: 'navigation.json'

export const navigationStateLogger = getFileLogger(tag);
  1. Configure navigation state listener
import {createNavigationContainerRef} from '@react-navigation/native';
import {EventArg, EventListenerCallback, EventMapCore} from '@react-navigation/core';

export const navigationRef = createNavigationContainerRef();
export type StateListenerCallbackType = EventListenerCallback<EventMapCore<any>, 'state'>;
export function addNavigationStateListener(listener: StateListenerCallbackType): void {
  navigationRef.addListener('state', listener);
  1. Pass ref to NavigationContainer
import {navigationRef} from 'path/to/file'

return (
  <NavigationContainer ref={navigationRef} >
  1. Use logger inside navigation state listener
import {addStateListener, StateListenerCallbackType} from 'path/to/file'
import {navigationStateLogger} from 'path/to/file'

const stateListener: StateListenerCallbackType = e => {
  if (this.isInitialized && && e.type) {


Creating archive

Archiving logs from all file logger instances to a specified file. If you need to archive logs for a single instance, pass the tag as a second parameter (see API section).

import { Platform } from 'react-native';
import { archive, SupportedAndroidRootDirsEnum, SupportedIosRootDirsEnum } from 'react-native-redux-file-logger';

const zipFilePath = await archive({
    Platform.OS === 'android'
      ? SupportedAndroidRootDirsEnum.Files
      : SupportedIosRootDirsEnum.Cache,
  fileName: '',




type LoggerOptions<TState = any, TLogger extends {log: (message: string) => void} = Logger> = {
  actionInclusionPredicate?: InclusionPredicate<TState>;
  diffInclusionPredicate?: InclusionPredicate<TState>;

  shouldLogPrevState?: boolean;
  shouldLogNextState?: boolean;
  showDiff?: boolean;

  stateTransformer?: (state: any) => any;

  logger: TLogger;
  • actionInclusionPredicate - actions filtering function, called before middleware logic execution. If returns false, the middleware won't be applied
  • actionInclusionPredicate - diffs filtering function
  • shouldLogPrevState - whether to add previous state to the file
  • shouldLogNextState - whether to add next state to the file
  • showDiff - whether to add diff(prev to next) state to the file
  • stateTransformer - accepts prev & next state and applies its logic to is
  • logger - logger instance, that implements log(message: string) => void method


type InclusionPredicate<TState> = (action: AnyAction, getState: () => TState) => boolean;


type FileConfig = {
  fileName: string;
  nestedDir?: string;
  rootDir: SupportedIosRootDirsEnum | SupportedAndroidRootDirsEnum | string;


  • rootDir: /storage/emulated/0/Android/data/com.reduxfileloggerexample/files/ (i.e. SupportedAndroidRootDirsEnum.Files)
  • nestedDir: logs
  • fileName: time-travel.json
  • Resulting path: /storage/emulated/0/Android/data/com.reduxfileloggerexample/files/logs/time-travel.json



Dirs that correspond to FileManager.SearchPathDirectory in Foundation

enum SupportedIosRootDirsEnum {
  Downloads = 'Downloads',
  Documents = 'Documents',
  AppSupportFiles = 'AppSupportFiles',
  Cache = 'Cache',


Dirs taken from ReactApplicationContext

enum SupportedAndroidRootDirsEnum {
  Cache = 'Cache',
  Files = 'Files',



async function createReduxFileLoggerMiddleware<
  State = any,
  BasicAction extends Action = AnyAction,
  ExtraThunkArg = undefined
  tag: string,
  fileConfig: FileConfig,
  loggerOptions: Omit<LoggerOptions<State>, 'logger'>
): Promise<ThunkMiddleware<State, BasicAction, ExtraThunkArg>> {}

Creates a Redux file logger middleware. Notice, that it doesn't accept logger, because it's encapsulated

  • tag - unique logger identifier
  • fileConfig - determines the file path (see above)
  • loggerOptions - logger options (see above)


function createLoggerMiddleware<
  State = any,
  BasicAction extends Action = AnyAction,
  ExtraThunkArg = undefined
>(options: LoggerOptions<State>): ThunkMiddleware<State, BasicAction, ExtraThunkArg> {}

Creates a logger middleware. Unlike createReduxFileLoggerMiddleware(), it accepts a logger instance, so you can provide your own implementation.

  • options - logger options (see above)


const addFileLogger = async (tag: string, fileConfig: FileConfig) => Promise<void>

Creates a unique file logger instance and stores in a map. Use it when you need to add file logger in addition to Redux (e.g. navigation state change, see example above).

  • tag - unique logger identifier
  • fileConfig - determines the file path (see above)


interface Logger {
  log: (message: string) => void;

const getFileLogger = (tag: string) => Logger | undefined

Gets a logger instance from map by tag.

  • tag - unique logger identifier


async function archive(fileConfig: FileConfig, tag?: string): Promise<string> {}

Archive logs from all logger instances (or for a specific instance if tag is provided) to a file. Supports only zip format for Android. After a successful archive creation the logs are emptied.

  • tag - unique logger identifier
  • fileConfig - determines the file path (see above)



function createMiddlewareInjector<S = any, D extends Dispatch = Dispatch>(store: MiddlewareAPI<D, S>) {
    return function inject(middleware: Middleware) {
        store.dispatch = middleware(store)(store.dispatch);

Create an injector that can be used to add middlewares.


Pulling files from Android emulators

adb root
adb pull /storage/emulated/0/Android/data/com.reduxfileloggerexample/files/example/time-travel.json /Users/{$user}/Desktop

Browsing files on iOS emulators

  1. Copy archive result to the clipboard
  2. Finder --> Go --> Go to folder
  3. Paste the value & hit enter




Inspired by Oleg Titaev

Made with create-react-native-library

Package Sidebar


npm i react-native-redux-file-logger

Weekly Downloads






Unpacked Size

373 kB

Total Files


Last publish


  • kirill-rodichev