Closed b2whats closed 6 years ago
@b2whats I'd move this logic from state.js
to createState.js
and still keep global initial state as an observable. "Naming" reducer in state.js
allows to reusing the same reducer. I like the idea of scoped reducer, thanks! I'll link to that issue in the blogpost.
I came up with this:
// state.js
import Rx from "rxjs";
import createState from "app/rx-state/createState";
import CounterReducer$ from "app/reducers/CounterReducer";
// "counter" and "otherCounter" are like mounting points of the reducer
// can be easily turn into combineReducers
const reducer$ = Rx.Observable.merge(
CounterReducer$.map(reducer => ["counter", reducer]),
CounterReducer$.map(reducer => ["otherCounter", reducer]),
);
export default createState(reducer$);
// createState.js
import Rx from "rxjs";
function createState(reducer$, initialState$ = Rx.Observable.of({})) {
return initialState$
.merge(reducer$)
.scan((state, [scope, reducer]) => ({ ...state, [scope]: reducer(state[scope]) }))
.publishReplay(1)
.refCount();
}
export default createState;
// CounterReducer.js
import Rx from "rxjs";
import CounterActions from "app/actions/CounterActions";
const CounterReducer$ = Rx.Observable.merge(
CounterActions.increment$.map((n = 1) => counterState => counterState + n),
CounterActions.decrement$.map((n = 1) => counterState => counterState - n),
)
.startWith(() => 10); // function which returns initial state (optional)
export default CounterReducer$;
Then initial state works wrong. In first render we have empty state
It's not wrong in my opinion. It's just an initial state which in this case is an empty object and that's what will be emitted.
but the component in the first renderer expects the default state which we have described in reduce
Valid concerns, but no worries. Componens subscribe after state is created with all "default" reducers. Copy&pase the code into the project and you will see.
your example passes the single value, my = {} in fitst render props = {}
component not subscribe after state is created with all "default". file: connec.js
componentWillMount() {
this.subscription = state$.map(selector).subscribe(::this.setState);
}
first render in your example with my implementation Rx.Observable.of({}) // props = {}
export default connect(state$, state => ( {
counter: state.one.counter, // state = {} state.one = undefined state.one.counter = Error
...CounterActions,
}))(Counter);
second render first reducer - .startWith(() => {counter: 1}); // props = {one: {counter : 1}}
Copy&pase the code into the project and you will see.
Please, for further discussion provide an example where it behaves differently.
one moment, will do the fork and show you what I mean
I tried to make an app similar to Redux and moved the initialization state in reducer
file: createState.js
file: CounterReducer.js
file: state.js
what do you say about this approach?