Skip to content

Commit

Permalink
Merge pull request #23 from dgmstuart/keyed-session-data
Browse files Browse the repository at this point in the history
Separate session data per config
  • Loading branch information
dgmstuart authored Apr 22, 2024
2 parents 0327e7e + 85b9f3a commit e6ee863
Show file tree
Hide file tree
Showing 13 changed files with 93 additions and 55 deletions.
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const App: React.FC = () => {
{
path: "qr_code",
element: <QRCode />,
loader: configLoader,
loader: defaultConfigLoader,
},
],
},
Expand Down
8 changes: 4 additions & 4 deletions src/components/Card.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe("'New card' button", () => {
test("changes the words on the card", () => {
render(
<BrowserRouter>
<Card wordList={wordList} name={""} url={""} />
<Card wordList={wordList} name={""} url={""} id={""} />
</BrowserRouter>,
);
const initialCells = screen.queryAllByRole("gridcell");
Expand All @@ -30,7 +30,7 @@ describe("'New card' button", () => {
test("clears any stamped cells", () => {
render(
<BrowserRouter>
<Card wordList={wordList} name={""} url={""} />
<Card wordList={wordList} name={""} url={""} id={""} />
</BrowserRouter>,
);
const cells = screen.queryAllByRole("gridcell");
Expand All @@ -49,7 +49,7 @@ describe("'Clear' button", () => {
test("clears any stamped cells", () => {
render(
<BrowserRouter>
<Card wordList={wordList} name={""} url={""} />
<Card wordList={wordList} name={""} url={""} id={""} />
</BrowserRouter>,
);
const cells = screen.queryAllByRole("gridcell");
Expand All @@ -74,7 +74,7 @@ describe("'Share' button", () => {
const user = userEvent.setup();
render(
<BrowserRouter>
<Card wordList={wordList} name={""} url={""} />
<Card wordList={wordList} name={""} url={""} id={""} />
<textarea rows={5} />
</BrowserRouter>,
);
Expand Down
17 changes: 10 additions & 7 deletions src/components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import share from "../lib/share";
import type { CellProps } from "./Cell";
import type { ButtonClickHandler } from "../clickHandler";

const Card: React.FC<{ name: string; url: string; wordList: string[] }> = ({
name,
url,
wordList,
}) => {
const [cellDataList, toggleStamped, setNewWords, clearAllCells] =
useCard(wordList);
const Card: React.FC<{
id: string;
name: string;
url: string;
wordList: string[];
}> = ({ id, name, url, wordList }) => {
const [cellDataList, toggleStamped, setNewWords, clearAllCells] = useCard(
id,
wordList,
);

const cellPropsList: CellProps[] = cellDataList.map((cellData, index) => ({
...cellData,
Expand Down
6 changes: 3 additions & 3 deletions src/components/DynamicCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import React from "react";
import { useLoaderData } from "react-router-dom";
import Card from "./Card";
import flattenWordList from "../lib/flattenWordList";
import type { Config } from "../data/config";
import type { KeyedConfig } from "../loaders/configLoaders";

const DynamicCard: React.FC = () => {
const { name = "", url, wordList } = useLoaderData() as Config;
const { id, name = "", url, wordList } = useLoaderData() as KeyedConfig;

if (wordList.length > 0) {
// Card saves the word list to the session, so don't render a card if the
// word list hasn't loaded yet
const words = flattenWordList(wordList);
return <Card wordList={words} name={name} url={url} />;
return <Card wordList={words} name={name} url={url} id={id} />;
}
};

Expand Down
8 changes: 5 additions & 3 deletions src/hooks/useCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type GetterSetters = [
ButtonClickHandler,
];

const useCard = (wordList: string[]): GetterSetters => {
const useCard = (id: string, wordList: string[]): GetterSetters => {
const newWords = (): string[] => shuffle(wordList).slice(0, 25);

const newCellDataList = function (): CellData[] {
Expand All @@ -19,8 +19,10 @@ const useCard = (wordList: string[]): GetterSetters => {
});
};

const [cellDataList, setCellDataList] =
useSession<CellData[]>(newCellDataList);
const [cellDataList, setCellDataList] = useSession<CellData[]>({
keyName: `bingoSession-${id}`,
initFunction: newCellDataList,
});

const setStamped = (index: number, stamped: boolean): void => {
setCellDataList(
Expand Down
12 changes: 9 additions & 3 deletions src/hooks/useSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import GuaranteedJsonSession from "../lib/GuaranteedJsonSession";

type GetterSetter<T> = [T, (data: T) => void];

const useSession = function <T>(initialData: () => T): GetterSetter<T> {
const useSession = function <T>({
keyName,
initFunction,
}: {
keyName: string;
initFunction: () => T;
}): GetterSetter<T> {
const session = useMemo(
() => new GuaranteedJsonSession<T>(initialData),
[initialData],
() => new GuaranteedJsonSession<T>({ keyName, initFunction }),
[initFunction],
);

const [data, setData] = useState<T>(session.sessionData);
Expand Down
10 changes: 8 additions & 2 deletions src/lib/GuaranteedJsonSession.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ beforeEach(() => {

test("can store an array of cell data in the session", () => {
const initFunction = () => [];
const session = new GuaranteedJsonSession<CellData[]>(initFunction);
const session = new GuaranteedJsonSession<CellData[]>({
keyName: "key",
initFunction,
});

session.sessionData = [
{ word: "Aardvark", stamped: false },
Expand All @@ -22,7 +25,10 @@ test("can store an array of cell data in the session", () => {

test("returns a new array if nothing is stored", () => {
const initFunction = () => [{ word: "Camel", stamped: false }];
const session = new GuaranteedJsonSession<CellData[]>(initFunction);
const session = new GuaranteedJsonSession<CellData[]>({
keyName: "key",
initFunction,
});

expect(session.sessionData).toEqual([{ word: "Camel", stamped: false }]);
});
10 changes: 8 additions & 2 deletions src/lib/GuaranteedJsonSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ class GuaranteedJsonSession<T> {
#session: JsonSession<T>;
#initFunction: () => T;

constructor(initFunction: () => T) {
this.#session = new JsonSession<T>();
constructor({
keyName,
initFunction,
}: {
keyName: string;
initFunction: () => T;
}) {
this.#session = new JsonSession<T>(keyName);
this.#initFunction = initFunction;
}

Expand Down
19 changes: 0 additions & 19 deletions src/lib/JsonDataImporter.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/lib/JsonSession.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ beforeEach(() => {
});

test("can store an array of cell data in the session", () => {
const session = new JsonSession<CellData[]>();
const session = new JsonSession<CellData[]>("key");

session.sessionData = [
{ word: "Aardvark", stamped: false },
Expand All @@ -20,7 +20,7 @@ test("can store an array of cell data in the session", () => {
});

test("returns null if nothing is stored", () => {
const session = new JsonSession();
const session = new JsonSession("Key");

expect(session.sessionData).toEqual(null);
});
6 changes: 5 additions & 1 deletion src/lib/JsonSession.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
class JsonSession<T> {
#store = window.localStorage;
#keyName = "sessionData";
#keyName: string;

constructor(keyName: string) {
this.#keyName = keyName;
}

get sessionData(): T | null {
const sessionDataString: string | null = this.#store.getItem(this.#keyName);
Expand Down
21 changes: 21 additions & 0 deletions src/lib/importJsonData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
type EmptyObject = Record<string, never>;
type Result<T> =
| { success: true; data: T }
| { success: false; data: EmptyObject };

const importJsonData = async <T>(listName: string): Promise<Result<T>> => {
try {
return {
success: true,
data: (await import(`../data/${listName}.json`)).default,
};
} catch (error) {
return {
success: false,
data: {},
};
console.error("Failed to load the data", error);
}
};

export default importJsonData;
25 changes: 17 additions & 8 deletions src/loaders/configLoaders.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import defaultConfig from "../data/teamLindy.json";
import nullConfig from "../data/teamLindy.json";
import JsonDataImporter from "../lib/JsonDataImporter";
import importJsonData from "../lib/importJsonData";
import type { Params } from "react-router-dom";
import type { Config } from "../data/config";

export const defaultConfigLoader = (): Config => {
return defaultConfig;
export type KeyedConfig = { id: string } & Config;

const defaultKeyedConfig: KeyedConfig = { id: "teamLindy", ...defaultConfig };

export const defaultConfigLoader = (): KeyedConfig => {
return defaultKeyedConfig;
};

export const configLoader = async ({
params,
}: {
params: Params<string>;
}): Promise<Config> => {
if (params.gameName) {
const importer = new JsonDataImporter({ defaultData: defaultConfig });
return await importer.import(params.gameName);
}): Promise<KeyedConfig> => {
const id = params.gameName;
if (id) {
const { success, data } = await importJsonData<Config>(id);
if (success) {
return { id, ...data };
} else {
return defaultKeyedConfig;
}
} else {
console.error(
"expected a param of 'gameName' but didn't find one or it had a falsey value",
);
return nullConfig;
return { id: "null", ...nullConfig };
}
};

0 comments on commit e6ee863

Please sign in to comment.