React + Redux share actions between modules/domains - reactjs

Imagine you have an application with 2 modules (split up in the ducks way).
One is the eagerly loaded Notification module, which is used to display notifications when something succeeds or fails.
The other is a Calculation which makes some calculation
- Notification
- components
- actions
- ...
- index.js
- Calculation
- components
- actions
- ...
- index.js
In a lot of architecture articles they recommend that you should then export the action creators for each module through an index.js file, that sort of functions as the public API of your module.
For example, if I wanted to expose the a success action creator of my Notification module, then I'd export it from the index.js file in that module. Now my other modules can import these action creators.
I like this idea of a public facing API in your module.
What I find troublesome with that way of working is that you then very tightly couple the module to the redux library. Because if I'd switch to a new Notification module, then this module would have to expose action creators too, which is tied to redux.
Is my concern valid? If so, can you suggest a better (but still idiomatic) solution?
What I would do in Angular is the following:
I'd expose from the Notification module a singleton service that acts as the public facing API of this module. If any other module (eg. Calculation) needed to use a feature in the Notification module they could inject the service using dependency injection and call notificationService.addNotification('message'). In that singleton service I would then call the dispatch method on my store.
The Calculation module does not need to know whether the NotificationModule uses a store or not. And I could easily switch around the Notification module, as long as a public facing singleton service still exposed the addNotification method. By inverting the dependencies, I don't need to go change every module that uses the Notification module.
Thanks for your suggestions!

What about using connect function? This way your component
Can be user without Redux at all
dispatch and other similar redux staff will be hidden behind connect
Here is example
export const MyComponent = ({ alertState, notificationsArray, SetAlert, AddNotification }) => {
return <div>
Alert state: {alertState.toString()}
<button onClick={() => SetAlert(!alertState)}>Toggle alert</button>
<div>
Notifications: {notificationsArray.map(n => `${n}, `)}
<button onClick={() => AddNotification()}>Add notification</button>
</div>
</div>
}
export default connect(state => ({ alertState: state.alert.alertState, notificationsArray: state.notifications.notificationsArray }), {...Alerts.actionCreators, ...notification.actionsCreators})(MyComponent)
Note, that inside MyComponent there is no dispatch. So you can use MyComponent without Redux by doing
// Another file
import { MyComponent } from './MyComponent.js'
export const App = () => {
return <MyComponent alertState={true} SetAlert={(alert) => console.log(alert)} notificationsArray={[ 'notification1', 'notification2' ]} AddNotification={() => {}} />
}
Or, if you want to use it as connected, do
// Some third file
import MyComponent from './MyComponent.js' // Note, that now it is default import
export const AnotherComponent = () => {
return <MyComponent />
Now notice, that I don't provide any props to MyComponent a they will be provided by connect.
You can also move call to connect to some other file. So MyComponent will be completely independent of Redux.
You're also not obligated to fully connect MyCompoent to Redux. You can partially connect it
export default connect (state => ({ alertState: state.alert.alertState }), Alerts.actionCreators)(MyComponent)
Now you should provide notifications and AddNotification when invoking MyComponent as they are not taken from Redux.

I think the idiomatic Redux way is for the Calculation module to dispatch an action, and modules interested in that action to handle the action in their reducers. Since all actions are passed to all reducers, this makes for less tight coupling between action dispatcher and action consumer. In this case, the Calculation module doesn't need to care about which components, how many components, or indeed if any components are watching for that action. (Although in most cases, I find that I do create an action producer and one consumer -- and in most cases just one consumer -- of that action, and even though they are loosely coupled, I work on both at the same time.)
I suppose in theory it is possible to create a Notification singleton that you can call from the Calculation module, that would in turn dispatch an action that is handled only by the Notification module itself. I'm not too familiar with how Angular works, but it seems that if you are calling a function exposed by Notification, that creates tight coupling between the components. If you later want to switch out that Notification component for another, would you have to see up all the binding again? And what if other components are interested in the Calculation success? Does the Calculation module have to call functions exposed from singletons in those modules too, introducing more tight coupling?
As with most things, it seems there are pros and cons with both approaches. If you buy into the Redux way of doing things, less tight coupling between components is one of the 'pros', at the expense of less flexiblity should you decide down the road that you want to switch out Redux for a different approach.

You might be thinking/assuming some things incorrectly.
If you think in a sense, Actions/reducers etc are organized and written here in a way that makes those particular modules independent. So, Notification here is independent and so is the Calculation. Code for both are inside their respective folders. Calculation module need not worry of what is happening around the world. Calculations related stuffs are done and relevant actions are dispatched or reducers are updated. Now, if some module (e.g. Notification) wants to do something when Calculation is success, it can listen out for the success dispatch in its own territory.
(Note here that we need not make any changes in the Calculation module for Notification module to work). So, both are decoupled.
What I find troublesome with that way of working is that you then very tightly couple the module to the redux library Yes that is absolutey correct but isn't that what happens when you create a project using some particular framework. You use the syntaxes and the features provided by those libraries but that makes the overall project tightly bound to that library and if you change the library, a lot of code has to be re-written as per the new library or guidelines (unless there is some intelligent compiler). But, this doesn't makes modules coupled (here in redux at least)
This means that when I want to replace my Notification module with another Notification module that doesn't use redux, I'll have to refactor my whole app to not use the dispatch function anymore to create a success. Yes you have to because underlying library has changed. I am not an expert in angular but even in your angular project, if you decide to use something else for the Notification module, I am sure you have to rewrite a lot of stuff in or around the Calculation module for things to work out.
I think what you are saying generally happens if there are very big projects written badly which led to origin of micro-services like architecture. Take some example of e-commerce website. Initially, Authentication, Search, Checkout, Payment (basically backend services) etc were written altogether and so they were tightly coupled. Later on people created micro-services out of them and each of them can communicate with one another using APIs. Now, each of the service and underlying framework can be changed without effecting other but standard APIs are there. Similarly, in frontend as well, you can have such things but it essentially means that you have separate projects altogether which need to communicate and not the modules inside the same project. But it will have same issues be in Redux or in Angular.
Edit: Have updated few points after discussion in comments:
Can you have micro-frontends
Yes, you can have micro-frontends such as Notifications in ReactJsandCalculations in AngularJs and use some public methods such as [window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) or eventListeners but there will be pros and cons to it
Few articles which I found:
Frontend in Microservice Architecture
Micro frontends—a microservice approach to front-end web development
https://micro-frontends.org/
Is it popular right now?
No.
I think some of the reasons being:
Compared to backend, frontend is more of UI/UX and has to look consistent in look and feel wise. So, achieving that might be a bit of issue
Network latency is a big issue and a lot needs to be downloaded. Using 2 or 3 frameworks means you have to download additional data for that framework to work. E.g. both React and Angular library etc. If you see, a lot work goes on reducing the download size which will increase upon increasing the number of frameworks
Most of the websites don't have many pages. Say at max 10-12 different pages and so, creating all of them in one framework is easy and cheap. However, if the project gets big, then big companies do divide. There are very big projects where a.domain.com is in reactJs, b.domain.com is in angular. But that generally happens when projects are big and completely separated from one another.
So, yes you can have it but it depends a lot on factors including but not limited to resources, price, availability etc
If you want to build the micro-frontend, you can use
window.postMessage
EventListeners
Isolating micro-apps into IFrames using libraries and window.postMessage APIs to coordinate. IFrames share APIs exposed by their parent window
Event Emitters (a very good library for the same is - https://github.com/chrisdavies/eev)
Using html5 storage and listening to them for the changes (on in general anything which lets us to play in or around dom/window because that will be the API layer to help us communicate between different modules)
Hope, I am able to clarify if for you. Revert for any doubts/confusion.

To decouple a state-module from the Redux dispatch/action paradigm, you can expose a "public API" via hooks:
For example, suppose your module has an action:
// notifications/actions.js
const createNotification = (data) => ({
type: 'CREATE_NOTIFICATION',
data
});
// ...
In your module, define a hook that returns a function that dispatches the action:
// notifications/hooks.js
import { useDispatch } from 'react-redux';
import { createNotification } from './actions';
function useCreateNotification() {
const dispatch = useDispatch();
return (data) => dispatch(createNotification(data))
}
// ...
Now your component doesn't have to know about dispatch/actions. Just import and use the hooks.
// components/MyComponent.js
import React from 'react';
import { useCreateNotification } from './notifications/hooks'
function MyComponent() {
const createNotification = useCreateNotification();
const handleClick = () => createNotification('foo');
return (
<button onClick={handleClick}>Create Notification</button>
);
}
If you need the public API to expose plain (non-hook) functions, you can do this via a higher-order-function that
takes dispatch and returns a set of functions. For the sake of this example, these functions will be termed "endpoints".
// endpoints.js
import * as actions from './actions';
const createEndpoints = (dispatch) => {
const createNotification = (data) => {
dispatch(actions.createNotification(data))
}
// ...
return {
createNotification,
// ...
}
}
Call the higher-order-function by giving it dispatch:
// store.js
import { createStore } from 'redux';
import rootReducer from './reducer';
import { createEndpoints } from './notifications/endpoints';
export const store = createStore(rootReducer, {});
export const {
createNotification,
// ...
} = createEndpoints(store.dispatch);
Now your UI doesn't have to know about dispatch, actions, or hooks; just call the plain functions as such:
// MyComponent.js
import { createNotification } from './store'
function MyComponent() {
const handleClick = () => createNotification('foo');
return (
<button onClick={handleClick}>Create Notification</button>
);
}
With this approach, you are largely decoupled from a redux implementation. You will still rely on having a redux "dispatch" in order to use the module, but now you are coupled at one point (createEndpoints) instead of many points throughout your components.

Related

How to use separation of concern with react-query (in a clean architecture context)

I'm currently thinking about the perfect architecture for my professionals projects needs.
I read a lot of article about (clean) architecture and I got to the point were I think that I want my UI managed with React totally separated from the application business logic that will be managed by "application manager". The issue is that I want the "application manager" to config and trigger mutations (I think get queries can be used in components without any issue). But since react-query require it to be in React component by using hooks, I don't think it is possible.
I am wrong ?
Does it exist a workaround ?
Maybe you have a library that manage that better ? I'm thinking about RTK Query maybe...
I am a heavy user of RQ for quite some time and since architecture question can never have an objectively correct answer, I can demonstrate what I do personally.
First, I extract all queries and components into API modules by domain, given a simple app with posts, authors and comments, I would have files along these lines with those exports:
// apis/posts.js
export function useGetPosts() {}
export function useGetPost(postId) {}
export function usePutPost() {}
export function usePostPost() {}
export function useDeletePost() {}
// apis/comments.js
export function useGetComments(postId) {}
export function useGetComment(commentId) {}
export function usePutComment() {}
export function usePostComment() {}
export function useDeleteComment() {}
// apis/authors.js
export function useGetAuthors() {}
export function useGetAuthor(authorId) {}
export function usePutAuthor() {}
export function usePostAuthor() {}
export function useDeleteAuthor() {}
Each of those modules would internally handle everything necessary to work as a whole, like useDeleteAuthor would have a mutation and also modify the cache on success, or possibly implement optimistic updates.
Each will have a system of query keys so that the consumer (your components) don't have to know a thing about them.
function MyComponent() {
const posts = useGetPosts()
}
function MyOtherComponent() {
const deletePost = useDeletePost()
}
Try to make the APIs as complete as possible, but also don't forget that mutations can, for example, accept callbacks on call-site:
deletePost.mutate(payload, {
onMutate: () => setState(false)
})
Let's assume you can use this to for example close a confirmation modal before deleting. Something like this doesn't belong to API module, so we just provide it as a local callback to the mutation.
As stated above, there is no correct answer. There is definitely an argument for doing it the other way round and using collocation more, putting queries next to the components where you are using them. But if you want separation, this would be a place to start in my opinion.
As Ben wrote in the comment to your question, RQ is just hooks, so I agree that trying to put it "outside of react" is non-sensical.
You're right, the short answer is react-query is not compatible with clean architecture, and by experience it leads to tight coupling between logic and components
One way that I'm experimenting with is using the queries in components as is, without implementing side effects. Unless it is side effects specifically for that components.
Then inside my logic layer, I would use the QueryObserver and subscribe to changes to whatever key/keys I need.
const observer = new QueryObserver(myQueryClient, {
queryKey: ['key']
})
observer.subscribe(result => console.log(result))
In this example I have my queryClient defined in its own file.
This way I can have my logic seperated from the view layer, but still use the awesome way react-query works.
Note that this way, the logic will only run when a component is mounted that the query function is resolved.
Also the subscibe function can only be called after the inital useQuery is mounted. Else you will get a "Missing queryFn" error. Which is not ideal. Or even close.

How can I count the number of components on a page that have been connected with redux' connect() function?

I'm doing some performance analysis on a large React/Redux app that uses many connected components (hundreds).
I'd like to keep track of the number of connected components as a performance metric so that I can accurately estimate my per-frame budget for e.g. mapStateToProps and other Redux logic.
React devtools has access to all of the components on a page, so I'm looking to either reuse the same hook it does or would also accept any hook that allows me to enumerate all components on a page (and from there I can figure out if they're wrapped in Connect or not).
react-redux doesn't seem to have specific functionality to hook into connected components, while Redux dev tools are applied to a store as a middleware and are agnostic of connected React components.
A straightforward way is to monkey-patch react-redux module and extend connected component with specific behaviour, a demo:
import * as reactRedux from "react-redux";
let connectedCount = 0;
function patchConnect(connect) {
return (...args) => {
const wrapWithConnect = connect(...args);
return WrappedComponent => {
const Connect = wrapWithConnect(WrappedComponent);
return class ConnectWithCounter extends Connect {
componentDidMount() {
console.log(++connectedCount, this);
super.componentDidMount();
}
componentWillUnmount() {
console.log(--connectedCount);
super.componentWillUnmount();
}
};
};
};
}
reactRedux.connect = patchConnect(reactRedux.connect);
reactRedux.connectAdvanced = patchConnect(reactRedux.connectAdvanced);
// import the rest of the app that imports redux-react
import('./app').catch(console.error);
react-redux should be patched first, and modules should be writable. This puts restrictions on modular environment, because ES modules can be read-only (they are, according to the spec) and import statements may go before the rest of the code (they should, according to the spec). There's a chance that reactRedux.connect = ... patching will happen too late or won't happen at all.
For instance, the example uses Codesandbox which would hoist import './app' without an error, and the same example wouldn't work in Stackblitz because SystemJS module implementation results in read-only imports.
It's preferable to configure development environment to use CommonJS modules because require allows this kind of modifications.

Accessing Redux Store in a Util file

TLDR: I want to be able to grab the latest Redux State in an external "Util" file. How can I do this?
Say I have a playlist.. and in many different areas of the app, you can "Start" the playlist. So in a "Util" file I have the "startPlaylist" function so I dont have to write the function in numerous places but in only one place.
The problem with this, is that if I make any changes to the playlist while the playlist is running, "playNextPageInPlaylist" function will not receive any updates to the playlist.
What can I do and change so that my function(s) in the Util file will receive the most updated Redux State?
I have startPlaylist function in 7 different areas, and the functions it involves (all in the Util file) are quite complex.. and it wouldn't make sense to copy and paste that in all 7 files.
Thanks for any help
React.Component File 1
import { startPlaylist } from '../util/EntitiesUtil';
start1() {
startPlaylist( store.playlists[0] );
}
React.Component File 2
import { startPlaylist } from '../util/EntitiesUtil';
start2() {
startPlaylist( store.playlists[0] );
}
EntitiesUtil.js
export function startPlaylist( playlistFromStore ) {
// do stuff
playNextPageInPlaylist( playlistFromStore ); // keeps grabbing next page on a timer
}
You got couple of options, the main options as i see it are:
pass the store to the function (bah please don't do that!).
You can write your own middleware that handles certain action types
and can dispatch other actions if needed (you also get a free
access to the ENTIRE store!).
I think the 2nd option is ideal, as you want your util to do stuff that reflect in the store or need stuff from the store. So basically your util wants to be a part of the redux flow!
Well it's not a component so you can't "connect" it but it can (and should be in my opinion) ad middleware that sits between your actions and reducers.
You can read about middlewares here.
I would have provided you an example of your use case but you didn't post any meaningful code.
Edit
A followup to your comment:
Its quite basic.
You have a signature of a function that never changes, just look at
the docs (it uses
currying,
this is another js topic you should learn)
You need to inject it to the store when you create it with
applymiddleware (same as you did with redux-thunk which is a
middleware by itself).
I realy recommend to look at the source code of redux-thunk the whole 11 lines of it.
You can learn a lot from it.
I believe the store has a getState() method available to you.
Import your created store and then call store.getState()
Check out this example from redux's main site:
http://redux.js.org/docs/api/Store.html#example
function select(state) {
return state.some.deep.property
}
let currentValue
function handleChange() {
let previousValue = currentValue
currentValue = select(store.getState())
if (previousValue !== currentValue) {
console.log(
'Some deep nested property changed from',
previousValue,
'to',
currentValue
)
}
}
We faced a similar issue in using corporate ui react library where state creation was delegated to core library. Thus, exporting store as public variable was not an option to us.
However, there is a horrible way of 'public static variable' that will be updated with your root reducer or 'slicing reducers'.
so, you should make some 'store-util.tsx' with 'let utilStore' variable and export some setter (for reducer) and getter (for any utility functions) functions.

Data models and business logic in isomorphic (React/Redux/Express/Mongo) app

I've recently built a few isomporphic/univeral projects using the React-Redux-Express-Mongoose stack.
In my mongoose models is contained a lot of business-logic. As a very basic example (excuse my ES6):
import mongoose, {Schema} from 'mongoose';
const UserSchema = new Schema({
name: String,
password: String,
role: String
});
UserSchema.methods.canDoSomeBusinessLogic = function(){
return this.name === 'Jeff';
};
UserSchema.methods.isAdmin = function(){
return this.role === 'admin';
};
This is all great on the server, however when these models are hydrated in the browser as plain JSON objects, I then have to re-implement this same business logic in some React component or Redux reducer, which doesn't feel very clean to me. I'm wondering how best to approach this.
From reading around Mongoose, there seems to be limited browser support, mostly just for document validation. I suppose my main options are:
Move all the business logic into some "normal" JS classes, and instantiate those all over the place. For example:
# JS Class definition - classes/user.js
export default class User {
constructor(data = {}){
Object.assign(this,data);
}
canDoSomeBusinessLogic(){
return this.name === 'Jeff';
};
isAdmin(){
return this.role === 'admin';
}
}
# Server - api/controllers/user.js
import UserClass from
User.findById(1,function(err,user){
let user = new UserClass(user.toJSON();
});
# Client - reducers/User.js
export default function authReducer(state = null, action) {
switch (action.type) {
case GET_USER:
return new UserClass(action.response.data);
}
}
# Client - containers/Page.jsx
import {connect} from 'react-redux';
#connect(state => ({user: state.user}))
export default class Page extends React.Component {
render(){
if(this.props.user.isAdmin()){
// Some admin
}
}
}
Move all the business logic into a some static helper functions. I won't write out the whole example again, but essentially:
# helpers/user.js
export function isAdmin(user){
return user.role === 'admin';
}
I suppose the difference between the above 2 is just personal preference. But does anyone have any other thoughts about isomorphic apps and data modelling? Or have seen any open-source example of people solving this problem.
As an extension to the above, what about an isomorphic save() function e.g. User.save(). So if called on the client it could do a POST to the relevant API endpoint, and if run on the server it would call the Mongoose save() function.
Spoiler: Expect an opinionated reply. There is no 'right' way to do it.
First of all, I want to make the difference between isomorphic and universal clear, so that you know exactly what we are talking about:
Isomorphism is the functional aspect of seamlessly switching between client- and server-side rendering without losing state. Universal is a term used to emphasize the fact that a particular piece of JavaScript code is able to run in multiple environments.
Is it worth it that much abstraction into an universal app?
Generally what you want an universal app for is to have the client and the server that pre-renders the app both loading the same code. Although you can run the API from the same server that pre-renders the app, I would rather proxy it and run it in a different process.
Let me show you two different React repositories:
React + API erikras/react-redux-universal-hot-example
React wellyshen/react-cool-starter
Erikras well-known boilerplate uses his universal app to share dependencies globally, and code between the server that pre-renders the page and the client. Although he could, he does not share validation. Survey API validation Survey client validation
Wellyshen does not have an API, but he also shares his dependencies and code, though only between the server and the client. The server loads the routes, the store and everything that is being run by the client app. That is to provide isomorphism.
Having said that, it is up to you whether to move all validation in one place. I probably would just consider it for complicated validation cases, like an email validation which you could actually have a helper for that. (that was just an example, for email validation you already have validator). In certain occasions, it might be more convenient to rely on the API validation, albeit not being the best practice.
Simple validations, like the ones in your examples, can be done effortless with redux-form anyway, which that I know there is no direct way to translate it on the API. Instead you should probably be looking for express-validator on it.
One more thing, despite the fact that a few very popular React boilerplates will have the API and client together, I tend to work with two different repositories: the React + server-side rendering and the API. In the long term run it will result in a cleaner code that will be totally independent one from the other. organizing-large-react-applications

The Complexity of dom manipulation in React + Redux

I'm trying to simplify the workflow when using React and Redux.
For basic DOM Manipulation tasks like scrolling an element down or performing a check for something in an interval, you have to declare many functions and all of a sudden you may have 100 lines of code when there should only be a few lines of code.
The following example is with the react-redux starter kit. Using helper modules such as
createAction, handleActions
The task performed is basicly
var objDiv = document.getElementById("id");
objDiv.scrollTop = objDiv.scrollHeight;
First we have to set our action type in the module
export const SCROLL_DOWN = 'SCROLL_DOWN'
Then we have to define the action for scrolling down
export const setScrollDown = createAction(SCROLL_DOWN, (setScrollDown = true) => setScrollDown)
Then the reducer, where we set the state hasScrolledDown which in turn has to be defined in the module we're in.
export default handleActions({
[SCROLL_DOWN]: (state, { payload }) => {
return {
...state, hasScrolledDown: payload
}
}
}, {hasScrolledDown: false})
Next we have to use all of this in the View
We define the propTypes
class MessagesView extends React.Component {
static propTypes = {
setScrollDown: PropTypes.func,
hasScrolledDown: PropTypes.bool
};
And finally we use them in the componentDidMount & componentDidUpdate functions, to manipulate the dom we need to import the react-dom module, so that goes at the top of the document.
import ReactDOM from 'react-dom'
And then we're finally able to scroll down the page.
componentDidMount () {
this.props.setScrollDown(false)
}
componentDidUpdate (prevProps, prevState) {
if (!this.props.hasScrolledDown) {
ReactDOM.findDOMNode(this).scrollIntoView(false)
this.props.setScrollDown(true)
}
}
So, my question is: Am i going about this the wrong way, or is there a simple solution to handle simple dom-manipulation like this?
As you would imagine when having a large application where you need to perform basic dom-manipulation tasks like this here and there, the codebase grows quite fast.
For DOM stuff like this, like scrolling to a certain point, doing the whole Redux state/action/reducer thing is a bit of an overkill. Of course, this all comes down to personal preference, but there's no need to make things more complicated than they are.
Not knowing your entire application, I'm just making assumptions here, but imagine that your page has a button or something which sole purpose is to scroll the user back to the top of the window. This is something Redux or React shouldn't need to care about, so leveraging it to plain old javascript is a heck of a lot easier to figure about. The way to go about this would usually involve componentDidMount, add a click listener to the button, and take care of the scrolling there.
I even use this approach on tooltips and other GUI stuff that I don't need in a persistent state, because usually it doesn't make sense. Redux is great for keeping track of application state, but that doesn't mean you are forced into using it for every little thing that you don't have to be able to reproduce at any given time.

Resources