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

support sharing redux store for a request #1518

Merged
merged 1 commit into from
Feb 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/subapp-react/lib/framework-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ class FrameworkLib {
reduxData = await prepare({ request, context });
}

let storeContainer = context.user.storeContainer;

if (!storeContainer) {
storeContainer = context.user.storeContainer = {};
}

if (!reduxData) {
reduxData = { initialState: {} };
}
Expand All @@ -154,7 +160,8 @@ class FrameworkLib {
// next we take the initial state and create redux store from it
this.store =
reduxData.store ||
(subApp.reduxCreateStore && (await subApp.reduxCreateStore(this.initialState)));
(subApp.reduxCreateStore &&
(await subApp.reduxCreateStore(this.initialState, storeContainer)));
assert(
this.store,
`redux subapp ${subApp.name} didn't provide store, reduxCreateStore, or reducers`
Expand Down
55 changes: 35 additions & 20 deletions packages/subapp-react/test/spec/ssr-framework.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,30 +189,45 @@ describe("SSR React framework", function() {
expect(res).contains("Hello foo bar");
});

it("should init redux store and render Component", async () => {
it("should init redux store in context and render Component", async () => {
const Component = connect(x => x)(props => <div>Hello {props.test}</div>);
let storeReady;

const framework = new lib.FrameworkLib({
subApp: {
__redux: true,
Component,
reduxCreateStore: initState => Redux.createStore(x => x, initState),
async reduxStoreReady() {
storeReady = true;
},
prepare: () => ({ test: "foo bar" })
const subApp = {
__redux: true,
Component,
reduxCreateStore: (initState, container) => {
// simulate sharing store in conainter (see subapp-redux/lib/shared)
if (!container.store) {
container.store = Redux.createStore(x => x, initState);
}
return container.store;
},
subAppServer: {},
options: { serverSideRendering: true },
context: {
user: {}
}
});
const res = await framework.handleSSR();
expect(res).contains("Hello foo bar");
expect(framework.initialStateStr).equals(`{"test":"foo bar"}`);
expect(storeReady).equal(true);
async reduxStoreReady() {
storeReady = true;
},

prepare: () => ({ test: "foo bar" })
};
const context = { user: {} };
const verify = async () => {
const framework = new lib.FrameworkLib({
subApp,
subAppServer: {},
options: { serverSideRendering: true },
context
});
const res = await framework.handleSSR();
expect(res).contains("Hello foo bar");
expect(framework.initialStateStr).equals(`{"test":"foo bar"}`);
expect(context.user).to.have.property("storeContainer");
expect(storeReady).equal(true);
};
await verify();
const store = context.user.storeContainer.store;
// should be able to render again with the same store in context
await verify();
expect(store).to.equal(context.user.storeContainer.store);
});

it("should init redux store and render Component but doesn't attach initial state", async () => {
Expand Down
54 changes: 36 additions & 18 deletions packages/subapp-redux/src/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,33 @@ import { createStore, combineReducers } from "redux";
// - a top level initializer can be specified to do this
//

let shared = { namedStores: {} };
let persistenStoreContainer = { namedStores: {} };

function setStoreContainer(container) {
shared = container;
shared.namedStores = shared.namedStores || {};
function initContainer(storeContainer) {
storeContainer = storeContainer || persistenStoreContainer;
if (!storeContainer.namedStores) {
storeContainer.namedStores = {};
}
return storeContainer;
}

function setStoreContainer(storeContainer) {
persistenStoreContainer = storeContainer;
initContainer(storeContainer);
}

function clearSharedStore() {
shared.namedStores = {};
persistenStoreContainer.namedStores = {};
}

function getSharedStore(name) {
return (name && shared.namedStores[name === true ? "_" : name]) || {};
function getSharedStore(name, storeContainer) {
storeContainer = initContainer(storeContainer);
return (name && storeContainer.namedStores[name === true ? "_" : name]) || {};
}

function setSharedStore(name, contents) {
shared.namedStores[name === true ? "_" : name] = contents;
function setSharedStore(name, contents, storeContainer) {
storeContainer = initContainer(storeContainer);
storeContainer.namedStores[name === true ? "_" : name] = contents;
}

const assert = (flag, msg) => {
Expand Down Expand Up @@ -64,21 +74,21 @@ const combineSharedReducers = (info, container, reducers) => {
return combineReducers(addSharedReducer(info, container, reducers));
};

function replaceReducer(newReducers, info) {
let { store, reducerContainer } = getSharedStore(info.reduxShareStore);
function replaceReducer(newReducers, info, storeContainer) {
let { store, reducerContainer } = getSharedStore(info.reduxShareStore, storeContainer);
const reducer = combineSharedReducers(info, reducerContainer, newReducers);
return store[originalReplaceReducerSym](reducer);
}

function createSharedStore(initialState, info) {
const sharedStore = info.reduxShareStore;
function createSharedStore(initialState, info, storeContainer) {
const sharedStoreName = info.reduxShareStore;

if (sharedStore) {
if (sharedStoreName) {
assert(
info._genReduxCreateStore || !info.reduxCreateStore,
`${WHEN_SHARED_MSG}, you cannot have reduxCreateStore`
);
let { store, reducerContainer } = getSharedStore(sharedStore);
let { store, reducerContainer } = getSharedStore(sharedStoreName, storeContainer);
if (store) {
// TODO: redux doesn't have a way to set initial state
// after store's created? What can we do about this?
Expand All @@ -97,14 +107,22 @@ function createSharedStore(initialState, info) {
// that alters argument type is worst
// Maybe create a proxy store object, one for each sub-app
//
store.replaceReducer = (reducers, info2) => replaceReducer(reducers, info2);
// NOTE: It's only on SSR that we need to share store within the
// request and replaceReducer doesn't make sense for SSR, but it's
// taking a storeContainer here anyways.
//
store.replaceReducer = (reducers, info2, storeContainer2) => {
return replaceReducer(reducers, info2, storeContainer2);
};
}
setSharedStore(sharedStore, { store, reducerContainer });
setSharedStore(sharedStoreName, { store, reducerContainer }, storeContainer);
return store;
}

// call user provided reduxCreateStore
if (info.reduxCreateStore && !info._genReduxCreateStore) {
// TODO: given the complexities of dealing with and maintaining store
// allowing user reduxCreateStore is not a good idea. Consider for removal.
return info.reduxCreateStore(initialState);
}

Expand All @@ -121,7 +139,7 @@ function createSharedStore(initialState, info) {
}

function getReduxCreateStore(info) {
return initialState => createSharedStore(initialState, info);
return (initialState, storeContainer) => createSharedStore(initialState, info, storeContainer);
}

export {
Expand Down