Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible writeup of all this work? #7

Closed
markerikson opened this issue Mar 8, 2017 · 7 comments
Closed

Possible writeup of all this work? #7

markerikson opened this issue Mar 8, 2017 · 7 comments
Labels

Comments

@markerikson
Copy link

Hi. I do regular searches for any new or recently-updated Redux-related libraries. I've seen your repos pop up in that list pretty frequently. Skimming the readmes, it sounds like you're building some interesting things, but I'm not clear on how things fit together and what all you'd use them for. Any chance of you putting together a long-form article that describes what problems you're trying to solve and how these libraries might be useful for people?

@jedwards1211
Copy link
Member

Yeah, I think I've done a poor job of explaining what this is for. I need to do some more work on the READMEs and maybe even put a demo on GitHub pages. Here's a more lengthy description -- how does this sound?

When a web app gets big enough, initial page load will be too slow unless you split your code into bundles and avoid loading various features until they're needed. For instance, in a React/Redux app, you may have a UserProfileView and userProfileReducer that you don't want to load until the user visits your /user/profile route.

Webpack already provides a foundation for this; you can System.import those modules when needed and Webpack will put them in a separate code bundle. And react-router versions 2 and 3 support getComponent hooks on routes in which you can System.import the component to render.

But these are bare-bones tools: they don't automatically show any kind of spinner and tell the user what's happening while a bundle is loading, and more importantly, they don't automatically show the user an error if it fails to load. Also, if you want to load reducers or redux middleware from a separate bundle, you need some way to install them in your redux store once they've loaded. None of these are incredibly difficult problems, and you may have basic solutions to them, but you may not have time to design a systematic, well-organized, DRY solution.

This is where redux-features comes in! It allows you to define features, each of which may contain a reducer, middleware, React components, or anything else you want. All you have to do is dispatch a loadFeature action, and it takes care of loading them and hooking in your reducer and middleware when it's finished. It stores their loading status and load errors (if any) in Redux state, which you can then connect into a component and display to the user. Together with react-redux-features, you can create a component with just a few lines of code that automatically starts loading a feature when it mounts, displays its loading or error status to the user, and renders a React component from the feature once it's loaded.

redux-features is also a great plugin framework for your app. A third-party developer can write a feature, following the contract of this package, and by simply adding the feature definition via redux-features, it can extend or modify your app's behavior.

@markerikson
Copy link
Author

Neat. FYI, I'm one of Redux's maintainers, and also keep a couple links lists for React/Redux tutorials and resources and Redux addons. This kind of stuff absolutely intrigues me, and I see this idea as kind of the "holy grail" of advanced Redux app structure.

I've compiled a number of related articles on encapsulation and libs for component state and feature encapsulation, but I haven't had time to seriously dig through the various libs I've seen. Some of them look like they have potential for what I generally envision, but I don't think any of them have come up with a fully-developed solution. One similar-ish looking idea is brianneisler/duxtape#1 , but looks like there hasn't been any more progress on that in the last couple months.

So yeah, I don't have any time to try to build anything like this myself, but I'm very interested in the idea as a whole.

@jedwards1211
Copy link
Member

jedwards1211 commented Mar 8, 2017

@markerikson Cool! Well I'm actively using and maintaining this library.

redux-features isn't specifically intended to tackle the reusable component/reducer problem, but it's compatible with it. For instance, you could pass in action type prefixes and mount points when creating features:

counterReducer.js

import {createReducer} from 'mindfront-redux-utils'

export default function counterReducer(actionTypePrefix) {
  return createReducer({
    [actionTypePrefix + 'INCREMENT']: (state = 0) => state + 1,
    [actionTypePrefix + 'DECREMENT']: (state = 0) => state - 1,
  })
}

counterFeature.js

export default function counterFeature({actionTypePrefix, mountPoint}) {
  return {
    async load(store) {
      const counterReducer = (await System.import('./counterReducer'))(actionTypePrefix)
      return {
        reducer: (state, action) => state.updateIn(mountPoint, counterReducer)
      }
    }
  }
}

addFeatures.js

import {addFeature} from 'redux-features'
import counterFeature from './counterFeature'

export default function addFeatures(store) {
  store.dispatch(addFeature('fooCounter', counterFeature({actionTypePrefix: '@@foo/', mountPoint: ['foo']}))
  store.dispatch(addFeature('barCounter', counterFeature({actionTypePrefix: '@@bar/', mountPoint: ['bar']}))
}

Now after you dispatch(loadFeature('fooCounter')), you'll be able to dispatch {type: '@@foo/INCREMENT'} and {type: '@@foo/DECREMENT'} actions and they'll affect the counter at state.foo. Likewise for the barCounter feature.

@jedwards1211
Copy link
Member

jedwards1211 commented Mar 8, 2017

@markerikson redux-dynamix is similar to this, in that it provides a way to add and remove reducers at runtime, but it is very simple. This is a lot more full-fledged, since it has a well-defined way to load features and keep track of their status. I haven't seen any other libraries yet that are focused on asynchronously loading react/redux code bundles.

@jedwards1211
Copy link
Member

jedwards1211 commented Sep 12, 2017

@markerikson I still haven't really pitched the idea behind this as well as I could.

Imagine you're the architect at a big org with a big Stack Overflow-like app. But you're making it an community project and you want people to be able to make their own add-on features for it. So some enterprising soul comes along and decides to write a badges feature, just like Stack Overflow has, that users can install.

The badges feature will touch many different places in the UI: it'll show badge counts next to users, next to questions, it'll add an entire route to the user profile section where they can view all of their badges, and it'll add a Next badge component to the sidebar. And it will need to run its own reducer and middleware (for whatever reason, let's say to fetch badges from somewhere).

So what if you could structure your app in such a way that when a user adds a the badges feature from the marketplace, all it has to do is load badgesFeature from some JS and do:

store.dispatch(addFeature(badgesFeature))

And then voila, with that one line of code, all of those badge components immediately show up where they belong, the user badges route is now accessible, and the reducer and middleware for the badges is being run. Without the user even needing to reload the page!

That's the power redux-features gives you. All your app has to do is define "insertion points" for the components from features:

import {featureComponents} from 'react-redux-features'

const UserCardComponents = featureComponents({
  getComponents: feature => feature.userCardComponents,
})

const UserCard = ({user}) => (
  <Card>
    <Header>{user.name}</Header>
    {user.reputation} Reputation
    {/*** here is the insertion point ***/}
    <UserCardComponents user={user} />
  </Card>
)

And then when a feature that contains userCardComponents is added, they'll show up at that insertion point.

const UserBadges = connect(selectBadgesForUser)({goldBadges, silverBadges, copperBadges}) => (
  <div>
    <Badge color="gold" /> {goldBadges}
    <Badge color="silver" /> {silverBadges}
    <Badge color="copper" /> {copperBadges}
  </div>
)

export const badgesFeature = {
  userCardComponents: UserBadges,
}

@mainrs
Copy link

mainrs commented Dec 14, 2017

@jrmclaurin I actually tried to implement something similar for one of my apps that I am working on. I wanted some kind of plugin system were peope could add reducers, middlewares, routes and components as plugins and load them at startup. I am not sure how webpack's System.import works but is it possible to load features from fles that are not known at compile time to webpack? This would be a similar plugin system like atom or vscode use.

@jedwards1211
Copy link
Member

@sirwindfield yes it's definitely possible, the simplest way would be something like this. I don't know if there are more state-of-the-art ways to interface between webpacked and non-webpacked code, maybe there are but I haven't looked lately.

In webpacked code

window.__addFeature__ = function (id, feature) {
  store.dispatch(addFeature(id, feature))
}

In non-webpacked code

window.__addFeature__('myPlugin', {
  reducer: ...,
  middleware: ...,
  rootRoutes: [
    <Route key={0} path="/foo" component={...} />,
    <Route key={1} path="/bar" component={...} />,
  ],
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants