CRA-based fractal project structure.
Branch: master
Clone or download

README.md

React Modern

React Modern is create-react-app based project structure.
It has a built-in style of files and directories organization.
It isn't ejected so project can get updates from create-react-app.

Installation

React Modern projects can be created using create-react-modern CLI utility.

Implemented functional

  • [Redux] Code style for writing types, actions and reducers. Fractionally based on redux-ducks.
  • [React Router] Routing organization style (partially).
  • [Project structure] Something kind of upgraded Fractal Project Structure.
  • [NodeJS] Express server and basic structure organization style for it.
  • [Project Settings] The ability to control aliases. Created with react-app-rewired.
  • [Dependencies] Some basic dependencies for your project (redux-thunk, styled-components, moment.js)

Not implemented yet

  • [Project Structure] Organization style for pages (high-level components, used in <Route />) and their interaction with containers.
  • [Project Structure] Don't know how to do perfect directories structure for component and container files of the same component.

Table of contents

Usage

This part describes the main aspects of writing an application using the React Modern structure.

Application basis

The structure of the application is divided into the following logical parts:

  • Assets

    The place where the various static files are located: images, videos, etc.

  • Components

  • Containers

  • Shared

    Various reusable components.

  • Store

    Redux store and everything related to it.

  • Routes

    React Component with all routes of the application.

  • Helpers

    Various functions used in the application.

Each directory has a short alias and can be accessed with @ symbol (for example, @components). Also, there is an alias for @reducers directory, located into @store (it made for comfort).

Index file

index.js file is the core of the application. Here the application connects with redux using Provider and with react-router using Router. The explanation of the parameters passed to them is written further.

This is what the standard index.js file looks like:

// Import React and ReactDOM
import React from "react";
import ReactDOM from "react-dom";
// Import everything related to redux
import { Provider } from "react-redux";
import store from "@store";
// Import everything related to react-router
import { Router } from "react-router-dom";
import history from "@helpers/history";
// Import the layout of our application
import Layout from "./Layout";
// Import serviceWorker
import * as serviceWorker from "./serviceWorker";

// Connect the application to redux and react-router and render it
ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <Layout />
    </Router>
  </Provider>,
  document.getElementById("root")
);

// We don't use serviceWorker
serviceWorker.unregister();

App Component

App.js is React component used to set up your application main structure.

Here you can render global styles, a component with routes and add various additional components that you want to be at the highest level of the application:

import React, { Component } from "react";
import { createGlobalStyle } from "styled-components";
import Routes from "@routes";

const GlobalStyle = createGlobalStyle`
  * {
    box-sizing: border-box;
  }

  html,
  body,
  #root {
    height: 100%;
  }

  body {
    margin: 0;
  }
`;

class App extends Component {
  render() {
    return (
      <React.Fragment>
        <GlobalStyle />
        <Routes />
      </React.Fragment>
    );
  }
}

export default App;

Components and Containers

For convenience during development, the components are divided into several logical parts.

The first is the components themselves. In the simplest case, here is the code responsible for rendering, but there may also be a local state and various functions for interacting with it.

The second is the containers. It's a redux wrappers that allow a component to get the store from redux (or part of it) and various actionCreators into the props.

Redux

General view of the redux store:

alt text

Store

The redux store located in ./src/store directory.

// store/index.js

import { createStore, applyMiddleware, compose } from "redux";
// React Modern uses thunk for creating async actions
import thunk from "redux-thunk";
// Export of all reducers is located in the index file of the directory ./reducers
import reducers from "./reducers";

// Create a redux store using existing reducers
// Also here we connect Thunk and Redux Devtools to the store
const store = createStore(
  reducers,
  compose(
    applyMiddleware(thunk),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )
);

export default store;

Reducers

Here how the reducers export looks like:

// store/reducers/index.js

import { combineReducers } from "redux";

import session from "./session";
import session2 from "./session2";

export default combineReducers({ session, session2 });

Style of writing reducers

General view of the reducer:

alt text

// types.js

// React Modern uses a slightly modified style of redux-ducks to write action types.
export const LOGIN = "session/login";
export const LOGOUT = "session/logout";
// actions.js

import * as types from "./types";

export const login = username => ({
  type: types.LOGIN,
  payload: {
    username
  }
});

export const logout = () => ({
  type: types.LOGOUT
});
// index.js

import * as types from "./types";

const initialState = {
  isAuthorized: false,
  username: null
};

export default (state = initialState, action) => {
  const { payload } = action;

  switch (action.type) {
    case types.LOGIN:
      return {
        ...state,
        isAuthorized: true,
        username: payload.username
      };

    case types.LOGOUT:
      return defaultState;

    default:
      return state;
  }
};

Router

To begin with, the Layout component (in which the application is located) is wrapped in Router:

// src/index.js

// History is created in a separate file.
// In this way, we can import history and use it in absolutely any part of the application
import history from "@helpers/history";

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <Layout />
    </Router>
  </Provider>,
  document.getElementById("root")
);

The Layout component contains all the routes inside it.

// src/Layout.js

const Layout = () => (
  <React.Fragment>
    <GlobalStyle />
    <Routes />
  </React.Fragment>
);

And here are the routes themselves:

// routes/index.js

import React from "react";
import { Switch, Route } from "react-router-dom";

import MainPage from "@components/MainPage";
import Test from "@components/Test";

const Routes = () => (
  <Switch>
    <Route exact path="/" component={MainPage} />
    <Route exact path="/test" component={Test} />
  </Switch>
);

export default Routes;