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

Add a staleTime to queries #1167

Merged
merged 14 commits into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from 13 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
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