Last active
December 15, 2020 15:39
-
-
Save slorber/c095671a14b2eccf8ad665a97a7b8ad1 to your computer and use it in GitHub Desktop.
Use Redux ecosystem without Redux
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
//////////////////////////////////////////////////////////////////////// | |
// Intro | |
/////////////////////// | |
// Tools like Redux-saga, React-redux and Reselect can easily be used without Redux | |
// For Reselet there's nothing to do, it's just not coupled to Redux | |
// For the others, you just need to provide an adapter | |
// At Stample.co we use a legacy framework that is quite close to Redux but with a bad API | |
// We want to progressively migrate to Redux, so starting now to use Redux tools on new features will make our migration faster | |
// Our legacy framework: https://github.com/stample/atom-react | |
// This framework, like Redux, has an unique immutable state object, and you can dispatch events and register event listeners | |
// It is quite similar to many Flux implementations so I believe you can adopt the same strategy if you plan to migrate to Redux | |
//////////////////////////////////////////////////////////////////////// | |
// Redux-saga | |
/////////////////////// | |
// To be able to use redux-saga, you just need a system that listen to and publish events | |
// Hopefully Yassine has worked on my project and Redux-saga to make it flexible enough and being able to work in other contexts | |
// You can provide your own "reduxSagaIO" if the default one (targeting Redux) does not satisfy you | |
// See also https://github.com/yelouafi/redux-saga/issues/16 | |
import { runSaga } from 'redux-saga' | |
const reduxSagaIO = { | |
// publishEvents() is the equivalent of dispatch() for our legacy framework | |
dispatch: events => context.publishEvents(events), | |
subscribe: listener => { | |
const callback = event => { | |
// Note that if you'd like simple action matching like take("ACTION_NAME"), you should be sure that your actions has a type property. | |
// It was not the same in our framework where we had action.name so we add a type automatically here | |
listener({...event, type: event.name}) | |
} | |
// addEventListener() is the equivalent of subscribe for our legacy framework | |
context.addEventListener(callback) | |
// removeEventListener() is how we unsubscribe in our legacy framework | |
return () => context.removeEventListener(callback) | |
} | |
} | |
runSaga(saga,reduxSagaIO) | |
//////////////////////////////////////////////////////////////////////// | |
// React-redux | |
/////////////////////// | |
// To be able to use React-redux, you need the same publish/subscribe as redux-saga requires | |
// but you also need access to the global state object | |
// The connect() method does not require a Redux store to be inside React-context, it only requires a store in context that actually has the Redux-store API | |
// If you build an adapter to your framework with the same API, it will also work | |
// Here's how I transform an atomReactContext to a reduxStoreLike | |
function toReduxStoreAPI(atomReactContext) { | |
// Listeners that will be called on every state change, like Redux does | |
const onStateChangeListeners = [] | |
// This in our legacy is run just after state changes, but just before we try to render it from the very top | |
// As we want to move to Redux, we don't want to render from the top anymore but instead render nested components directly with connect() | |
atomReactContext.beforeRender(() => { | |
onStateChangeListeners.forEach(listener => listener()) | |
}) | |
// We return an object that has the same API as a Redux-store, so it is compatible with connect() | |
return { | |
subscribe: listener => { | |
onStateChangeListeners.push(listener) | |
var isSubscribed = true | |
return function unsubscribe() { | |
if (!isSubscribed) { | |
return | |
} | |
isSubscribed = false | |
var index = onStateChangeListeners.indexOf(listener) | |
onStateChangeListeners.splice(index, 1) | |
} | |
}, | |
dispatch: event => { | |
return atomReactContext.publishEvent(event) | |
}, | |
// in our case, we have a single state object so it's very similar to Redux | |
// if you want to do the same but have many singleton Flux stores, you can easily aggregate them in a single object here | |
getState: () => { | |
return atomReactContext.getState() | |
} | |
} | |
} | |
// Usage is simple like if you were using a normal Redux-store. You have to use the provider, and that's all. | |
import { Provider } from 'react-redux' | |
const Main = React.createClass({ | |
render: function() { | |
return ( | |
<Provider store={toReduxStoreAPI(atomReactContext)}> | |
<AppMainLayout/> | |
</Provider> | |
) | |
} | |
}) | |
// Then you can use connect to get your data injected anywhere | |
// You can also use Reselect without anything else to do |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Someone proposed an implementation for alt here: redux-saga/redux-saga#347 (comment)