-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
340 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
19 changes: 19 additions & 0 deletions
19
docs/blog/2024-08-Modular-React-Redux/_listings/components/A/component.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
27 changes: 27 additions & 0 deletions
27
docs/blog/2024-08-Modular-React-Redux/_listings/components/A/slice.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
24
docs/blog/2024-08-Modular-React-Redux/_listings/pages/_app.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
19
docs/blog/2024-08-Modular-React-Redux/_listings/pages/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
29 changes: 29 additions & 0 deletions
29
docs/blog/2024-08-Modular-React-Redux/_listings/store/reducerManager.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
19
docs/blog/2024-08-Modular-React-Redux/_listings/store/store.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |