reverse-proxy-frontend/docs/redux-rtk-upgrade-plan.md
Tudor Stanciu c05de1a7dc Merged PR 108: Frontend full upgrade and migration to Typescript
- feat: Add session management components and improve system overview
- feat: Update dependencies and replace react-flags with react-country-flag
- Update dependencies in package.json: reintroduce react-dom and upgrade redux to version 5.0.1
- refactor: update chatbot implementation and dependencies
- refactor: migrate to Redux Toolkit and update dependencies
- feat: enhance ReactCountryFlag component with SVG support
- refactor: remove Bootstrap dependency and update Node engine requirement; add LabelValue component for better UI consistency
- refactor: enhance LabelValue component usage in ServerSummary for improved readability and tooltip support
- refactor: replace inline text with LabelValue component in ActiveSessionSummary and SessionSummary for improved consistency and readability
- refactor: update components to use LabelValue for improved consistency and readability
- refactor: optimize LabelValue component for improved readability and structure
- refactor: improve code readability in SessionForwardsComponent by standardizing arrow function syntax and adjusting styling properties
2025-09-27 23:24:55 +00:00

8.6 KiB

Redux to RTK Upgrade Plan

Current State Analysis

What's Working Well

  1. Modular Organization: Reducers organized by features (server, sessions, chatbot, etc.)
  2. Redux Toolkit Integration: Using @reduxjs/toolkit and configureStore
  3. Consistent Loading States: Pattern for loading/loaded on each entity
  4. TypeScript Integration: Well-defined types for actions and state

Current Architecture Pattern

features/
├── server/
│   ├── actionTypes.ts     - Action type constants
│   ├── actionCreators.ts  - Async action creators with thunks
│   ├── reducer.ts         - State management logic
│   └── api.ts            - API calls
└── [other features...]

Upgrade Opportunities

1. Redux Toolkit Slices (Major Improvement)

Current approach uses classic Redux pattern with separate files:

// actionTypes.ts
export const LOAD_SERVER_DATA_SUCCESS = "LOAD_SERVER_DATA_SUCCESS" as const;

// actionCreators.ts
export function loadServerData() {
  return async function(dispatch: Dispatch): Promise<void> {
    try {
      const data = await dispatch(sendHttpRequest(api.getServerData()) as any);
      dispatch({ type: types.LOAD_SERVER_DATA_SUCCESS, payload: data });
    } catch (error) {
      throw error;
    }
  };
}

// reducer.ts
export default function serverReducer(state: ServerState = initialState.server, action: ServerAction): ServerState {
  switch (action.type) {
    case types.LOAD_SERVER_DATA_SUCCESS:
      return { ...state, data: { ...action.payload, loading: false, loaded: true } };
    default:
      return state;
  }
}

Recommended RTK Slice approach:

// serverSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const loadServerData = createAsyncThunk(
  'server/loadData',
  async () => {
    return await api.getServerData();
  }
);

const serverSlice = createSlice({
  name: 'server',
  initialState: {
    data: { loading: false, loaded: false },
    activeSession: { loading: false, loaded: false }
  },
  reducers: {
    // Synchronous actions
    clearServerData: (state) => {
      state.data = { loading: false, loaded: false };
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(loadServerData.pending, (state) => {
        state.data.loading = true;
      })
      .addCase(loadServerData.fulfilled, (state, action) => {
        state.data = { ...action.payload, loading: false, loaded: true };
      })
      .addCase(loadServerData.rejected, (state) => {
        state.data.loading = false;
      });
  }
});

export const { clearServerData } = serverSlice.actions;
export default serverSlice.reducer;

Benefits:

  • 70% less boilerplate code
  • Automatic action creators
  • Built-in loading/error handling
  • Immer integration (direct state mutations)
  • Better TypeScript inference

2. RTK Query for API Management

Current API pattern:

// Separate API calls, manual caching, manual loading states
export function loadServerData() {
  return async function(dispatch: Dispatch): Promise<void> {
    try {
      const data = await dispatch(sendHttpRequest(api.getServerData()) as any);
      dispatch({ type: types.LOAD_SERVER_DATA_SUCCESS, payload: data });
    } catch (error) {
      throw error;
    }
  };
}

RTK Query approach:

// api/serverApi.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const serverApi = createApi({
  reducerPath: 'serverApi',
  baseQuery: fetchBaseQuery({
    baseUrl: '/api/',
    prepareHeaders: (headers) => {
      // Add auth headers, content-type, etc.
      return headers;
    },
  }),
  tagTypes: ['ServerData'],
  endpoints: (builder) => ({
    getServerData: builder.query<ServerData, void>({
      query: () => 'server',
      providesTags: ['ServerData'],
    }),
    getActiveSession: builder.query<ActiveSession, string>({
      query: (sessionId) => `sessions/${sessionId}`,
      providesTags: ['ServerData'],
    }),
  }),
});

export const { useGetServerDataQuery, useGetActiveSessionQuery } = serverApi;

Usage in components:

function ServerComponent() {
  const { data: serverData, isLoading, error } = useGetServerDataQuery();

  if (isLoading) return <Spinner />;
  if (error) return <ErrorMessage />;

  return <ServerDisplay data={serverData} />;
}

Benefits:

  • Automatic caching and cache invalidation
  • Built-in loading/error states
  • Automatic re-fetching
  • Optimistic updates
  • Request deduplication
  • Background sync

3. Enhanced Type Safety

Current types:

interface LoadServerDataSuccessAction {
  type: typeof types.LOAD_SERVER_DATA_SUCCESS;
  payload: any; // ❌ Too generic
}

Improved types:

interface ServerData {
  hostname: string;
  sessionsCount: number;
  isChainMember: boolean;
  domain: {
    name: string;
  };
  // ... specific fields instead of 'any'
}

interface ActiveSession {
  sessionId: string;
  isActive: boolean;
  forwards: Forward[];
  // ... specific structure
}

4. Selectors with Reselect

Current approach:

// Direct state access in components
const serverData = useSelector(state => state.server.data);

Memoized selectors:

import { createSelector } from '@reduxjs/toolkit';

// Base selectors
export const selectServerState = (state: RootState) => state.server;
export const selectServerData = (state: RootState) => state.server.data;

// Memoized computed selectors
export const selectIsServerLoading = createSelector(
  selectServerData,
  (serverData) => serverData.loading
);

export const selectServerSummary = createSelector(
  selectServerData,
  (serverData) => ({
    hostname: serverData.hostname,
    status: serverData.isChainMember ? 'Active' : 'Inactive',
    sessions: serverData.sessionsCount
  })
);

Migration Strategy

Phase 1: Setup RTK Query Infrastructure

  1. Install RTK Query dependencies
  2. Configure store with RTK Query middleware
  3. Create base API slice

Phase 2: Migrate One Feature (Server Module)

  1. Convert server module to RTK Query
  2. Update components to use RTK Query hooks
  3. Remove old Redux actions/reducers
  4. Test thoroughly

Phase 3: Gradual Migration

  1. Migrate one feature at a time
  2. Sessions → Forwards → System → Charts
  3. Keep both patterns during transition

Phase 4: Cleanup

  1. Remove old HTTP action utilities
  2. Consolidate remaining slices
  3. Update all TypeScript types

Benefits of Upgrade

Developer Experience

  • 90% less boilerplate for API calls
  • Automatic TypeScript inference
  • Better debugging tools
  • Standardized patterns

Performance

  • Automatic request deduplication
  • Smart caching strategies
  • Background updates
  • Optimistic updates

Maintenance

  • Single source of truth for API logic
  • Automatic error handling
  • Built-in retry logic
  • Cache invalidation strategies

Implementation Priority

  1. High Impact, Low Risk: RTK Query for new API endpoints
  2. Medium Impact: Convert existing simple GET endpoints
  3. High Impact, Higher Risk: Complex state management with mutations
  4. Polish: Enhanced selectors and type safety

Timeline Estimate

  • Phase 1 (Setup): 1-2 days
  • Phase 2 (First module): 2-3 days
  • Phase 3 (Gradual migration): 1-2 weeks
  • Phase 4 (Cleanup): 2-3 days

Total: 3-4 weeks for complete migration

Files to Reference

Current key files in the Redux architecture:

  • src/redux/configureStore.ts - Store configuration
  • src/redux/reducers/index.ts - Root reducer
  • src/features/*/actionTypes.ts - Action constants
  • src/features/*/actionCreators.ts - Thunk actions
  • src/features/*/reducer.ts - State management
  • src/features/*/api.ts - API calls

These will be consolidated into:

  • src/store/store.ts - RTK configured store
  • src/api/ - RTK Query API slices
  • src/features/*/slice.ts - RTK slices for local state

Notes for Future Implementation

  • Start with the server module as it has the simplest data flow
  • Keep backward compatibility during transition
  • Use TypeScript strict mode to catch type issues early
  • Consider using RTK Query code generation for OpenAPI specs if available
  • Plan for cache persistence if needed for offline functionality

Decision Points

  • Do we need offline support? → Affects caching strategy
  • Are there real-time updates needed? → Consider WebSocket integration
  • How complex are the mutations? → Affects optimistic update strategy
  • Do we need request cancellation? → RTK Query provides this automatically

This upgrade will significantly modernize the Redux architecture while maintaining all current functionality.