Skip to content

Commit

Permalink
Add article on Modular React+Redux
Browse files Browse the repository at this point in the history
  • Loading branch information
basejumpa committed Aug 1, 2024
1 parent 0ca4e49 commit 8571a09
Show file tree
Hide file tree
Showing 10 changed files with 340 additions and 1 deletion.
5 changes: 4 additions & 1 deletion docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ html: .tools/.are-up-to-date deps-are-up-to-date
mkdir -p "$(CONFIG_BUILD__DIRS__BUILD)/$@"
@echo "Building sources at $(CONFIG_BUILD__DIRS__SOURCE)"
poetry run sphinx-build \
-j 1 \
-W \
--nitpicky \
-c "$(CONFIG_BUILD__DIRS__CONFIG)" \
"$(CONFIG_BUILD__DIRS__SOURCE)" \
"$(CONFIG_BUILD__DIRS__BUILD)/$@"
Expand All @@ -151,8 +153,9 @@ html-live: .tools/.are-up-to-date deps-are-up-to-date-dev
# Start continuous build, open browser automatically which reloads on change.
@echo "Building sources at $(CONFIG_BUILD__DIRS__SOURCE)"
poetry run sphinx-autobuild \
-j 10 \
-j 1 \
-W \
--nitpicky \
-c "$(CONFIG_BUILD__DIRS__CONFIG)" \
--watch "$(CONFIG_BUILD__DIRS__CONFIG)" \
"$(CONFIG_BUILD__DIRS__SOURCE)" \
Expand Down
30 changes: 30 additions & 0 deletions docs/blog/2024-08-Modular-React-Redux/_listings/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

import React from 'react';
import { Provider } from 'react-redux';
import store from './store/store';
import ComponentA from './components/A/component';
import ComponentB from './components/B/component';
import ComponentC from './components/C/component';
import ComponentD from './components/D/component';

const registerReducer = (key, reducer) => {
store.reducerManager.add(key, reducer);
store.replaceReducer(store.reducerManager.reduce);
};

export { registerReducer };

const App = () => {
return (
<Provider store={store}>
<div>
<ComponentA />
<ComponentB />
<ComponentC />
<ComponentD />
</div>
</Provider>
);
};

export default App;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { incrementA } from './slice';

const ComponentA = () => {
const valueA = useSelector(state => state.componentA.valueA);
const dispatch = useDispatch();

const incrementValueA = () => dispatch(incrementA());

return (
<button onClick={incrementValueA}>
Increment Value A: {valueA}
</button>
);
};

export default ComponentA;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

import { createSlice } from '@reduxjs/toolkit';
import { registerReducer } from '../../App';

const initialState = {
valueA: 19,
};

const componentASlice = createSlice({
name: 'componentA',
initialState,
reducers: {
incrementA(state) {
state.valueA += 1;
},
resetA(state) {
state.valueA = initialState.valueA;
},
},
});

export const { incrementA, resetA } = componentASlice.actions;

// Register the reducer when this module is loaded
registerReducer('componentA', componentASlice.reducer);

export default componentASlice.reducer;
11 changes: 11 additions & 0 deletions docs/blog/2024-08-Modular-React-Redux/_listings/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
24 changes: 24 additions & 0 deletions docs/blog/2024-08-Modular-React-Redux/_listings/pages/_app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

import { Provider } from 'react-redux';
import store from '../store/store';
import App from 'next/app';

const registerReducer = (key, reducer) => {
store.reducerManager.add(key, reducer);
store.replaceReducer(store.reducerManager.reduce);
};

export { registerReducer };

class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
}

export default MyApp;
19 changes: 19 additions & 0 deletions docs/blog/2024-08-Modular-React-Redux/_listings/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

import React from 'react';
import ComponentA from '../components/A/component';
import ComponentB from '../components/B/component';
import ComponentC from '../components/C/component';
import ComponentD from '../components/D/component';

const Home = () => {
return (
<div>
<ComponentA />
<ComponentB />
<ComponentC />
<ComponentD />
</div>
);
};

export default Home;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

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

export const createReducerManager = (initialReducers) => {
const reducers = { ...initialReducers };

let combinedReducer = combineReducers(reducers);

return {
getReducerMap: () => reducers,
reduce: (state, action) => combinedReducer(state, action),
add: (key, reducer) => {
if (!key || reducers[key]) {
return;
}

reducers[key] = reducer;
combinedReducer = combineReducers(reducers);
},
remove: (key) => {
if (!key || !reducers[key]) {
return;
}

delete reducers[key];
combinedReducer = combineReducers(reducers);
},
};
};
19 changes: 19 additions & 0 deletions docs/blog/2024-08-Modular-React-Redux/_listings/store/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

import { configureStore } from '@reduxjs/toolkit';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import { createReducerManager } from './reducerManager';

const initialReducers = {};

const reducerManager = createReducerManager(initialReducers);

const store = configureStore({
reducer: reducerManager.reduce,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(thunk, logger),
});

store.reducerManager = reducerManager;

export default store;
158 changes: 158 additions & 0 deletions docs/blog/2024-08-Modular-React-Redux/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
.. post:: 2024-08-01
:tags: React, Redux, Software Architecture, Extensibility, Modularity
:language: English


React with Redux the Extensible Way
===================================

With this article I try to provide a comprehensive guide to setting up a client-side rendering (CSR) React application using Redux, with a focus on modularity and extensibility.

I will walk you through the steps needed to configure Redux and manage state effectively in a React application, and discuss the design decisions made to ensure the application is easy to extend and maintain.

The Example Application
-----------------------

This article designs an application to satisfy the following software requirements / design constraints:

The user shall be able to interact with the application by entering values and getting calculated values derived from their inputs.

ComponentA shall have a button. When the user presses this button a value is incremented. The button label shows that value. Initial value is 19.

ComponentB shall have a button as well. When pressed it retrieves a value from an API call and adds it to its own value which is displayed in the button label. Initial state of the own variable is 76.

ComponentC displays the sum of the values of ComponentA und ComponentB.

ComponentD has a button to reset the variables to their initial ones. ComponentD has no knowledge of the initial values.


Project Structure
-----------------

Below is the directory structure of our project:

.. code-block:: text
/src
/components
/A
component.js
slice.js
/B
component.js
slice.js
/C
component.js
slice.js
/D
component.js
slice.js
/E
slice.js
/store
reducerManager.js
store.js
/App.js
/index.js
Setting Up the Project
----------------------

Initialize a React Project
~~~~~~~~~~~~~~~~~~~~~~~~~~

1. Install Create React App:

.. code-block:: bash
npx create-react-app your-app-name
cd your-app-name
2. Install Redux and related packages:

.. code-block:: bash
npm install @reduxjs/toolkit react-redux redux-thunk redux-logger
Design for Modularity and Extensibility
---------------------------------------

In this project, we emphasize modularity and extensibility. Each component manages its own state and reducers, which are dynamically registered to the store. This allows the application to scale easily as new features are added.

Design Alternatives Considered
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1. **Single Store File**: Initially, we considered managing all reducers in a single `store.js` file. However, this approach is not scalable as it requires modifying the store file every time a new reducer is added, leading to tight coupling and reduced maintainability.

2. **Manual Reducer Registration**: Another approach was to manually register reducers in the `store.js` file. This was rejected because it hinders extensibility. Instead, we opted for a dynamic registration mechanism that allows each component to register its own reducer.

3. **Hardcoded Reducers**: Hardcoding reducers in the main store configuration was also considered but discarded due to the lack of flexibility. Dynamic registration provides better modularity and allows for lazy loading of reducers.

Configure Redux Store
---------------------

Create the Redux store configuration files.

.. literalinclude:: _listings/store/reducerManager.js
:language: jsx
:caption: store/reducerManager.js

.. literalinclude:: _listings/store/store.js
:language: jsx
:caption: store/store.js

Configure Redux in React
------------------------

Set up Redux in the React `App.js` file.

.. literalinclude:: _listings/App.js
:language: jsx
:caption: App.js

Create Redux Slices
-------------------

Each component has its own Redux slice, which handles its state and actions. This modular approach keeps the codebase organized and makes it easy to add new features.

Example slice file for component A.

.. literalinclude:: _listings/components/A/slice.js
:language: jsx
:caption: components/A/slice.js

Create Components
-----------------

Each component connects to the Redux store and interacts with its own slice.

Example component file for component A.

.. literalinclude:: _listings/components/A/component.js
:language: jsx
:caption: components/A/component.js

Main Entry Point
----------------

Set up the main entry point to use the components.

.. literalinclude:: _listings/index.js
:language: jsx
:caption: index.js

Running the Application
-----------------------

Start your React application:

.. code-block:: bash
npm start
Your application should now support client-side rendering with React and Redux. This setup ensures that your Redux state is managed correctly and that components interact seamlessly.

Conclusion
----------

By following this guide, you can set up a client-side rendered React application with Redux efficiently. The emphasis on modularity and extensibility ensures that the application is scalable and maintainable. This setup helps in managing the application state effectively, making your React application more robust and easier to extend.

0 comments on commit 8571a09

Please sign in to comment.