DEV Community

loading...
Cover image for React Clean Architecture

React Clean Architecture

kpiteng
CEO w KPITENG | Digital Transformation | Web, Mobile, AR/VR, AI/ML, Blockchain App Development | UI/UX Design, IoT Solutions In India, USA
Updated on ・4 min read

Hello Developers! Many of us use various design patterns with React Development to make React Apps more clean, clearly understandable, and more structured. But still we are facing issues with coding standards, we changed at one place and it breaks at another place. Is there any resolution for this? Yes, Try React Clean Architecture!

Quick Highlights - React Clean Architecture - which makes your code more structured, clean and anyone can easily take over your code and start working with you. So Let’s continue next. Which include everything API, State Management (Redux, Redux Saga),Storybook, Utilities, Component/Container and you can add more relevant features in defined structure hierarchy.

React Clean Architecture Covered -

react-clean-architecture
├── android
├── ios
├── src
│   ├── application
│   │   ├── common
│   │   ├── filters
│   │   ├── logger
│   │   ├── models
│   │   ├── persist
│   │   ├── plugins
│   │   ├── store
│   ├── infrastructure
│   │   ├── api(services)
│   │   ├── components (common components)
│   ├── presentation
│   │   ├── container
│   │   ├── component
├── index.js
├── package.json
└── README.md
Enter fullscreen mode Exit fullscreen mode

Application -

Application directory contains the State Management and Common utilities functions and constants. For State Management - I have used Redux Rematch, you can use Redux, Redux Saga,MobX State Management. For Common - I have used Fonts, Colours, Global Constants and Common Functions.

Let’s check some parts of Rematch and Understand what it does and what it means.

Store -

# Store/index.js 

import { init } from '@rematch/core';
import logger from 'redux-logger';

import * as models from '../models';
import { loadingPlugin } from '../plugins';
import { persistPlugin } from '../persist';

export default init({
  models,
  plugins: [loadingPlugin, persistPlugin],
  redux: {
    middlewares: [logger],
  },
});
Enter fullscreen mode Exit fullscreen mode

Here, I am initialize Redux Store, to initialize Redux Store we require models, plugins, middleware (optional).

PlugIns -

PlugIns itself means to add some value to Redux Store.Here, we are using Loading PlugIns means it will show loading indicator while API is fetching data. So we can show Loader to user, once data is fetched Loading plugin update loading State and based on that we can hide loaders in components.

import createLoadingPlugin from '@rematch/loading';

export const loadingPlugin = createLoadingPlugin({
  whitelist: ['ToDo/fetchTasks'],
});
Enter fullscreen mode Exit fullscreen mode

Persists -

Persist itself means to Persist something, Here, it will persist Rematch Store. To create persist store it will take few argument, key, whitelist (model - save in persist store), blacklist (model - not saved in persist store), version - help while upgrading application, storage - AsyncStorage (store persist store in AsyncStorage), transform - contains - filters which applied while persist store.

import AsyncStorage from '@react-native-community/async-storage';
import createRematchPersist from '@rematch/persist';
import { AllFilters } from '../filters';

export const persistPlugin = createRematchPersist({
  key: 'root',
  whitelist: ['ToDo'],
  version: 1,
  storage: AsyncStorage,
  transforms: AllFilters,
});
Enter fullscreen mode Exit fullscreen mode

Models -

Models will contain State, Reducer, Effect (Actions).

import { List } from '../../infrastructure/api/api';
export const ToDo = {
  state: {
    arrTasks: [],
    arrAPITasks: [],
    totalTasks: 3,
  },
  reducers: {
    setTasks(state, payload) {
      return {
        ...state,
        arrTasks: payload,
      };
    },
    setAPITasks(state, payload) {
      return {
        ...state,
        arrAPITasks: payload,
      };
    },
    clear() {
      return {
        arrBeneficiary: [],
      };
    },
  },
  effects: (dispatch) => ({
    async fetchTasks() {
      try {
        dispatch.ToDo.setTasks([
            {
                taskID: 1,
                taskName: 'Task #1',
            }
        ]);
      } catch (error) {
      }
    },
    async fetchTasksFromServer() {
      try {
        const response = await List.getListData().toPromise();
        dispatch.ToDo.setAPITasks(response);
      } catch (error) {
      }
    },
  }),
};
Enter fullscreen mode Exit fullscreen mode

Filters -

import { createBlacklistFilter } from 'redux-persist-transform-filter';

const toDoFilter = createBlacklistFilter('ToDo', ['totalTasks']);

export const AllFilters = [toDoFilter];
Enter fullscreen mode Exit fullscreen mode

Common -

Here, You can define your Global Constant, Common Files - Fonts, FontSize, Device Specification, Colors many more as per custom solution.

exports.globalVars = {
    userSalt: 'TOHV7eOQRAXmbe433BilgtJeCkugs1rgvZ',
    currentCountryCode: '',
};
export const BaseURL = "https://jsonplaceholder.typicode.com/";
export const TaskList = 'todos/';
export const apiVersion = 'events/';
export const Authsecret = '';
export const timeoutDuration = 30000;

// Error Messages
export const errorEncountered = 'Error was encountered processing this request';
export const timeoutMessage =
    "We are unable to fetch data at this time, kindly check your internet connection and we'll reconnect you.";

Enter fullscreen mode Exit fullscreen mode

Read Best JavaScript Coding Practices

Infrastructure -

Infrastructure contains API (Services) Files, API Handlers, Common Components like Loader, Common TextField, Buttons, etc. Here, I have used AXIOS, you can use JavaScript Fetch and create your API Wrapper class here.

Let’s check some parts of Insfrastructure and Understand what it does and what it means.

Read More About - Zero Bundle Size - React Server Components

API (Services) -

# api/api/List.js

import APIHandler from '../APIHandler';
import * as Globals from '../../../application/common/Globals';

export default {
  getListData: () => APIHandler.get(Globals.TaskList),
};
Enter fullscreen mode Exit fullscreen mode
# api/APIHandler.js

import { Alert } from 'react-native';
import { Observable, throwError, from } from 'rxjs';
import {
  mergeMap, retryWhen, take, delay, catchError, map,
} from 'rxjs/operators';
import axios, { AxiosPromise } from 'axios';
import * as Globals from '../../application/common/Globals';

async function handleRequest(req) {
  const ts = new Date().getTime();
  req.headers.Accept = 'application/json';
  req.headers.timestamp = ts;
  return req;
}

export default {
  post: (url: string, data: any, options?: any) => processApiRequest(
    axios.post(
      options && options.fullPath ? url : Globals.BaseURL + url,
      data,
      { timeout: Globals.timeoutDuration },
      options && { headers: options },
    ),
  ),
  get: (url: string, options?: any, data?: any) => {
    data = data ? (data instanceof Object && !Object.keys(data).length ? null : data) : null;
    const config = data
      ? { headers: options, data, timeout: Globals.timeoutDuration }
      : { headers: options, data: '', timeout: Globals.timeoutDuration };
    return processApiRequest(
      axios.get(options && options.fullPath ? url : Globals.BaseURL + url, config),
    );
  },
};
Enter fullscreen mode Exit fullscreen mode

Components (Common Components) -

# components/Loader/index.js

import React, { Component } from 'react';
import { View, ActivityIndicator } from 'react-native';
import Styles from './Styles';

function Loader(props)  {
    const { loading } = props;
    if (loading) {
        return (
            <View style={Styles.loaderWrapper}>
                <ActivityIndicator size="large" />
            </View>
        ) 
    } else {
        <View />
    }    
}

export default Loader;
Enter fullscreen mode Exit fullscreen mode

Presentation -

Presentation contains Component/Container. Component return design of your component, While Container contain wrapper of Component, HOC Wrapper Of Connect (Redux) to use Redux Store | Props into Components.

Let’s check some parts of Component/Container, what it does and what it means.

Container/Components -

# component/ToDo/index.js

import React from 'react';
import { SafeAreaView } from 'react-native';
import TaskListContainer from '../../container/ToDo/TaskListContainer';
import Styles from './Styles';

function ToDoManagement() {
    return (
        <SafeAreaView style={Styles.container}>
            <TaskListContainer />
        </SafeAreaView>
    );
}

export default ToDoManagement;
Enter fullscreen mode Exit fullscreen mode
# container/ToDo/TaskListContainer.js

import { connect } from 'react-redux';
import TaskListComponent from '../../component/ToDo/TaskListComponent';

const mapStateToProps = ({ ToDo, loading }) => ({
    arrTasks: ToDo.arrTasks,
    loading: loading.effects.ToDo.fetchTasks,
  });

  const mapDispatchToProps = ({ 
      ToDo: { 
        fetchTasks,
        fetchTasksFromServer,
      } 
    }) => ({
        fetchTasks: () => fetchTasks(),
        fetchTasksFromServer: () => fetchTasksFromServer()
  });

  export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent);
Enter fullscreen mode Exit fullscreen mode
# component/ToDo/TaskListComponent.js

import React, { useEffect } from 'react';
import { SafeAreaView, FlatList } from 'react-native';
import TaskItemContainer from '../../container/ToDo/TaskItemContainer';

function TaskListComponent(props) {
    useEffect(() => {
        props.fetchTasks();
        props.fetchTasksFromServer();
    }, [])
    return (
        <FlatList
            data={props.arrTasks}
            renderItem={({ item, index }) =>
                <TaskItemContainer
                    {...item}
                />}
        />
    );
}

export default TaskListComponent;

Enter fullscreen mode Exit fullscreen mode

Download React Clean Architecture Source Code!

Thanks for reading Article!

KPITENG | DIGITAL TRANSFORMATION
www.kpiteng.com/blogs | hello@kpiteng.com

Discussion (5)

Collapse
5ar profile image
Petar Kovačević

This is a pretty nice way to organise stuff on a top level following a separation of concerns principle.
However, in my experience a similar organisation usually comes more or less naturally and the biggest pain points on large react projects end up being:

  1. How to group independent infrastructure/components files once that folder become too large to manage (too many independent common components).
  2. Similarly, how to structure the presentation layer for larger apps. This part usually follows the projects routing mechanism (i.e. contexts) and then component nesting (i.e. hierarchy), but additional grouping is often needed for complex situations.
  3. Following the previous issue, how much coupling with components/containers can the application folder afford. If it should be avoided then the presentation layer becomes logic heavy, if the opposite is true then you can't reuse anything. As an example, where should a redirect logic be put for a specific successful action (e.g. login) and where should it be put for a common error situation (e.g. 404)?
  4. How to handle, or rather, where to put files that are neither directly presentation or common components (i.e. files that are context specific but reused in different contexts). Imagine having something like an interactive data graph that needs to be repeated as a subcomponent on 3-4 completely different routes, but is always linked to the same API calls and Redux store, should that be in common components or in presentation?

Do you have any rules of thumb or suggestions for these situations within your example architecture?

Collapse
kpiteng profile image
kpiteng Author

Hey Petar,

Thanks for reading article and sharing your suggestions.

I would like to share my thoughts as per my Knowledge.

Each language have multiple paradigm and architecture and all are perfect at their boundary. Every architecture has pros & cons.

Key points is what to pick and it's again depends on your Project Complexity. There is not any thumb rule to go with specific one. I prefer React Clean Architecture what I mention here for Average - Mid Size Project.

For Complex Project I prefer Ignite BoilerPlate - That is most powerful as of now as per my knowledge.

Please checkout Ignite BoilerPlate, You will get all answers for your suggestion - kpiteng.com/blogs/ignite-react-nat...

Thanks Petar for sharing your valuable suggestion, it is adding values to everyone knowledge.

Thanks

Collapse
rafaelrozon profile image
Rafael Rozon

Did you mean to reference the Clean Architecture concepts from Robert C Martin? Or it’s a totally different thing?

Collapse
kpiteng profile image
kpiteng Author

No, I don't about Robert C Martin, But I refer various articles and based on that I created this folder hierarchy.

Clean Architecture is the same paradigm which ideally we followed in our application. This paradigm divide project architecture into 3 parts - Application, Infrastructure and Presentation.

You can use Ignite Boilerplate - as well. Every boilerplate has own paradigm.

But All are perfect at own way.

Thanks

Collapse
deadwin19 profile image
Jay

This is very clean structure, thanks for sharing