Skip to content

πŸš€ Type-safe state management solution for Next.js applications with built-in persistence and server component support.

License

Notifications You must be signed in to change notification settings

RaheesAhmed/nextjs-state

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

16 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Next State

A modern, type-safe state management solution designed specifically for Next.js applications. Next State provides a minimal yet powerful API with built-in support for server components, persistence, and development tools.

npm version Bundle Size License: MIT

Features

  • 🎯 Type-safe: Full TypeScript support with type inference and strict null checks
  • ⚑ Lightweight: Less than 1KB minified and gzipped for optimal bundle size
  • πŸ”„ Middleware: Extensible middleware system for logging, persistence, validation, and more
  • πŸ’Ύ Persistence: Built-in storage adapters for localStorage and indexedDB with migration support
  • 🎨 Selectors: Efficient state access with automatic memoization to prevent unnecessary re-renders
  • πŸ–₯️ DevTools: Integrated development tools for debugging and time-travel
  • πŸš€ Server Components: First-class support for Next.js App Router and Server Components
  • πŸ”„ Optimistic Updates: Built-in support for optimistic UI updates with server synchronization
  • πŸ“¦ Zero dependencies: Only React as a peer dependency for minimal footprint

Installation

npm install nextjs-state
# or
yarn add nextjs-state
# or
pnpm add nextjs-state

Quick Start

import { createNextState } from 'nextjs-state';

// 1. Define your state type
interface CounterState {
  count: number;
  lastUpdated: string;
}

// 2. Create your state
const { useNextState } = createNextState<CounterState>({
  initialState: {
    count: 0,
    lastUpdated: new Date().toISOString(),
  },
  // Optional configuration
  options: {
    // Enable DevTools in development
    devTools: process.env.NODE_ENV === 'development',
    // Persist state to localStorage
    storage: {
      key: 'counter-app',
      version: '1.0',
    },
  },
});

// 3. Use in your components
function Counter() {
  // Access the full state
  const { state, setState } = useNextState();

  // Or use a selector for better performance
  // const count = useNextState(state => state.count);

  const increment = () => {
    setState({
      count: state.count + 1,
      lastUpdated: new Date().toISOString(),
    });
  };

  return (
    <div>
      <p>Count: {state.count}</p>
      <p>Last Updated: {new Date(state.lastUpdated).toLocaleString()}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

Complete Example

Here's a comprehensive example showcasing all features including state management, middleware, theming, and UI components:

'use client';

import {
  createNextState,
  createLoggingMiddleware,
  createPersistenceMiddleware,
} from 'nextjs-state';

// Define complete app state
interface AppState {
  counter: {
    value: number;
    history: number[];
    lastUpdated: string;
  };
  todos: {
    items: Array<{
      id: number;
      text: string;
      completed: boolean;
      priority: 'low' | 'medium' | 'high';
    }>;
    filter: 'all' | 'active' | 'completed';
  };
  theme: {
    mode: 'light' | 'dark';
    fontSize: 'small' | 'medium' | 'large';
    accentColor: string;
  };
  user: {
    preferences: {
      notifications: boolean;
      autoSave: boolean;
    };
    lastActivity: string;
  };
}

// Create middleware stack
const loggingMiddleware = createLoggingMiddleware<AppState>();
const persistenceMiddleware = createPersistenceMiddleware<AppState>('app-state');

// Custom analytics middleware
const analyticsMiddleware = (state: AppState, nextState: AppState) => {
  if (state.counter.value !== nextState.counter.value) {
    console.log('Analytics: Counter changed', {
      from: state.counter.value,
      to: nextState.counter.value,
    });
  }
  return nextState;
};

// Custom validation middleware
const validationMiddleware = (state: AppState, nextState: AppState) => {
  if (nextState.counter.value < 0) {
    console.warn('Validation: Negative counter values not allowed');
    return state;
  }
  return nextState;
};

// Initialize state
const { useNextState } = createNextState<AppState>({
  initialState: {
    counter: {
      value: 0,
      history: [],
      lastUpdated: new Date().toISOString(),
    },
    todos: {
      items: [],
      filter: 'all',
    },
    theme: {
      mode: 'light',
      fontSize: 'medium',
      accentColor: '#3b82f6',
    },
    user: {
      preferences: {
        notifications: true,
        autoSave: true,
      },
      lastActivity: new Date().toISOString(),
    },
  },
  middleware: [loggingMiddleware, validationMiddleware, analyticsMiddleware, persistenceMiddleware],
});

// Action creators
const actions = {
  counter: {
    increment: (state: AppState) => ({
      ...state,
      counter: {
        value: state.counter.value + 1,
        history: [...state.counter.history, state.counter.value],
        lastUpdated: new Date().toISOString(),
      },
    }),
  },
  theme: {
    toggleMode: (state: AppState) => ({
      ...state,
      theme: {
        ...state.theme,
        mode: state.theme.mode === 'light' ? 'dark' : 'light',
      },
    }),
  },
  // ... more actions
};

// Example component
function Counter() {
  const { state, setState } = useNextState();

  return (
    <div className={state.theme.mode === 'dark' ? 'bg-gray-800' : 'bg-white'}>
      <h2>Counter: {state.counter.value}</h2>
      <button onClick={() => setState(actions.counter.increment)}>Increment</button>
      <div>History: {state.counter.history.join(', ')}</div>
    </div>
  );
}

For a complete working example with all features and beautiful UI components, check out our demo repository.

Advanced Usage

Using with Next.js App Router

// app/providers.tsx
'use client';

import { createNextState } from 'nextjs-state';

interface AppState {
  theme: 'light' | 'dark';
  user: {
    name: string;
    preferences: Record<string, any>;
  } | null;
}

export const { useNextState } = createNextState<AppState>({
  initialState: {
    theme: 'light',
    user: null,
  },
});

// app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Using Selectors for Performance

import { createNextState, useSelector } from 'nextjs-state';

interface DeepState {
  users: {
    list: Array<{
      id: number;
      name: string;
      settings: {
        theme: string;
        notifications: boolean;
      };
    }>;
    selectedId: number | null;
  };
}

const { useNextState } = createNextState<DeepState>({
  initialState: {
    users: {
      list: [],
      selectedId: null,
    },
  },
});

function SelectedUserSettings() {
  const state = useNextState();
  const selectedUser = useSelector(state, (s) =>
    s.users.list.find((u) => u.id === s.users.selectedId)
  );

  if (!selectedUser) return <div>No user selected</div>;

  return (
    <div>
      <h2>{selectedUser.name}'s Settings</h2>
      <p>Theme: {selectedUser.settings.theme}</p>
      <p>Notifications: {selectedUser.settings.notifications ? 'On' : 'Off'}</p>
    </div>
  );
}

Best Practices

  1. State Organization

    • Keep state minimal and focused
    • Split large state into smaller, focused states
    • Use TypeScript interfaces for better type safety
  2. Performance

    • Use selectors for expensive computations
    • Avoid storing derived state
    • Split large components into smaller ones
  3. Middleware Usage

    • Order middleware from most to least important
    • Use middleware for cross-cutting concerns
    • Keep middleware pure and side-effect free

API Reference

createNextState<T>

Creates a new state instance.

function createNextState<T>(config: {
  initialState: T;
  middleware?: Array<(state: T, nextState: T) => T>;
}): {
  useNextState: () => {
    state: T;
    setState: (newState: T | ((prevState: T) => T)) => void;
  };
};

useSelector

Creates a memoized selector.

function useSelector<T, S>(hook: NextStateHook<T>, selector: (state: T) => S): S;

Middleware Creators

createLoggingMiddleware

function createLoggingMiddleware<T>(): (state: T, nextState: T) => T;

createPersistenceMiddleware

function createPersistenceMiddleware<T>(key: string): (state: T, nextState: T) => T;

Documentation

For more detailed documentation, please visit our documentation site:

Contributing

We welcome contributions! Please see our Contributing Guide for more details.

License

MIT