Building a React Project Structure from Scratch: A Comprehensive Guide
This article walks through creating a React application without scaffolding tools, detailing project directory layout, chosen technology stack—including React, Redux, Immutable.js, react-router, redux-saga, and related debugging and persistence utilities—while explaining component design, middleware, enhancers, routing, and asynchronous task management.
Introduction
Many scaffolding tools such as create-react-app can generate a React project with a single command, but they hide the learning opportunity of understanding the full project architecture and technology stack. To gain deeper control, this guide builds a React application from 0 to 1.
Project Structure and Technology Stack
We start without any scaffolding, creating each file and importing each third‑party library manually. The resulting directory layout is:
src/ // source code
webpack/ // webpack configuration
webpack.config.js // webpack entry file
package.json // dependency management
yarn.lock // lock file for exact versions
.babelrc // Babel configuration for JSX and ES6
.eslintrc & .eslintignore // ESLint configuration
postcss.config.js // PostCSS configuration
API.md // API documentation entry
docs/ // documentation folder
README.md // project descriptionThe src folder will later be populated with modules, routing, state management, and tests.
Chosen Tech Stack
The stack is selected based on eight considerations:
React and React‑DOM
React Router for routing
Redux as the state container
Immutable.js (optional) for immutable state
Redux‑Persist for state persistence
Redux‑Saga for asynchronous task handling
Jest and utility libraries (lodash, ramda) for testing
Reactotron (optional) for debugging
Development and Debugging Tools
redux‑devtools
Install the browser extension and add the enhancer to the store creation code:
yarn add --dev redux-devtools import { applyMiddleware, compose, createStore, combineReducers } from 'redux';
let composeEnhancers = compose;
if (__DEV__) {
const devExt = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
if (typeof devExt === 'function') {
composeEnhancers = devExt;
}
}
const store = createStore(combineReducers(...), initialState, composeEnhancers(applyMiddleware(...middleware), ...enhancers));Reactotron
Reactotron provides a desktop client for real‑time monitoring of Redux actions, sagas, and network requests.
yarn add --dev reactotron-react-js import Reactotron from 'reactotron-react-js';
import { reactotronRedux as reduxPlugin } from 'reactotron-redux';
import sagaPlugin from 'reactotron-redux-saga';
if (Config.useReactotron) {
Reactotron.configure({ name: 'React Blog' })
.use(reduxPlugin({ onRestore: Immutable }))
.use(sagaPlugin())
.connect();
Reactotron.clear();
console.tron = Reactotron;
}Wrap the root component to enable the overlay:
import './config/ReactotronConfig';
import DebugConfig from './config/DebugConfig';
class App extends Component {
render() {
return (
<Provider store={store}>
<AppContainer />
</Provider>
);
}
}
export default DebugConfig.useReactotron ? console.tron.overlay(App) : App;Component Division
React component design follows four roles:
Presentation Component
Container Component
Goal
UI rendering (HTML & CSS)
Business logic (data fetching, state updates)
Redux awareness
None
Yes
Data source
props
Redux store subscription
State change
Call props callbacks
Dispatch Redux actions
Reusability
Highly independent
Coupled to business logic
Redux
Redux is a predictable state container for JavaScript applications. Key concepts include a single source of truth, a store that holds the state tree, actions as plain objects, reducers as pure functions, and dispatch to trigger state changes.
Middleware
Middleware intercepts actions before they reach reducers, enabling logging, monitoring, routing, etc.
const logEnhancer = (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer);
const originalDispatch = store.dispatch;
store.dispatch = (action) => {
console.log(action);
originalDispatch(action);
};
return store;
};Store Enhancers
Enhancers can extend the store beyond dispatch, allowing full customization of the store API.
react‑redux
The Provider component injects the store via React context, and connect creates higher‑order components that map state and dispatch to props.
class App extends Component {
render() {
const { store } = this.props;
return (
<Provider store={store}>
<div><Routes /></div>
</Provider>
);
}
}createStore
Creating a store often involves applying middleware, saga middleware, and enhancers:
export default (rootReducer, rootSaga, initialState) => {
const blogRouteMiddleware = routerMiddleware(history);
const sagaMiddleware = createSagaMiddleware();
const middleware = [blogRouteMiddleware, sagaMiddleware];
const enhancers = [];
let composeEnhancers = compose;
const store = createStore(combineReducers({ router: routerReducer, ...reducers }), initialState, composeEnhancers(applyMiddleware(...middleware), ...enhancers));
sagaMiddleware.run(rootSaga);
return store;
};Redux with Immutable.js
When the initial state is an Immutable.Map, the default combineReducers fails. Use redux-immutable to combine reducers that accept immutable data.
import { combineReducers } from 'redux-immutable';
import Immutable from 'immutable';
const initialState = Immutable.Map();
export default () => {
const rootReducer = combineReducers({ ...RouterReducer, ...AppReducer });
return configureStore(rootReducer, rootSaga, initialState);
};React Router
React Router manages page‑level UI via declarative routes. Version 4 allows routes to be defined inside components, enabling dynamic routing and code‑splitting.
Static vs. Dynamic Routing
Older versions required a static route tree defined before rendering. v4 lets routes be declared at render time, improving flexibility.
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
el
);
const App = () => (
<div>
<nav><Link to="/about">Dashboard</Link></nav>
<Home />
<Route path="/about" component={About} />
<Route path="/features" component={Features} />
</div>
);Router Hooks
Instead of using onEnter or onLeave, component lifecycle methods such as componentDidMount can control routing.
Router Integration with Redux
Use react-router-redux (install @next for v4) to synchronize router state with the Redux store.
yarn add react-router-redux@next
yarn add history import createHistory from 'history/createBrowserHistory';
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux';
export const history = createHistory();
const middleware = routerMiddleware(history);
const store = createStore(combineReducers({ ...reducers, router: routerReducer }), applyMiddleware(middleware));
// In root component
<ConnectedRouter history={history}>...</ConnectedRouter>Dispatch‑Based Navigation
import { push } from 'react-router-redux';
store.dispatch(push('/about'));Redux Persistence
Persisting the Redux store across page reloads improves startup speed. The redux-persist library provides persistStore and autoRehydrate utilities.
yarn add redux-persist // In store configuration
if (ReduxPersistConfig.active) {
RehydrationServices.updateReducers(store);
}
// persistStore usage
persistStore(store, null, startApp);Version handling stores a reducer version in localStorage and clears the store when the version changes.
Persisting Immutable State
Use redux-persist-immutable to handle immutable data structures, providing a transform that converts between JS objects and Immutable objects.
import { persistStore } from 'redux-persist-immutable';
import immutablePersistenceTransform from '../services/ImmutablePersistenceTransform';
persistStore(store, { transforms: [immutablePersistenceTransform] }, startApp);Immutable.js
When Immutable.js is used, ensure the entire Redux state tree is immutable, persistence works with immutable data, and React Router state is also immutable.
Immutable Router Reducer Example
import Immutable from 'immutable';
import { LOCATION_CHANGE } from 'react-router-redux';
const initialState = Immutable.fromJS({ location: null });
export default (state = initialState, action) => {
if (action.type === LOCATION_CHANGE) {
return state.set('location', action.payload);
}
return state;
};seamless‑immutable
As a lighter alternative to Immutable.js, seamless-immutable offers a more native‑like API while still providing immutability guarantees.
Asynchronous Task Management
HTTP requests are handled with axios, a Promise‑based client supporting both browser and Node environments.
yarn add axiosredux‑saga
redux‑saga is a middleware that manages side effects using generator functions.
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from '../sagas/';
const sagaMiddleware = createSagaMiddleware({ sagaMonitor });
middleware.push(sagaMiddleware);
const store = createStore(rootReducer, initialState, compose(applyMiddleware(...middleware)));
sagaMiddleware.run(rootSaga);Root sagas fork individual module sagas:
import { fork } from 'redux-saga/effects';
import { HomeSaga } from './Home/flux';
import { AppSaga } from './App/flux';
export default function* root() {
yield [AppSaga, HomeSaga].map(saga => fork(saga));
}Example of an app saga handling a post‑list request:
const REQUEST_POST_LIST = 'REQUEST_POST_LIST';
const RECEIVE_POST_LIST = 'RECEIVE_POST_LIST';
function requestPostList(payload) { return { type: REQUEST_POST_LIST, payload }; }
function receivePostList(payload) { return { type: RECEIVE_POST_LIST, payload }; }
function* getPostListSaga({ payload }) {
const data = yield call(getPostList);
yield put(receivePostList(data));
}
export function* AppSaga() {
yield takeLatest(REQUEST_POST_LIST, getPostListSaga);
}The getPostList function uses axios (or a wrapper) to fetch data and format the response.
saga monitoring with Reactotron
const sagaMonitor = Config.useReactotron ? console.tron.createSagaMonitor() : null;
const sagaMiddleware = createSagaMiddleware({ sagaMonitor });Conclusion
This article provides a detailed walkthrough of building a React project from the ground up, covering project architecture, state management with Redux, routing, persistence, immutability, debugging, and asynchronous task handling, offering a solid foundation for front‑end engineers to deepen their understanding of modern web application engineering.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
