Skip to content

Commit

Permalink
Add a staleTime to queries (#1167)
Browse files Browse the repository at this point in the history
  • Loading branch information
Janpot authored Oct 20, 2022
1 parent 77d6144 commit 315f440
Show file tree
Hide file tree
Showing 14 changed files with 92 additions and 76 deletions.
1 change: 1 addition & 0 deletions packages/toolpad-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@sentry/nextjs": "^7.15.0",
"@swc/wasm": "^1.3.6",
"@tanstack/react-query": "^4.10.3",
"@tanstack/react-query-devtools": "^4.12.0",
"@types/cors": "^2.8.12",
"abort-controller": "^3.0.0",
"arg": "^5.0.2",
Expand Down
2 changes: 2 additions & 0 deletions packages/toolpad-app/src/appDom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ export interface QueryNode<Q = any> extends AppDomNodeBase {
readonly query: ConstantAttrValue<Q>;
readonly transform?: ConstantAttrValue<string>;
readonly transformEnabled?: ConstantAttrValue<boolean>;
/** @deprecated Not necessary to be user-facing, we will expose staleTime instead if necessary */
readonly refetchOnWindowFocus?: ConstantAttrValue<boolean>;
/** @deprecated Not necessary to be user-facing, we will expose staleTime instead if necessary */
readonly refetchOnReconnect?: ConstantAttrValue<boolean>;
readonly refetchInterval?: ConstantAttrValue<number>;
readonly cacheTime?: ConstantAttrValue<number>;
Expand Down
6 changes: 3 additions & 3 deletions packages/toolpad-app/src/canvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import * as React from 'react';
import { fireEvent, setEventHandler } from '@mui/toolpad-core/runtime';
import invariant from 'invariant';
import { throttle } from 'lodash-es';
import { NodeId, RuntimeEvent } from '@mui/toolpad-core';
import { RuntimeEvent } from '@mui/toolpad-core';
import ToolpadApp from '../runtime';
import * as appDom from '../appDom';
import { PageViewState } from '../types';
import { NodeHashes, PageViewState } from '../types';
import getPageViewState from './getPageViewState';
import { rectContainsPoint } from '../utils/geometry';
import { CanvasHooks, CanvasHooksContext } from '../runtime/CanvasHooksContext';

export interface AppCanvasState {
appId: string;
dom: appDom.RenderTree;
savedNodes: Record<NodeId, boolean>;
savedNodes: NodeHashes;
}

export interface ToolpadBridge {
Expand Down
3 changes: 2 additions & 1 deletion packages/toolpad-app/src/runtime/CanvasHooksContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NodeId } from '@mui/toolpad-core';
import * as React from 'react';
import { NodeHashes } from '../types';

export interface NavigateToPage {
(pageNodeId: NodeId): void;
Expand All @@ -9,7 +10,7 @@ export interface NavigateToPage {
* Context created by the app canvas to override behavior for the app editor
*/
export interface CanvasHooks {
savedNodes?: Record<NodeId, boolean>;
savedNodes?: NodeHashes;
navigateToPage?: NavigateToPage;
}

Expand Down
23 changes: 19 additions & 4 deletions packages/toolpad-app/src/runtime/ToolpadApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ import NoSsr from '../components/NoSsr';
import { execDataSourceQuery, useDataQuery, UseDataQueryConfig, UseFetch } from './useDataQuery';
import { useAppContext, AppContextProvider } from './AppContext';
import { CanvasHooksContext, NavigateToPage } from './CanvasHooksContext';
import useBoolean from '../utils/useBoolean';

const ReactQueryDevtoolsProduction = React.lazy(() =>
// eslint-disable-next-line import/extensions
import('@tanstack/react-query-devtools/build/lib/index.prod.js').then((d) => ({
default: d.ReactQueryDevtools,
})),
);

const EMPTY_ARRAY: any[] = [];
const EMPTY_OBJECT: any = {};
Expand All @@ -82,8 +90,6 @@ const INITIAL_FETCH: UseFetch = {
const USE_DATA_QUERY_CONFIG_KEYS: readonly (keyof UseDataQueryConfig)[] = [
'enabled',
'refetchInterval',
'refetchOnReconnect',
'refetchOnWindowFocus',
];

function usePageNavigator(): NavigateToPage {
Expand Down Expand Up @@ -420,12 +426,11 @@ function QueryNode({ node }: QueryNodeProps) {
const bindings = useBindingsContext();
const setControlledBinding = useSetControlledBindingContext();

const queryId = node.id;
const params = resolveBindables(bindings, `${node.id}.params`, node.params);

const configBindings = _.pick(node.attributes, USE_DATA_QUERY_CONFIG_KEYS);
const options = resolveBindables(bindings, `${node.id}.config`, configBindings);
const queryResult = useDataQuery(queryId, params, options);
const queryResult = useDataQuery(node.id, params, options);

React.useEffect(() => {
const { isLoading, error, data, rows, ...result } = queryResult;
Expand Down Expand Up @@ -872,6 +877,7 @@ const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
staleTime: 60 * 1000,
},
},
});
Expand Down Expand Up @@ -899,6 +905,12 @@ export default function ToolpadApp({

React.useEffect(() => setResetNodeErrorsKey((key) => key + 1), [dom]);

const { value: showDevtools, toggle: toggleDevtools } = useBoolean(false);

React.useEffect(() => {
(window as any).toggleDevtools = () => toggleDevtools();
}, [toggleDevtools]);

return (
<AppRoot ref={rootRef}>
<NoSsr>
Expand All @@ -918,6 +930,9 @@ export default function ToolpadApp({
<BrowserRouter basename={basename}>
<RenderedPages dom={dom} />
</BrowserRouter>
{showDevtools ? (
<ReactQueryDevtoolsProduction initialIsOpen={false} />
) : null}
</QueryClientProvider>
</AppContextProvider>
</ComponentsContext>
Expand Down
13 changes: 6 additions & 7 deletions packages/toolpad-app/src/runtime/useDataQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export async function execDataSourceQuery({

export type UseDataQueryConfig = Pick<
UseQueryOptions<any, unknown, unknown, any[]>,
'enabled' | 'refetchOnWindowFocus' | 'refetchOnReconnect' | 'refetchInterval'
'enabled' | 'refetchInterval'
>;

export interface UseFetch {
Expand All @@ -64,15 +64,14 @@ export function useDataQuery(
{
enabled = true,
...options
}: Pick<
UseQueryOptions<any, unknown, unknown, any[]>,
'enabled' | 'refetchOnWindowFocus' | 'refetchOnReconnect' | 'refetchInterval'
>,
}: Pick<UseQueryOptions<any, unknown, unknown, any[]>, 'enabled' | 'refetchInterval'>,
): UseFetch {
const { appId, version } = useAppContext();
const { savedNodes } = React.useContext(CanvasHooksContext);

const isNodeAvailableOnServer: boolean = savedNodes ? queryId && savedNodes[queryId] : true;
// These are only used by the editor to invalidate caches whenever the query changes during editing
const nodeHash: number | undefined = savedNodes ? savedNodes[queryId] : undefined;
const isNodeAvailableOnServer: boolean = savedNodes ? !!savedNodes[queryId] : true;

const {
isLoading,
Expand All @@ -81,7 +80,7 @@ export function useDataQuery(
data: responseData = EMPTY_OBJECT,
refetch,
} = useQuery(
[appId, version, queryId, params],
[appId, version, nodeHash, queryId, params],
({ signal }) =>
queryId &&
execDataSourceQuery({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ReactDOM from 'react-dom';
import invariant from 'invariant';
import * as appDom from '../../../appDom';
import { HTML_ID_EDITOR_OVERLAY } from '../../../constants';
import { PageViewState } from '../../../types';
import { NodeHashes, PageViewState } from '../../../types';
import { ToolpadBridge } from '../../../canvas';
import useEvent from '../../../utils/useEvent';
import { LogEntry } from '../../../components/Console';
Expand Down Expand Up @@ -46,7 +46,7 @@ export interface EditorCanvasHostProps {
appId: string;
pageNodeId: NodeId;
dom: appDom.AppDom;
savedNodes: Record<NodeId, boolean>;
savedNodes: NodeHashes;
onRuntimeEvent?: (event: RuntimeEvent) => void;
onConsoleEntry?: (entry: LogEntry) => void;
overlay?: React.ReactNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import {
List,
ListItem,
DialogActions,
Checkbox,
FormControlLabel,
TextField,
InputAdornment,
Divider,
Expand Down Expand Up @@ -261,19 +259,6 @@ function QueryNodeEditorDialog<Q>({
);
}, []);

const handleRefetchOnWindowFocusChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setInput((existing) =>
update(existing, {
attributes: update(existing.attributes, {
refetchOnWindowFocus: appDom.createConst(event.target.checked),
}),
}),
);
},
[],
);

const handleEnabledChange = React.useCallback((newValue: BindableAttrValue<boolean> | null) => {
setInput((existing) =>
update(existing, {
Expand All @@ -284,19 +269,6 @@ function QueryNodeEditorDialog<Q>({
);
}, []);

const handleRefetchOnReconnectChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setInput((existing) =>
update(existing, {
attributes: update(existing.attributes, {
refetchOnReconnect: appDom.createConst(event.target.checked),
}),
}),
);
},
[],
);

const handleRefetchIntervalChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const interval = Number(event.target.value);
Expand Down Expand Up @@ -419,26 +391,6 @@ function QueryNodeEditorDialog<Q>({
onChange={handleEnabledChange}
disabled={mode !== 'query'}
/>
<FormControlLabel
control={
<Checkbox
checked={input.attributes.refetchOnWindowFocus?.value ?? true}
onChange={handleRefetchOnWindowFocusChange}
disabled={mode !== 'query'}
/>
}
label="Refetch on window focus"
/>
<FormControlLabel
control={
<Checkbox
checked={input.attributes.refetchOnReconnect?.value ?? true}
onChange={handleRefetchOnReconnectChange}
disabled={mode !== 'query'}
/>
}
label="Refetch on network reconnect"
/>
<TextField
InputProps={{
startAdornment: <InputAdornment position="start">s</InputAdornment>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { useNavigate } from 'react-router-dom';
import invariant from 'invariant';
import * as appDom from '../../../../appDom';
import EditorCanvasHost, { EditorCanvasHostHandle } from '../EditorCanvasHost';
import { getSavedNodes, useDom, useDomApi, useDomLoader } from '../../../DomLoader';
import { getNodeHashes, useDom, useDomApi, useDomLoader } from '../../../DomLoader';
import { usePageEditorApi, usePageEditorState } from '../PageEditorProvider';
import RenderOverlay from './RenderOverlay';
import { NodeHashes } from '../../../../types';

const classes = {
view: 'Toolpad_View',
Expand Down Expand Up @@ -37,9 +38,9 @@ export default function RenderPanel({ className }: RenderPanelProps) {

const navigate = useNavigate();

const savedNodes = React.useMemo(
() => getSavedNodes(domLoader.dom, domLoader.savedDom),
[domLoader.dom, domLoader.savedDom],
const savedNodes: NodeHashes = React.useMemo(
() => getNodeHashes(domLoader.savedDom),
[domLoader.savedDom],
);

const handleRuntimeEvent = React.useCallback(
Expand Down
9 changes: 4 additions & 5 deletions packages/toolpad-app/src/toolpad/DomLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import useShortcut from '../utils/useShortcut';
import useDebouncedHandler from '../utils/useDebouncedHandler';
import { createProvidedContext } from '../utils/react';
import { mapValues } from '../utils/collections';
import insecureHash from '../utils/insecureHash';
import { NodeHashes } from '../types';

export type DomAction =
| {
Expand Down Expand Up @@ -233,11 +235,8 @@ export interface DomLoader {
saveError: string | null;
}

export function getSavedNodes(
dom: appDom.AppDom,
savedDom: appDom.AppDom,
): Record<NodeId, boolean> {
return mapValues(dom.nodes, (node) => node === savedDom.nodes[node.id]);
export function getNodeHashes(dom: appDom.AppDom): NodeHashes {
return mapValues(dom.nodes, (node) => insecureHash(JSON.stringify(node)));
}

const [useDomLoader, DomLoaderProvider] = createProvidedContext<DomLoader>('DomLoader');
Expand Down
2 changes: 2 additions & 0 deletions packages/toolpad-app/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,5 @@ export interface AppTheme {
export type VersionOrPreview = 'preview' | number;

export type AppTemplateId = 'blank' | 'stats' | 'images';

export type NodeHashes = Record<NodeId, number | undefined>;
23 changes: 23 additions & 0 deletions packages/toolpad-app/src/utils/insecureHash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Returns a hash code for a string.
* (Compatible to Java's String.hashCode())
*
* The hash code for a string object is computed as
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* using number arithmetic, where s[i] is the i th character
* of the given string, n is the length of the string,
* and ^ indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* Do not use in a security critical context.
*
* @see https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0
*/
export default function insecureHash(str: string) {
let h = 0;
for (let i = 0; i < str.length; i += 1) {
// eslint-disable-next-line no-bitwise
h = (Math.imul(31, h) + str.charCodeAt(i)) | 0;
}
return h;
}
2 changes: 1 addition & 1 deletion packages/toolpad-app/src/utils/useBoolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';

export default function useBoolean(initialValue: boolean) {
const [value, setValue] = React.useState(initialValue);
const toggle = React.useCallback(() => setValue((existing) => !!existing), []);
const toggle = React.useCallback(() => setValue((existing) => !existing), []);
const setTrue = React.useCallback(() => setValue(true), []);
const setFalse = React.useCallback(() => setValue(false), []);
return { value, setValue, toggle, setTrue, setFalse };
Expand Down
23 changes: 22 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2596,11 +2596,27 @@
resolved "https://registry.yarnpkg.com/@swc/wasm/-/wasm-1.3.6.tgz#01bae6087bb2458b23c29a5a8ad914c83ec11618"
integrity sha512-rFygmNDMl25/t2ETAtFjpcw6acQOm/o4sW/GN0fVPFUdNpI2zr2/oCXpyRM71OUPbXvksy9jXrt7yMZGD65+wQ==

"@tanstack/match-sorter-utils@^8.1.1":
version "8.5.14"
resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.5.14.tgz#12efcd536abe491d09521e0242bc4d51442f8a8a"
integrity sha512-lVNhzTcOJ2bZ4IU+PeCPQ36vowBHvviJb2ZfdRFX5uhy7G0jM8N34zAMbmS5ZmVH8D2B7oU82OWo0e/5ZFzQrw==
dependencies:
remove-accents "0.4.2"

"@tanstack/query-core@4.10.3":
version "4.10.3"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.10.3.tgz#a6477bab9ed1ae4561ca0a59ae06f8c615c692b7"
integrity sha512-+ME02sUmBfx3Pjd+0XtEthK8/4rVMD2jcxvnW9DSgFONpKtpjzfRzjY4ykzpDw1QEo2eoPvl7NS8J5mNI199aA==

"@tanstack/react-query-devtools@^4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-4.12.0.tgz#18f341185f0a5580fc8b2daf8197389108be7131"
integrity sha512-gUV+aKpwgP7Cfp2+vwgu6krXCytncGWjTqFcnzC1l2INOf8dRi8/GEupYF7npxD9ky72FTuxc5+TI/lC8eB0Eg==
dependencies:
"@tanstack/match-sorter-utils" "^8.1.1"
superjson "^1.10.0"
use-sync-external-store "^1.2.0"

"@tanstack/react-query@^4.10.3":
version "4.10.3"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.10.3.tgz#294deefa0fb6ada88bc4631d346ef5d5551c5443"
Expand Down Expand Up @@ -10502,6 +10518,11 @@ regexpp@^3.2.0:
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==

remove-accents@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5"
integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==

require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
Expand Down Expand Up @@ -11247,7 +11268,7 @@ sucrase@^3.20.0, sucrase@^3.28.0:
pirates "^4.0.1"
ts-interface-checker "^0.1.9"

superjson@^1.10.1:
superjson@^1.10.0, superjson@^1.10.1:
version "1.10.1"
resolved "https://registry.yarnpkg.com/superjson/-/superjson-1.10.1.tgz#9c73e9393489dddab89d638694eadcbf4bda2f36"
integrity sha512-7fvPVDHmkTKg6641B9c6vr6Zz5CwPtF9j0XFExeLxJxrMaeLU2sqebY3/yrI3l0K5zJ+H9QA3H+lIYj5ooCOkg==
Expand Down

0 comments on commit 315f440

Please sign in to comment.