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.
Related
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.
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.
I am using react-navigation without redux. so i have two tabs each with their own stack navigator, having one screen each. so i need and array of locations in both screens. currently i am doing this in both screens:
state = { locations: [] };
componentDidMount() {
this.getAllLocations();
}
async getAllLocations() {
let locations = await this.getMoviesFromApi();
this.setState({ locations });
}
i just want to have this array at one location and both components should share this single source of truth. so changes made by either screen is reflected in the other screen. Is this possible without redux?
RN 0.59 has opened great possibilities with its release. One of them are react hooks, which is available in the latest version... in the future react hooks will be used everywhere. Trust me. So, a while back I looked for the possibilities of having a global state using react hooks and found the reactn library. It uses react native hooks, and even you can use global state in CLASS components. which opens a new door for theming and sharing data. Now my app supports light/dark mode, dynamic font size, Languages, and early implementation of "portals" using only this library.
The best part about it is that you can use it like state. There is no need of provider, or redux stuff (although it provides it). It can be integrated with react navigation (it requires modifying some source code, at most, adding an "n" to react, and reference the global variable). Is awesome and I love it.
I have been thinking in doing an article on medium about this, because the lib is not that popular in RN community, but hope that you will give it a chance the library is only 22KB, less than one full component.
As an alternative, you could think about writing your own library using hooks. But it's gonna be hard. Try it, there is no going back
It is possible if you have a singleton object :
export default class SharedData {
constructor(){
if(SharedData.instance){
return SharedData.instance;
}
this.state = {locations:[]};
this.listners =[];
SharedData.instance = this;
return SharedData.instance;
}
setLocations(locations){
this.state.locations = locations;
this.listners.forEach(listner=>listner(this.state.locations));
}
getLocations(){
return this.state.locations;
}
addListner(listner){
this.listners.push(listner);
return listner;
}
removeListner(listner){
let index = this.listners.indexOf(listner);
if(index > -1){
this.listners.splice(index,1);
}
}
}
and then in every tab where you want to access shared locations state:
// get an instance of SharedData
this.sharedData = new SharedData();
// subscribe to locations changes
this.listner = sharedData.addListner((locations)=>{
this.setState({locations});
});
// set locations
this.sharedData.setLocations([]);
// unregister when destroying the component
this.sharedData.removeListner(this.listner);
I guess in order to achieve your goal, you're going to need some sort of a mechanism for storing 'global data', and if you don like Redux because it requires a lot of setup to achieve this simple task of sharing global data, then you chould you unstated ... which is alot simple to setup
My tech lead has given me a challenge to engineer a way only load only parts of the store that is needed for the UI that is loaded in a single page application. This is a big data application so that is why this is important. The idea is that entire store does not need to be loaded because the amount of data.
I implemented similar recently and found How to dynamically load reducers for code splitting in a Redux application? which features a link to http://nicolasgallagher.com/redux-modules-and-code-splitting/ where Nicolas describes how they did it at Twitter.
TL;DR You want lazy-loaded reducers for this. The approach described there is to have a class as a "reducer-registry". You register your reducer/s when you need to use it/them. The registry then calls a listener with a combined reducer which includes all the currently registered reducers. You attach a listener to the registry which calls replaceReducer on your store to update it's reducer.
My implementation is here.. https://github.com/lecstor/redux-helpers/blob/master/src/reducer-registry.ts
In your mapStateToProps you can select the keys of the redux store you need in your component.
For eg.
function mapStateToProps(state) {
const { key1, key2 } = state;
const {subKey, ...restKeys} = key1;
return {
remainder: ...restKeys,
subKey,
key2,
};
}
Now this data can be accessed in the component with this.props.remainder or this.props.subKey or this.props.key2
At the moment I'm creating actions and then a reducer to handle different parts of my app... the different domains.
My app lists classes and pupils.
Currently I have an action that the app has loaded so that I know when to remove the loading spinner, I have actions for classes and pupils. My problem is that I find I need to execute several actions in a row and am not sure if this is a valid way to use redux.
Here is an example function that dispatches several actions after the data is loaded:
/**
* Callback used by readAppData.
*
* #param object ioResult An object: {success: boolean, errorObj?: object, data?: string}
*/
dataLoaded(ioResult: Object) {
if (ioResult.success === true) {
this.props.dispatch(appActions.appHasLoaded());
if (ioResult.data !== '') {
const jsonData = JSON.parse(ioResult.data);
this.props.dispatch(classActions.replaceClasses(jsonData.classes));
this.props.dispatch(pupilActions.replacePupils(jsonData.pupils));
}
} else if (ioResult.errorObj.code === 'ENOENT') { // File doesn't exist.
writeAppData('', this.dataSaved);
} else { // Some other error.
this.props.dispatch(appActions.appHasErrored());
}
}
I was thinking about putting the jsonData.classes and jsonData.pupils into the appActions.appHasLoaded() call and just have a new case for APP_HAS_LOADED in my classes and pupils reducers.
Is this a better approach? To reduce it down to one action? Having separate actions makes it easy to see what is happening... maybe in 6 months time I will have to look through my code to work out exactly what happens on APP_HAS_LOADED if I use it in other reducers. Also the data that is loaded on app start is going to expand beyond just classes and pupils so if I don't combine the calls there could soon be many more dispatches to make - maybe I can store the data in separate files and load each one one at a time which would also fix my problem of having to call mutiple actions in a row.
Is it OK to call dispatch multiple times?
Yes, you can.
From Redux creator Dan Abramov:
Many reducers may handle one action. One reducer may handle many actions.
Referenced in Redux Docs
also, there is a conversation on github about this.