DocsGetting StartedCode Structure

Code Structure

We recommend the following file structure for projects using ReModel. This structure ensures a clear separation of concerns, making your codebase more maintainable and scalable.

      • Header.tsx
      • Footer.tsx
      • ModelProvider.tsx
      • index.ts
      • index.ts
        • actions.ts
        • index.ts
        • selectors.ts
        • epics.ts
        • services.ts
        • reducer.ts
        • types.ts

Explanation of Directories and Files

  • src/app:

    • App.tsx: The entry point for the application, where the main UI layout is defined.
  • src/components: Contains reusable UI components that can be shared across the application.

  • src/providers:

    • ModelProvider.tsx: A provider for integrating the model layer (e.g., Redux and RxJS) with the application, exposing context or hooks for accessing shared state or logic.
    • index.ts: Serves as the entry point for the providers module, re-exporting relevant components or functions.
  • src/models:

    • index.ts: The main entry point for the models directory, consolidating exports from all submodules.
    • user:
      • actions.ts: Defines actions related to the user feature, describing state transitions and events.
      • index.ts: Serves as the entry point for the user submodule, exporting relevant parts like actions, selectors, and reducers.
      • selectors.ts: Contains selector functions to retrieve specific slices of the user state.
      • epics.ts: Manages RxJS-based epics for handling side effects like API calls.
      • services.ts: Encapsulates logic for interacting with APIs or other external services.
      • reducer.ts: Defines the reducer logic for the user state, managing how actions modify the state.
      • types.ts: Contains TypeScript type definitions for the user module, ensuring strong typing throughout the codebase.

File Contents

Below are the code examples for the key files in the project.

App entry point

src/app/App.tsx
import React from 'react';
import { ReModelProvider } from '@blue-functor/remodel';
 
import model from '../models';
 
const App: React.FC = () => {
    return (
        <ReModelProvider model={model}>
            <div style={{ textAlign: 'center' }}>
                <main style={{ padding: '2rem' }}>
                    <p>This is the main application layout. Start building your app here!</p>
                </main>
            </div>
        </ReModelProvider>
    );
};
 
export default App;

In your app’s entry point, you wrap the main layout with the ReModelProvider component, passing the model object as a prop. This setup ensures that the model layer is integrated with the application, allowing components to access shared state and logic.

ReModelProvider documentation

models/index.ts

src/models/index.ts
import { reducer as ui, epics as uiEpics } from './ui';
import { reducer as user, epics as userEpics } from './user';
 
export type RootState = {
  ui: ReturnType<typeof ui>;
  user: ReturnType<typeof user>;
};
 
export const models = {
  reducers: {
    ui,
    user,
  },
  epics: {
    uiEpics,
    userEpics,
  },
};

The models/index.ts file serves as the entry point for the application’s model layer. In this file, we create the models object, which combines all the reducers and epics in the application. This object is passed to the ReModelProvider to integrate the Redux and RxJS layers into the application.

models/user/actions.ts

src/models/users/actions.ts
import { generateScopedActions } from '@blue-functor/remodel';
 
const { createAction, createEffectAction } = generateScopedActions('USER');
 
export const setIsLoading = createAction('SET_IS_LOADING');
export const signIn = createEffectAction<
  { username: string; password: string },
  { token: string }
>('SIGN_IN');

This file defines all the actions related to the USER model using the generateScopedActions utility from @blue-functor/remodel. The generateScopedActions function ensures that all actions created in this file are scoped with the prefix USER/, providing a clear and consistent namespace.

This scoping helps avoid conflicts between action types across different models and makes debugging and tracing actions easier.

models/users/selectors.ts

src/models/users/selectors.ts
import { RootState } from '../rootState';
 
export const userToken = (state: RootState) => state.user.token;
export const userDetails = (state: RootState) => state.user.details;
export const userLoading = (state: RootState) => state.user.loading;

This file defines selectors for accessing specific parts of the USER state in the Redux store.

  • Selectors: Functions like userToken and userDetails retrieve specific slices of the USER state from the global RootState. For example:
  • userToken returns the user’s token.
  • userDetails returns detailed information about the user.

Selectors provide a clean and reusable way to access state, ensuring consistency and reducing duplication throughout the application.

models/users/reducer.ts

src/models/users/reducer.ts
import { createReducer } from '@reduxjs/toolkit';
import * as actions from './actions';
 
const initialState = {
  loading: false,
  token: null,
  details: null,
};
 
const reducer = createReducer(initialState, (builder) =>
  builder
    .addCase(actions.signIn, (state, { payload }) => {
      state.loading = true;
    })
    .addCase(actions.signIn.succeeded, (state, { payload }) => {
      state.token = payload.token;
      state.details = payload.details;
      state.loading = false;
    })
    .addCase(actions.signIn.failed, (state) => {
      state.loading = false;
    })
);
 
export default reducer;

This file defines the reducer for managing the USER state in the Redux store. It uses @reduxjs/toolkit’s createReducer utility for a clean and concise syntax:

  • Initial State: The initialState object defines the default structure and values of the USER state, such as token and details.

  • Reducers: The reducer listens to actions defined in actions.ts:

    • signIn: Handles the initial sign-in action, potentially updating state based on the payload.
    • signInEffect.succeeded: Handles the successful result of a side effect (e.g., API response), updating the token and details state with the payload.

This pattern keeps the state management logic centralized and easy to maintain while leveraging action creators for consistency.

createReducer documentation

models/users/epics.ts

src/models/users/epics.ts
import { combineEpics } from 'redux-observable';
import { ofType, Epic, effect } from '@blue-functor/remodel';
 
import * as actions from './actions';
 
const signInEpic: Epic = (action$) =>
  action$.pipe(
    ofType(actions.signIn),
    effect(actions.signInEffect, ({ username, password }) =>
      Promise.resolve({ token: 'dummyToken', details: { username, roles: ['user'] } })
    ),
  );
 
export default combineEpics(signInEpic);

This file defines the epics for handling side effects related to the USER model. It uses redux-observable and @blue-functor/remodel to streamline the management of asynchronous workflows:

  • signInEpic:

  • Triggers when the signIn action is dispatched.

  • Uses the effect operator to simulate an API call, returning a mock token and details.

  • Combining Epics:

  • The combineEpics function merges all individual epics into a single export, making it easy to integrate with the application’s middleware.

Epics provide a powerful mechanism for handling asynchronous logic and separating it from reducers, ensuring a clean and maintainable codebase.