Skip to content

Commit

Permalink
xarc-react-recoil added ssr support with store contains recoil atoms (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
durrab authored Mar 11, 2021
1 parent 4acbe1c commit 30d818f
Show file tree
Hide file tree
Showing 8 changed files with 420 additions and 270 deletions.
60 changes: 33 additions & 27 deletions packages/xarc-react-recoil/src/common/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint-disable no-console */
import { SubAppDef, SubAppFeatureFactory, SubAppFeature, envHooks } from "@xarc/subapp";
/* eslint-disable prefer-const */
/* eslint-disable dot-notation */
/* eslint-disable max-statements, complexity */

import { RecoilRoot } from "recoil";
import { SubAppDef, SubAppFeatureFactory, SubAppFeature } from "@xarc/subapp";
import { atom, RecoilState, RecoilRoot } from "recoil";

export * from "recoil";
export * as Recoil from "recoil";

/**
* options for recoil feature
Expand All @@ -13,10 +15,18 @@ export type RecoilFeatureOptions = {
* The React module.
*
* This is needed for the recoil feature to wrap subapp's component inside
* the Recoil Provider component.
* the recoil Provider component.
*/
React: Partial<{ createElement: unknown }>;

/**
* prepare recoil initial state
*
* @param initialState - when SSR sent initialState used, it will be passed. The client
* `prepare` can just return `{initialState}` as is.
*
* @returns Promise<{initialState: any}>
*/
prepare(initialState: any): Promise<any>;
};

Expand All @@ -26,59 +36,55 @@ export type RecoilFeatureOptions = {
export type RecoilFeature = SubAppFeature & {
options: RecoilFeatureOptions;
wrap: (_: any) => any;
RecoilRoot: typeof RecoilRoot;
prepare: any;
atomsMap: any;
_store: any;
};

/**
* Add support for Recoil to a subapp
* Add support for recoil to a subapp
*
* @param options - recoil feature options
* @returns unknown
*/

/**
* @param options - recoil feature options
* @returns unknown
*/
export function recoilFeature(options: RecoilFeatureOptions): SubAppFeatureFactory {
const { createElement } = options.React; // eslint-disable-line
const id = "state-provider";
const subId = "react-recoil";
const add = (def: SubAppDef) => {
const subAppName = def.name;
const subapp = envHooks.getContainer().get(subAppName);
const add = (subapp: SubAppDef) => {
const recoil: Partial<RecoilFeature> = { id, subId };
subapp._features.recoil = recoil as SubAppFeature;
// wrap: callback to wrap component with recoil
recoil.options = options;
recoil.RecoilRoot = RecoilRoot;
recoil.wrap = ({ Component, store }) => {
return (
<RecoilRoot>
<Component store={store} />
</RecoilRoot>
);
};

recoil.prepare = options.prepare;
recoil._store = new Map();
recoil.execute = async function ({ input, csrData }) {
let initialState: any;

const props = csrData && (await csrData.getInitialState());
const initialState = await options.prepare(props);
initialState = (await options.prepare(props)).initialState;

if (recoil.atomsMap === undefined) {
const atomsMap = {};
if (initialState && initialState.atoms) {
initialState.atoms.forEach((state: any) => {
atomsMap[state.key] = state;
});
recoil.atomsMap = atomsMap;
if (initialState) {
for (const value of Object.values(initialState.state)) {
if (value && value["key"] && !recoil._store.get(value["key"])) {
const state = atom({ key: value["key"], default: value["value"] }) as RecoilState<any>;
recoil._store.set(value["key"], state);
}
}
}

return {
Component: () =>
this.wrap({
Component: input.Component ?? subapp._getExport()?.Component,
store: recoil.atomsMap
Component: input.Component || subapp._getExport()?.Component,
store: recoil._store
}),
props: initialState
};
Expand Down
24 changes: 19 additions & 5 deletions packages/xarc-react-recoil/test/spec/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ import { expect } from "chai";
import { SubAppDef, SubAppContainer, envHooks } from "@xarc/subapp";
import { render, waitFor, screen } from "@testing-library/react";
import sinon from "sinon";
import { recoilFeature, RecoilFeature, RecoilRoot } from "../../src/browser/index";
import { recoilFeature, RecoilFeature } from "../../src/browser/index";

const { createElement } = React; // eslint-disable-line

const mockPrepare = async initialState => {
return { atoms: { key: "atomKey", value: {} } };
return {
initialState: {
state: {
todoListState: { key: "todoListState", value: [] },
todoListFilterState: { key: "todoListFilterState", value: "Show All" }
},
selectors: {}
}
};
};

const options = {
Expand Down Expand Up @@ -51,8 +59,6 @@ describe("reactRecoilFeature", function () {
factory.add(def);

const recoil: Partial<RecoilFeature> = def._features.recoil;
expect(recoil.RecoilRoot).equal(RecoilRoot);

expect(recoil.wrap).to.be.an("function");
expect(recoil.execute).to.be.an("function");

Expand Down Expand Up @@ -82,7 +88,15 @@ describe("reactRecoilFeature", function () {
factory.add(def);

const recoil: Partial<RecoilFeature> = def._features.recoil;
const atomsMap = { key: "key", value: "RecoilState" };
const atomsMap = {
initialState: {
state: {
todoListState: { key: "todoListState", value: [] },
todoListFilterState: { key: "todoListFilterState", value: "Show All" }
},
selectors: {}
}
};

render(
recoil.wrap({
Expand Down
7 changes: 6 additions & 1 deletion samples/subapp2-poc/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ export const staticHome = declareSubApp({

export const recoilApp = declareSubApp({
name: "recoilApp",
getModule: () => import("./recoilTodo")
getModule: () => import("./recoil-todo-app")
});

export const characterCounterApp = declareSubApp({
name: "sampleApp",
getModule: () => import("./recoil-character-counter")
});

xarcV2.debug("app.tsx");
7 changes: 0 additions & 7 deletions samples/subapp2-poc/src/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { Demo2 } from "./demo2";
import { message } from "./message";
import electrodePng from "../static/electrode.png";
import custom from "./styles/custom.module.css"; // eslint-disable-line no-unused-vars
import { RecoilRoot } from "@xarc/react-recoil";

export const demo1 = declareSubApp({
name: "demo1",
Expand All @@ -24,14 +23,9 @@ export const demo1B = declareSubApp({
name: "demo1b",
getModule: () => import("./demo1")
});
export const recoilApp = declareSubApp({
name: "recoilApp",
getModule: () => import("./recoilTodo")
});

const Demo1 = createDynamicComponent(demo1, { ssr: true });
const Demo1B = createDynamicComponent(demo1B, { ssr: true });
const RecoilTodoApp = createDynamicComponent(recoilApp, { ssr: true });

export const Demo3 = subAppInlineComponent(
declareSubApp({
Expand Down Expand Up @@ -88,7 +82,6 @@ const Home = props => {
<Demo3 />
<h1>subapp with react-query</h1>
<Demo4 />
<h1>Recoil Todo App</h1>
</div>
);
};
Expand Down
99 changes: 99 additions & 0 deletions samples/subapp2-poc/src/recoil-character-counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { React, ReactSubApp, xarcV2, AppContext } from "@xarc/react";

import { recoilFeature, Recoil } from "@xarc/react-recoil";

const selectorsMap = new Map();
const charCountState = store => {
if (selectorsMap.get("charCountState") === undefined) {
const selector = Recoil.selector({
key: "charCountState", // unique ID (with respect to other atoms/selectors)
get: ({ get }) => {
const text = get(store.get("textState"));
return text.length;
}
});
selectorsMap.set("charCountState", selector);
}
return function () {
return selectorsMap.get("charCountState");
};
};

function CharacterCount(props) {
const count = Recoil.useRecoilValue(charCountState(props.store)());

return <>Character Count: {count}</>;
}

function CharacterCounter(props) {
return (
<div>
<TextInput {...props} />
<CharacterCount {...props} />
</div>
);
}

function TextInput(props) {
const { store } = props;
const [text, setText] = Recoil.useRecoilState(store.get("textState"));

const onChange = event => {
setText(event.target.value);
};

return (
<div>
<input type="text" value={text} onChange={onChange} />
<br />
Echo: {text}
</div>
);
}

const CharacterCounterApp = props => {
return (
<AppContext.Consumer>
{({ isSsr, ssr }) => {
return props ? (
<div
style={{
backgroundColor: "orange",
padding: "5px",
border: "solid",
marginLeft: "15%",
marginRight: "15%",
marginBottom: 20
}}
>
<h1>Recoil Character Counter App</h1>
<CharacterCounter {...props} />
</div>
) : null;
}}
</AppContext.Consumer>
);
};

export { CharacterCounterApp as Component };

export const subapp: ReactSubApp = {
Component: CharacterCounterApp,
wantFeatures: [
recoilFeature({
React,
prepare: async initialState => {
xarcV2.debug("Recoil subapp recoil prepare, initialState:", initialState);
if (initialState) {
return { initialState };
} else {
return {
initialState: {
state: { deal: { key: "textState", value: "My Special Recoil Deals......" } }
}
};
}
}
})
]
};
Loading

0 comments on commit 30d818f

Please sign in to comment.