wafflepie / redux-syringe

💉 Maintaining large Redux applications with ease.
https://redux-syringe.js.org
MIT License
4 stars 2 forks source link

Great library! #14

Open owen26 opened 2 years ago

owen26 commented 2 years ago

I'm surprised to see this library doesn't get too much exposure on the internet because it seems provided one important missing feature from redux official lib.

I previously used redux-dynamic-modules for a while but this one looks a lot better!

I can see it's forked from the previous redux-tools lib with some nice improvements, especially TypeScript support.

I want to integrate it with my current enterprise project, but since I can't find any use cases publicly, just want to double-check with you, is this lib in a state that is production-ready without any known blocking bugs? I hope you don't mind the direct approach @wafflepie , thanks.

wafflepie commented 2 years ago

Hello @owen26, thank you for the kind words. Redux Syringe is (in my opinion) production-ready with no known bugs -- I didn't really need to touch it since the rewrite into TypeScript, so I consider it stable enough.

The reason for this fork: I'm the original author of Redux Tools as well, but I'm no longer associated with Lundegaard. I just wanted to "continue doing things my way without having to confirm/consult everything", though I'm sure it wouldn't be a problem (that, and changing the name).

Couple of things to keep in mind, however:

  1. I don't use @redux-syringe/epics (Redux Observable) anymore, so I cannot confirm that this functionality works 100%, but the few tests seem to be passing.
  2. The redux-syringe preset (npm package) reexports @redux-syringe/actions, which is no longer present in the repository (or the documentation). This is due to backwards compatibility for applications which I'm maintaining. Please don't use any of functions it provides, Redux Toolkit is a much better choice.
  3. Ramda is included as a dependency in most of the packages, which might increase bundle size (unless you're using Ramda already). This is something that I would like to fix eventually (remove all unnecessary dependencies), but it's not a priority for me right now.

Regarding 2 and 3, just make sure you have tree shaking / dead-code elimination set up properly and it shouldn't really matter.

Let me know if there's anything else you'd like to know. Also, we might want to keep this issue open in case more people stumble upon this library by chance.

Good luck!

owen26 commented 2 years ago

Hi @wafflepie, glad to see your reply and I really appreciate it.

I have done a quick demo integration. Had a few issues in the middle but sorted them out eventually. Works like a charm. Even for epics. I don't use namespaces though but I'm sure it's a cool feature in some use caes.

A few notes during the integration (in case other people want to give it a try):

  1. Example code in the documentation uses createStore from redux, but it's totally fine to use configureStore from '@reduxjs/toolkit` with a bit of caution since the toolkit by default applies a couple of recommended middleware, below is a working example that also integrates epics middleware.
import { configureStore } from '@reduxjs/toolkit';
import { makeReducersEnhancer } from 'redux-syringe';
import { makeEnhancer as makeEpicsEnhancer } from '@redux-syringe/epics';

// ...

export const store = configureStore({
  reducer: (state) => state, // this is a must, same as using identity from Ramda
  enhancers: (defaultEnhancers) => {
    return [
      makeReducersEnhancer({
        initialReducers: {
          counter: counterSlice.reducer, // here you initialise the real eagerly loaded reducers
        },
      }),
      makeEpicsEnhancer({ epicMiddleware }),
      ...defaultEnhancers, // orders are important, epics enhancer need to be loaded before applyMiddleware() enhancer, which is inside defaultEnhancers
    ];
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(epicMiddleware),
});
  1. In TypeScript context, it can't infer the correct component types after compose multiple injectors, for example:
import { compose } from 'Ramda'
// or simply use
import { compose } from '@reduxjs/toolkit';

export const Counter1Component = compose(
    withReducers({
      counter1: counter1Reducer,
    }),
    withEpics(counter1Epic)
  )(PureCounter1Component)

// Counter1Component has type `unknown`

This can be solved by a quick workaround annotating your component type manually:

export const Counter1Component: ComponentType<CounterProps & Partial<FeatureAndNamespace>> = compose(...);

Or even better write a utility composeEnhancers function that can infer the correct type. The one that came with Ramda or redux-toolkit is too generic.

Redux is gradually fading away from regular web projects nowadays since there are easier ways to manage simple states now. But with enterprise-level complex scenarios, I still find it hard to replace it.

With this lib covering state modularization in a monorepo & micro-frontend architectures. It's very helpful for my current project.

Thanks again for this awesome lib.

wafflepie commented 2 years ago

@owen26 Awesome!

Ad point 1, although your solution obviously works, the underlying reason is slightly different. The epics enhancer actually needs to be initialized after applyMiddleware(). However, because the enhancers are passed to the compose function, they are applied right-to-left.

Ad point 2, you can try using pipe instead of compose (from Ramda or fp-ts). The inference usually works better when the functions are applied left-to-right, though using pipe with decorators/HOCs is quite counterintuitive.

owen26 commented 2 years ago

1) right! It's indeed get composed from a redux point of view

2) pipe() works, I guess in the case of enhancing a lazy-loaded React component it's fine since all functions composed take the same component as input.

👍 👍