-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
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
Time travel #113
Comments
Hmm, why would the state live on the Redux instance? Didn't we decide that the true state "lives" inside the dispatcher? |
It's currently inside The dispatcher is given |
Exactly, so if the true state lives inside the dispatcher, why does meta state need to leak out to the Redux instance at all? Can't it be completely self-contained? |
Dispatcher needs to be completely replaceable at any point of time for hot reloading. If it doesn't fully yield its state to the Redux instance “for keeps”, the next dispatcher won't receive any hidden fields on hot reload. |
Okay, that makes sense. But in that case, I think the dispatcher should be completely stateless, and we pass it a |
So then the state would live completely on the Redux instance and nowhere else. |
To summarize what I'm proposing: createRedux({
dispatcher: (getState, setState, dispatch) => {...} // returns a pure dispatch function, no state
prepareState: (rawState) => {...} // returns state prepared for consumer, stripped of "meta"
}) Ignore the particulars of the API and just focus on the inputs and outputs |
I was worried What is the |
I am, just put export default function thunkMiddleware(getState) {
return (next) => {
const recurse = (action) =>
typeof action === 'function' ?
action(recurse, getState) :
next(action);
return recurse;
};
}
|
Can you PR your proposed changes when you get some time? |
Sure, I will tonight |
Thank you! I really like where this is going 🎉 |
We might not need a dispatcher. Consider this variation of export default class Redux {
constructor(reducer, initialState) {
this.state = initialState;
this.listeners = [];
this.replaceReducer(reducer);
}
getReducer() {
return this.reducer;
}
replaceReducer(nextReducer) {
this.reducer = nextReducer;
this.dispatch({ type: '@@INIT' });
}
dispatch(action) {
const { reducer } = this;
this.state = reducer(this.state, action);
this.listeners.forEach(listener => listener());
}
getState() {
return this.state;
}
subscribe(listener) {
const { listeners } = this;
listeners.push(listener);
return function unsubscribe() {
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
} Very little here, right? Then consider this: import timeTravel, { ActionTypes, StateKeys } from './timeTravel';
import { createRedux } from '../../../src/index';
export default function createDebugRedux(reducer) {
const timeMachine = createRedux(timeTravel(reducer));
return {
timeMachine: timeMachine,
subscribe: ::timeMachine.subscribe,
getReducer: ::timeMachine.getReducer,
replaceReducer: ::timeMachine.replaceReducer,
dispatch(action) {
timeMachine.dispatch({
type: ActionTypes.PERFORM_ACTION,
action
});
},
getState() {
return timeMachine.getState()[StateKeys.CURRENT_STATE]
}
};
} In this example, Our Why do it like this? Using Redux to implement Redux devtools is convenient, but that's not the only reason. Notice that we also export the real Redux instance as a field on the wrapper. This lets us connect devtools like this: const reducer = composeReducers(reducers);
const redux = createDebugRedux(reducer);
export default class App {
render() {
return (
<div>
<Provider redux={redux}> // <------- fake Redux instance
{() => <TodoApp /> }
</Provider>
<Provider redux={redux.timeMachine}> // <------ time machiney actions and state!
{() =>
<DebugPanel top left bottom>
<ReduxMonitor />
</DebugPanel>
}
</Provider>
</div>
);
}
} Guess what One thing I don't mention here is Finally, the middleware. I don't have it here yet, but it seems to be that the middleware is just |
@acdlite I'd love your input on this. If you agree it's the way to go this probably supersedes your current PRs. The “stateless dispatcher” won't matter because there's no dispatcher now, and the other PR will need to be updated because we can try to implement |
👍 Yes, I like this |
Sounds awesome! Just have one question though (perhaps I didn't understand correctly): Since you are wrapping normal action dispatches inside another meta action, what would happen for action creators that return thunk actions? Suppose: export function loadStuff() {
return dispatch => {
api.getStuff().then(stuff => dispatch({type: 'STUFF_RECEIVED', stuff}));
};
} Then if I'm using time travel, I'll be wrapping the thunk inside the meta action Is that something you already thought of and perhaps I didn't understand? I guess that's the part you were talking in the end about middleware, but didn't quite get it. |
@leoasis Yes, this is why I am against re-implementing the existing action middleware as this new API — which is great, but shouldn't be the sole extension point. Action middleware is about transforming a stream of raw actions (which could be functions, promises, observables, etc.) into proper action payloads that are ready to be reduced. |
Ah I see it now, so you'll be keeping both "middlewares" (you @acdlite clarified this to me even further here #166 (comment)). |
I'm probably missing something, but for this: export default function createDebugRedux(reducer) {
const timeMachine = createRedux(timeTravel(reducer));
return {
timeMachine: timeMachine,
subscribe: ::timeMachine.subscribe,
getReducer: ::timeMachine.getReducer,
replaceReducer: ::timeMachine.replaceReducer,
dispatch(action) {
timeMachine.dispatch({
type: ActionTypes.PERFORM_ACTION,
action
});
},
getState() {
return timeMachine.getState()[StateKeys.CURRENT_STATE]
}
};
} Wouldn't you need to implement your own So something like: export default function createDebugRedux(reducer) {
const timeMachine = createRedux(timeTravel(reducer));
return {
timeMachine: timeMachine,
subscribe: ::timeMachine.subscribe,
getReducer() { return reducer; },
replaceReducer(newReducer) {
reducer = newReducer;
timeMachine.replaceReducer(timeTravel(newReducer));
},
dispatch(action) {
timeMachine.dispatch({
type: ActionTypes.PERFORM_ACTION,
action
});
},
getState() {
return timeMachine.getState()[StateKeys.CURRENT_STATE]
}
};
} |
@bdowning |
You seem right. I wonder how it worked for me last time I was testing :-O |
It seems that it works either way because, strictly speaking, the only requirement is that Perhaps there's a better API possible here ( |
Some updates in terms of how we think about it. We've got some basic new terminology:
Here's how time travel fits here:
import timeTravel from 'redux-time-travel';
const createTimeTravelStore = timeTravel(createStore);
const store = createTimeTravelStore(reducer);
// equivalent to
import { lift, unlift } from 'redux-time-travel';
const store = unlift(createStore(lift(reducer)); I'm not sure how coherent it is at this point.. It'll be easier when you see the code (soon :-). |
Is this time travel tool going to be in core, or is the thrust of this issue here just figuring out how to rearrange the core API to allow things like time travel to be easily written (i.e. by Store [new terminology] composition)? |
It's going to be outside the core. Yes, this issue is just about making this possible (and easy). |
And |
* “Stateless Stores” are now called reducers. (#137 (comment)) * The “Redux instance” is now called “The Store”. (#137 (comment)) * The dispatcher is removed completely. (#166 (comment)) * <s>`composeStores`</s> is now `composeReducers`. * <s>`createDispatcher`</s> is gone. * <s>`createRedux`</s> is now `createStore`. * `<Provider>` now accepts `store` prop instead of <s>`redux`</s>. * The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`. * If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it. * The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: #113 (comment). * The `dispatch` provided by the default thunk middleware now walks the whole middleware chain. * It is enforced now that Actions have to be plain object. Use middleware for transforming anything else into the actions. * The object in React context is renamed from <s>`redux`</s> to `store`. * Some tests are rewritten for clarity, focus and edge cases. * Redux in examples is now aliased to the source code for easier work on master.
Naming: * “Stateless Stores” are now called reducers. (#137 (comment)) * The “Redux instance” is now called “The Store”. (#137 (comment)) * The dispatcher is removed completely. (#166 (comment)) API changes: * <s>`composeStores`</s> is now `composeReducers`. * <s>`createDispatcher`</s> is gone. * <s>`createRedux`</s> is now `createStore`. * `<Provider>` now accepts `store` prop instead of <s>`redux`</s>. * The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`. * If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it. * The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: #113 (comment). * The `dispatch` provided by the default thunk middleware now walks the whole middleware chain. * It is enforced now that Actions have to be plain object. Use middleware for transforming anything else into the actions. Internal changes: * The object in React context is renamed from <s>`redux`</s> to `store`. * Some tests are rewritten for clarity, focus and edge cases. * Redux in examples is now aliased to the source code for easier work on master.
Naming: * “Stateless Stores” are now called reducers. (#137 (comment)) * The “Redux instance” is now called “The Store”. (#137 (comment)) * The dispatcher is removed completely. (#166 (comment)) API changes: * <s>`composeStores`</s> is now `composeReducers`. * <s>`createDispatcher`</s> is gone. * <s>`createRedux`</s> is now `createStore`. * `<Provider>` now accepts `store` prop instead of <s>`redux`</s>. * The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`. * If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it. * The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: #113 (comment). * The `dispatch` provided by the default thunk middleware now walks the whole middleware chain. * It is enforced now that raw Actions at the end of the middleware chain have to be plain objects. Internal changes: * The object in React context is renamed from <s>`redux`</s> to `store`. * Some tests are rewritten for clarity, focus and edge cases. * Redux in examples is now aliased to the source code for easier work on master.
Naming: * “Stateless Stores” are now called reducers. (#137 (comment)) * The “Redux instance” is now called “The Store”. (#137 (comment)) * The dispatcher is removed completely. (#166 (comment)) API changes: * <s>`composeStores`</s> is now `composeReducers`. * <s>`createDispatcher`</s> is gone. * <s>`createRedux`</s> is now `createStore`. * `<Provider>` now accepts `store` prop instead of <s>`redux`</s>. * The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`. * If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it. * The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: #113 (comment). Correctness changes: * The `dispatch` provided by the default thunk middleware now walks the whole middleware chain. * It is enforced now that raw Actions at the end of the middleware chain have to be plain objects. * Nested dispatches are now handled gracefully. (#110) Internal changes: * The object in React context is renamed from <s>`redux`</s> to `store`. * Some tests are rewritten for clarity, focus and edge cases. * Redux in examples is now aliased to the source code for easier work on master.
Naming: * “Stateless Stores” are now called reducers. (#137 (comment)) * The “Redux instance” is now called “The Store”. (#137 (comment)) * The dispatcher is removed completely. (#166 (comment)) API changes: * <s>`composeStores`</s> is now `composeReducers`. * <s>`createDispatcher`</s> is gone. * <s>`createRedux`</s> is now `createStore`. * `<Provider>` now accepts `store` prop instead of <s>`redux`</s>. * The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`. * If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it. * The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: #113 (comment). Correctness changes: * The `dispatch` provided by the default thunk middleware now walks the whole middleware chain. * It is enforced now that raw Actions at the end of the middleware chain have to be plain objects. * Nested dispatches are now handled gracefully. (#110) Internal changes: * The object in React context is renamed from <s>`redux`</s> to `store`. * Some tests are rewritten for clarity, focus and edge cases. * Redux in examples is now aliased to the source code for easier work on master.
So here is why I built Redux: |
Redux DevTools will appear here in a week or so: https://github.com/gaearon/redux-devtools |
redux DevTools. redux had been plan to our production release, it's best innovation for flux. |
Redux DevTools 0.1.0 are released. |
Naming: * “Stateless Stores” are now called reducers. (reduxjs/redux#137 (comment)) * The “Redux instance” is now called “The Store”. (reduxjs/redux#137 (comment)) * The dispatcher is removed completely. (reduxjs/redux#166 (comment)) API changes: * <s>`composeStores`</s> is now `composeReducers`. * <s>`createDispatcher`</s> is gone. * <s>`createRedux`</s> is now `createStore`. * `<Provider>` now accepts `store` prop instead of <s>`redux`</s>. * The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`. * If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it. * The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: reduxjs/redux#113 (comment). Correctness changes: * The `dispatch` provided by the default thunk middleware now walks the whole middleware chain. * It is enforced now that raw Actions at the end of the middleware chain have to be plain objects. * Nested dispatches are now handled gracefully. (reduxjs/redux#110) Internal changes: * The object in React context is renamed from <s>`redux`</s> to `store`. * Some tests are rewritten for clarity, focus and edge cases. * Redux in examples is now aliased to the source code for easier work on master.
Naming: * “Stateless Stores” are now called reducers. (reduxjs/redux#137 (comment)) * The “Redux instance” is now called “The Store”. (reduxjs/redux#137 (comment)) * The dispatcher is removed completely. (reduxjs/redux#166 (comment)) API changes: * <s>`composeStores`</s> is now `composeReducers`. * <s>`createDispatcher`</s> is gone. * <s>`createRedux`</s> is now `createStore`. * `<Provider>` now accepts `store` prop instead of <s>`redux`</s>. * The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`. * If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it. * The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: reduxjs/redux#113 (comment). Correctness changes: * The `dispatch` provided by the default thunk middleware now walks the whole middleware chain. * It is enforced now that raw Actions at the end of the middleware chain have to be plain objects. * Nested dispatches are now handled gracefully. (reduxjs/redux#110) Internal changes: * The object in React context is renamed from <s>`redux`</s> to `store`. * Some tests are rewritten for clarity, focus and edge cases. * Redux in examples is now aliased to the source code for easier work on master.
Naming: * “Stateless Stores” are now called reducers. (reduxjs/redux#137 (comment)) * The “Redux instance” is now called “The Store”. (reduxjs/redux#137 (comment)) * The dispatcher is removed completely. (reduxjs/redux#166 (comment)) API changes: * <s>`composeStores`</s> is now `composeReducers`. * <s>`createDispatcher`</s> is gone. * <s>`createRedux`</s> is now `createStore`. * `<Provider>` now accepts `store` prop instead of <s>`redux`</s>. * The new `createStore` signature is `createStore(reducer: Function | Object, initialState: any, middlewares: Array | ({ getState, dispatch }) => Array)`. * If the first argument to `createStore` is an object, `composeReducers` is automatically applied to it. * The “smart” middleware signature changed. It now accepts an object instead of a single `getState` function. The `dispatch` function lets you “recurse” the middleware chain and is useful for async: reduxjs/redux#113 (comment). Correctness changes: * The `dispatch` provided by the default thunk middleware now walks the whole middleware chain. * It is enforced now that raw Actions at the end of the middleware chain have to be plain objects. * Nested dispatches are now handled gracefully. (reduxjs/redux#110) Internal changes: * The object in React context is renamed from <s>`redux`</s> to `store`. * Some tests are rewritten for clarity, focus and edge cases. * Redux in examples is now aliased to the source code for easier work on master.
Reviving musings from #6, I'm going to post my progress on working on time travel & other devtool goodness here.
I have a working proof of concept of time travel that uses the default dispatcher and relies on store composition to keep a log of state. This is not feature-complete (only “jumping to state #N” is supported), but shows how to implement such functionality with Store composition:
Usage:
This relies on the root atom being “normal” a JS object though. I think we'll need to store it internally as
atom
field instead inside a plain JS object created by us to support such “hidden” tool state.I'd accept a PR that hides the user store's state inside an
atom
field on the Redux instance'sstate
object, at the same time preserving the current API and behavior exactly.Redux.getState()
should keep retuning that atom. Any other fields on the internalstate
will be assumed to be only used by the devtools.The text was updated successfully, but these errors were encountered: