Replace dexie observable with useLiveQuery

This commit is contained in:
Mitchell McCaffrey 2021-06-25 17:45:14 +10:00
parent 45ce2685c6
commit 3b8565ad54
6 changed files with 86 additions and 227 deletions

View File

@ -21,8 +21,8 @@
"color": "^3.1.3", "color": "^3.1.3",
"comlink": "^4.3.0", "comlink": "^4.3.0",
"deep-diff": "^1.0.2", "deep-diff": "^1.0.2",
"dexie": "^3.0.3", "dexie": "3.1.0-beta.13",
"dexie-observable": "^3.0.0-beta.10", "dexie-react-hooks": "^1.0.6",
"err-code": "^3.0.1", "err-code": "^3.0.1",
"fake-indexeddb": "^3.1.2", "fake-indexeddb": "^3.1.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",

View File

@ -236,38 +236,7 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) {
updateAssetURL(); updateAssetURL();
// Update the url when the asset is added to the db after the hook is used
function handleAssetChanges(changes) {
for (let change of changes) {
const id = change.key;
if (
change.table === "assets" &&
id === assetId &&
(change.type === 1 || change.type === 2)
) {
const asset = change.obj;
setAssetURLs((prevURLs) => {
if (!(assetId in prevURLs)) {
const url = URL.createObjectURL(
new Blob([asset.file], { type: asset.mime })
);
return {
...prevURLs,
[assetId]: { url, id: assetId, references: 1 },
};
} else {
return prevURLs;
}
});
}
}
}
database.on("changes", handleAssetChanges);
return () => { return () => {
database.on("changes").unsubscribe(handleAssetChanges);
// Decrease references // Decrease references
setAssetURLs((prevURLs) => { setAssetURLs((prevURLs) => {
if (assetId in prevURLs) { if (assetId in prevURLs) {

View File

@ -1,9 +1,14 @@
import React, { useEffect, useState, useContext, useCallback } from "react"; import React, {
useEffect,
useState,
useContext,
useCallback,
useMemo,
} from "react";
import { useLiveQuery } from "dexie-react-hooks";
import { useUserId } from "./UserIdContext";
import { useDatabase } from "./DatabaseContext"; import { useDatabase } from "./DatabaseContext";
import { applyObservableChange } from "../helpers/dexie";
import { removeGroupsItems } from "../helpers/group"; import { removeGroupsItems } from "../helpers/group";
const MapDataContext = React.createContext(); const MapDataContext = React.createContext();
@ -18,33 +23,39 @@ const defaultMapState = {
}; };
export function MapDataProvider({ children }) { export function MapDataProvider({ children }) {
const { database, databaseStatus } = useDatabase(); const { database } = useDatabase();
const userId = useUserId();
const mapsQuery = useLiveQuery(
() => database?.table("maps").toArray(),
[database]
);
const mapStatesQuery = useLiveQuery(
() => database?.table("states").toArray(),
[database]
);
const maps = useMemo(() => mapsQuery || [], [mapsQuery]);
const mapStates = useMemo(() => mapStatesQuery || [], [mapStatesQuery]);
const mapsLoading = useMemo(
() => !mapsQuery || !mapStatesQuery,
[mapsQuery, mapStatesQuery]
);
const mapGroupQuery = useLiveQuery(
() => database?.table("groups").get("maps"),
[database]
);
const [maps, setMaps] = useState([]);
const [mapStates, setMapStates] = useState([]);
const [mapsLoading, setMapsLoading] = useState(true);
const [mapGroups, setMapGroups] = useState([]); const [mapGroups, setMapGroups] = useState([]);
// Load maps from the database and ensure state is properly setup
useEffect(() => { useEffect(() => {
if (!userId || !database || databaseStatus === "loading") { async function updateMapGroups() {
return;
}
async function loadMaps() {
const storedMaps = await database.table("maps").toArray();
setMaps(storedMaps);
const storedStates = await database.table("states").toArray();
setMapStates(storedStates);
const group = await database.table("groups").get("maps"); const group = await database.table("groups").get("maps");
const storedGroups = group.items; setMapGroups(group.items);
setMapGroups(storedGroups);
setMapsLoading(false);
} }
if (database && mapGroupQuery) {
loadMaps(); updateMapGroups();
}, [userId, database, databaseStatus]); }
}, [mapGroupQuery, database]);
const getMap = useCallback( const getMap = useCallback(
async (mapId) => { async (mapId) => {
@ -138,77 +149,6 @@ export function MapDataProvider({ children }) {
[database] [database]
); );
// Create DB observable to sync creating and deleting
useEffect(() => {
if (!database || databaseStatus === "loading") {
return;
}
function handleMapChanges(changes) {
for (let change of changes) {
if (change.table === "maps") {
if (change.type === 1) {
// Created
const map = change.obj;
const state = { ...defaultMapState, mapId: map.id };
setMaps((prevMaps) => [map, ...prevMaps]);
setMapStates((prevStates) => [state, ...prevStates]);
} else if (change.type === 2) {
const map = change.obj;
setMaps((prevMaps) => {
const newMaps = [...prevMaps];
const i = newMaps.findIndex((m) => m.id === map.id);
if (i > -1) {
newMaps[i] = map;
}
return newMaps;
});
} else if (change.type === 3) {
// Deleted
const id = change.key;
setMaps((prevMaps) => {
const filtered = prevMaps.filter((map) => map.id !== id);
return filtered;
});
setMapStates((prevMapsStates) => {
const filtered = prevMapsStates.filter(
(state) => state.mapId !== id
);
return filtered;
});
}
}
if (change.table === "states") {
if (change.type === 2) {
// Update map state
const state = change.obj;
setMapStates((prevMapStates) => {
const newStates = [...prevMapStates];
const i = newStates.findIndex((s) => s.mapId === state.mapId);
if (i > -1) {
newStates[i] = state;
}
return newStates;
});
}
}
if (change.table === "groups") {
if (change.type === 2 && change.key === "maps") {
const group = applyObservableChange(change);
const groups = group.items.filter((item) => item !== null);
setMapGroups(groups);
}
}
}
}
database.on("changes", handleMapChanges);
return () => {
database.on("changes").unsubscribe(handleMapChanges);
};
}, [database, databaseStatus]);
const [mapsById, setMapsById] = useState({}); const [mapsById, setMapsById] = useState({});
useEffect(() => { useEffect(() => {
setMapsById( setMapsById(

View File

@ -1,37 +1,44 @@
import React, { useEffect, useState, useContext, useCallback } from "react"; import React, {
useEffect,
useState,
useContext,
useCallback,
useMemo,
} from "react";
import { useLiveQuery } from "dexie-react-hooks";
import { useUserId } from "./UserIdContext";
import { useDatabase } from "./DatabaseContext"; import { useDatabase } from "./DatabaseContext";
import { applyObservableChange } from "../helpers/dexie";
import { removeGroupsItems } from "../helpers/group"; import { removeGroupsItems } from "../helpers/group";
const TokenDataContext = React.createContext(); const TokenDataContext = React.createContext();
export function TokenDataProvider({ children }) { export function TokenDataProvider({ children }) {
const { database, databaseStatus } = useDatabase(); const { database } = useDatabase();
const userId = useUserId();
const tokensQuery = useLiveQuery(
() => database?.table("tokens").toArray(),
[database]
);
const tokens = useMemo(() => tokensQuery || [], [tokensQuery]);
const tokensLoading = useMemo(() => !tokensQuery, [tokensQuery]);
const tokenGroupQuery = useLiveQuery(
() => database?.table("groups").get("tokens"),
[database]
);
const [tokens, setTokens] = useState([]);
const [tokensLoading, setTokensLoading] = useState(true);
const [tokenGroups, setTokenGroups] = useState([]); const [tokenGroups, setTokenGroups] = useState([]);
useEffect(() => { useEffect(() => {
if (!userId || !database || databaseStatus === "loading") { async function updateTokenGroups() {
return;
}
async function loadTokens() {
const storedTokens = await database.table("tokens").toArray();
setTokens(storedTokens);
const group = await database.table("groups").get("tokens"); const group = await database.table("groups").get("tokens");
const storedGroups = group.items; setTokenGroups(group.items);
setTokenGroups(storedGroups);
setTokensLoading(false);
} }
if (database && tokenGroupQuery) {
loadTokens(); updateTokenGroups();
}, [userId, database, databaseStatus]); }
}, [tokenGroupQuery, database]);
const getToken = useCallback( const getToken = useCallback(
async (tokenId) => { async (tokenId) => {
@ -83,15 +90,15 @@ export function TokenDataProvider({ children }) {
const updateTokensHidden = useCallback( const updateTokensHidden = useCallback(
async (ids, hideInSidebar) => { async (ids, hideInSidebar) => {
// Update immediately to avoid UI delay // // Update immediately to avoid UI delay
setTokens((prevTokens) => { // setTokens((prevTokens) => {
let newTokens = [...prevTokens]; // let newTokens = [...prevTokens];
for (let id of ids) { // for (let id of ids) {
const tokenIndex = newTokens.findIndex((token) => token.id === id); // const tokenIndex = newTokens.findIndex((token) => token.id === id);
newTokens[tokenIndex].hideInSidebar = hideInSidebar; // newTokens[tokenIndex].hideInSidebar = hideInSidebar;
} // }
return newTokens; // return newTokens;
}); // });
await Promise.all( await Promise.all(
ids.map((id) => database.table("tokens").update(id, { hideInSidebar })) ids.map((id) => database.table("tokens").update(id, { hideInSidebar }))
); );
@ -108,67 +115,6 @@ export function TokenDataProvider({ children }) {
[database] [database]
); );
// Create DB observable to sync creating and deleting
useEffect(() => {
if (!database || databaseStatus === "loading") {
return;
}
function handleTokenChanges(changes) {
// Pool token changes together to call a single state update at the end
let tokensCreated = [];
let tokensUpdated = {};
let tokensDeleted = [];
for (let change of changes) {
if (change.table === "tokens") {
if (change.type === 1) {
// Created
const token = change.obj;
tokensCreated.push(token);
} else if (change.type === 2) {
// Updated
const token = change.obj;
tokensUpdated[token.id] = token;
} else if (change.type === 3) {
// Deleted
const id = change.key;
tokensDeleted.push(id);
}
}
if (change.table === "groups") {
if (change.type === 2 && change.key === "tokens") {
const group = applyObservableChange(change);
const groups = group.items.filter((item) => item !== null);
setTokenGroups(groups);
}
}
}
const tokensUpdatedArray = Object.values(tokensUpdated);
if (
tokensCreated.length > 0 ||
tokensUpdatedArray.length > 0 ||
tokensDeleted.length > 0
) {
setTokens((prevTokens) => {
let newTokens = [...tokensCreated, ...prevTokens];
for (let token of tokensUpdatedArray) {
const tokenIndex = newTokens.findIndex((t) => t.id === token.id);
if (tokenIndex > -1) {
newTokens[tokenIndex] = token;
}
}
return newTokens.filter((token) => !tokensDeleted.includes(token.id));
});
}
}
database.on("changes", handleTokenChanges);
return () => {
database.on("changes").unsubscribe(handleTokenChanges);
};
}, [database, databaseStatus]);
const [tokensById, setTokensById] = useState({}); const [tokensById, setTokensById] = useState({});
useEffect(() => { useEffect(() => {
setTokensById( setTokensById(

View File

@ -1,7 +1,6 @@
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import Dexie, { DexieOptions } from "dexie"; import Dexie, { DexieOptions } from "dexie";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import "dexie-observable";
import { loadVersions } from "./upgrade"; import { loadVersions } from "./upgrade";
import { getDefaultMaps } from "./maps"; import { getDefaultMaps } from "./maps";

View File

@ -5369,12 +5369,17 @@ detect-port-alt@1.1.6:
address "^1.0.1" address "^1.0.1"
debug "^2.6.0" debug "^2.6.0"
dexie-observable@^3.0.0-beta.10: dexie-react-hooks@^1.0.6:
version "3.0.0-beta.10" version "1.0.6"
resolved "https://registry.yarnpkg.com/dexie-observable/-/dexie-observable-3.0.0-beta.10.tgz#ad7a7e136defbb62f9eab9198a5cb9e10bce1c87" resolved "https://registry.yarnpkg.com/dexie-react-hooks/-/dexie-react-hooks-1.0.6.tgz#a21d116addb2bb785507bf924cc6e963d858d5d5"
integrity sha512-GMPwQMLh1nYqM1MYsOZudsIwSMqDMrAOBxNuw+Y2ijsrQTBPi3nRF2CinY02IdlmffkaU7DsDfnlgdaMEaiHTQ== integrity sha512-OFoOBC4BQzkVGicuWl/cIMtlPp0wTAnUXwUJzq+l/zp0XVGmwEWkemRFq7JbudJLT0DINFVVzgVhGV7KOUK7uA==
"dexie@^3.0.0-alpha.5 || ^2.0.4", dexie@^3.0.3: dexie@3.1.0-beta.13:
version "3.1.0-beta.13"
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.1.0-beta.13.tgz#54b3438e2aca3b60f87a823a535ce1b4313056ec"
integrity sha512-pUcX9YyX1VDjF1oMqiOys6N2zoXIA/CeTghB3P4Ee77U8n9q0qa2pmNYoHyyYPKLU58+gzsMJuOc6HLPJDQrQQ==
"dexie@^3.0.0-alpha.5 || ^2.0.4":
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.0.3.tgz#ede63849dfe5f07e13e99bb72a040e8ac1d29dab" resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.0.3.tgz#ede63849dfe5f07e13e99bb72a040e8ac1d29dab"
integrity sha512-BSFhGpngnCl1DOr+8YNwBDobRMH0ziJs2vts69VilwetHYOtEDcLqo7d/XiIphM0tJZ2rPPyAGd31lgH2Ln3nw== integrity sha512-BSFhGpngnCl1DOr+8YNwBDobRMH0ziJs2vts69VilwetHYOtEDcLqo7d/XiIphM0tJZ2rPPyAGd31lgH2Ln3nw==
@ -7202,7 +7207,7 @@ image-outline@^0.1.0:
marching-squares "^0.2.0" marching-squares "^0.2.0"
minimist "^1.2.0" minimist "^1.2.0"
ndarray "^1.0.18" ndarray "^1.0.18"
immediate@~3.0.5: immediate@~3.0.5:
version "3.0.6" version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"