Skip to content

Commit

Permalink
feat: display errors in SWRCache (#41)
Browse files Browse the repository at this point in the history
* feat: display errors in SWRCache

* fix: font-size for data is too large

* feat: display error values

* chore(example): return an error by the query value

* fix: history panel with errors

* fix: lint error

* chore: remove tsbuildinfo from git
  • Loading branch information
koba04 authored Dec 29, 2021
1 parent 8aee40f commit 7f7f672
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 36 deletions.
15 changes: 10 additions & 5 deletions examples/swr-devtools-demo/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ const DevToolsArea = () => {
);
};

const fetcher = async (url) => {
const res = await fetch(url);
const json = await res.json();
if (res.ok) {
return json;
}
throw new Error(json.message);
};

function MyApp({ Component, pageProps }) {
return (
<SWRConfig
value={{
fetcher: (url) => fetch(url).then((r) => r.json()),
}}
>
<SWRConfig value={{ fetcher }}>
<SWRDevTools>
<Component {...pageProps} />
<DevToolsArea />
Expand Down
6 changes: 5 additions & 1 deletion examples/swr-devtools-demo/pages/api/hello.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@ let counter = 1;

export default (req, res) => {
++counter;
res.status(200).json({ name: `Hello World ${counter}` });
if (req.query.error) {
res.status(500).json({ message: "this is an error message" });
} else {
res.status(200).json({ name: `Hello World ${counter}` });
}
};
9 changes: 7 additions & 2 deletions examples/swr-devtools-demo/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import useSWR from "swr";
import { useEffect } from "react";

export default function Home() {
const { data, mutate } = useSWR("/api/hello");
// const { data, mutate } = useSWR("/api/hello?error=true");
const { data, mutate, error } = useSWR(
`/api/hello${typeof window !== "undefined" ? location.search : ""}`
);
const { data: data2 } = useSWR("/api/hello?foo");

useEffect(() => {
Expand Down Expand Up @@ -36,7 +39,9 @@ export default function Home() {
<section>
<p className={styles.row}>
<span className={styles.cacheKey}>/api/hello</span>
<span>{data ? data.name : "...loading"}</span>
{!data && !error && <span>...loading</span>}
{data && <span>{data.name}</span>}
{error && <span>Error: {error.message}</span>}
<span className={styles.note}>(auto increment in 5 seconds)</span>
</p>
<p className={styles.row}>
Expand Down
1 change: 0 additions & 1 deletion examples/swr-devtools-demo/tsconfig.tsbuildinfo

This file was deleted.

59 changes: 44 additions & 15 deletions packages/swr-devtools-panel/src/components/CacheData.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,42 @@
import React, { lazy, Suspense } from "react";
import { ReactJsonViewProps } from "react-json-view";
import styled from "styled-components";
import { SWRCacheData } from "swr-devtools/lib/swr-cache";
import { CacheKey } from "./CacheKey";
import { ErrorLabel } from "./ErrorLabel";

type Props = {
data: SWRCacheData;
};

export const CacheData = React.memo(({ data }: Props) => (
<>
<Wrapper>
<Title>
{data.error && <ErrorLabel />}
<CacheKey cacheKey={data.key} />
&nbsp;
<TimestampText>{data.timestampString}</TimestampText>
</Title>
<DataWrapper>
<CacheDataView data={data.data} />
{data.error && <ErrorText>{data.error}</ErrorText>}
{data.data && <CacheDataView data={data.data} />}
{data.error && <ErrorData error={data.error} />}
</DataWrapper>
</>
</Wrapper>
));
CacheData.displayName = "CacheData";

const DataWrapper = styled.div`
border-bottom: solid 1px var(--swr-devtools-border-color);
font-size: 1rem;
height: 100%;
margin: 0;
padding: 0 0.3rem;
`;
const ErrorData = ({ error }: { error: string | ReactJsonViewProps }) => (
<ErrorWrapper>
<ErrorTitle>ERROR</ErrorTitle>
{typeof error === "string" ? (
<ErrorText>{error}</ErrorText>
) : (
<CacheDataView data={error} />
)}
</ErrorWrapper>
);

const CacheDataView = ({ data }: Props) => {
const CacheDataView = ({ data }: { data: ReactJsonViewProps }) => {
if (typeof window === "undefined") return null;
return (
<Suspense fallback="loading">
Expand All @@ -39,7 +45,7 @@ const CacheDataView = ({ data }: Props) => {
);
};

const AsyncReactJson = ({ data }: Props) => {
const AsyncReactJson = ({ data }: { data: ReactJsonViewProps }) => {
const ReactJson = lazy(() => import("react-json-view"));
return (
<ReactJson
Expand All @@ -53,13 +59,36 @@ const AsyncReactJson = ({ data }: Props) => {
);
};

const Wrapper = styled.div`
padding: 0.2rem;
`;

const DataWrapper = styled.div`
font-size: 0.8rem;
height: 100%;
margin: 0;
padding: 0 0.3rem;
`;

const ErrorWrapper = styled.div`
margin-top: 1rem;
padding: 0.5rem 0;
`;

const ErrorText = styled.p`
color: var(--swr-devtools-error-text-colora);
margin: 0;
color: var(--swr-devtools-text-color);
`;

const ErrorTitle = styled.h4`
margin: 0;
padding: 0.3rem 0;
color: var(--swr-devtools-text-color);
`;

const Title = styled.h3`
margin: 0;
padding: 1rem 0.5rem;
padding: 0.5rem 0rem;
color: var(--swr-devtools-text-color);
`;

Expand Down
11 changes: 8 additions & 3 deletions packages/swr-devtools-panel/src/components/CacheKey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@ import {
export const CacheKey = ({ cacheKey }: { cacheKey: string }) => {
if (isInfiniteCache(cacheKey)) {
return (
<span>
<CacheText>
<CacheTag>Infinite</CacheTag>
{getInfiniteCacheKey(cacheKey)}
</span>
</CacheText>
);
}
return <span>{cacheKey}</span>;
return <CacheText>{cacheKey}</CacheText>;
};

const CacheText = styled.span`
display: inline-block;
padding: 0.3rem;
`;

const CacheTag = styled.b`
margin-right: 0.3rem;
padding: 0.2rem;
Expand Down
10 changes: 10 additions & 0 deletions packages/swr-devtools-panel/src/components/ErrorLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from "react";
import styled from "styled-components";

export const ErrorLabel = () => <ErrorLabelWrapper>ERROR</ErrorLabelWrapper>;

const ErrorLabelWrapper = styled.span`
padding: 0.2rem;
background-color: var(--swr-devtools-error-text-color);
color: var(--swr-devtools-text-color);
`;
10 changes: 7 additions & 3 deletions packages/swr-devtools-panel/src/components/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CacheData } from "./CacheData";
import { CacheKey } from "./CacheKey";
import { useDevToolsCache } from "../devtools-cache";
import { SearchInput } from "./SearchInput";
import { ErrorLabel } from "./ErrorLabel";

export const Panel = ({
cache,
Expand Down Expand Up @@ -40,7 +41,7 @@ export const Panel = ({
<CacheItems>
{cacheData
.filter(({ key }) => filterText === "" || key.includes(filterText))
.map(({ key, timestampString, timestamp }) => (
.map(({ key, error, timestampString, timestamp }) => (
<CacheItem
key={`${type}--${key}--${
type === "history" ? timestamp.getTime() : ""
Expand All @@ -54,6 +55,7 @@ export const Panel = ({
<CacheItemButton
onClick={() => onSelectItem({ key, timestamp })}
>
{error && <ErrorLabel />}
<CacheKey cacheKey={key} /> ({timestampString})
</CacheItemButton>
</CacheItem>
Expand Down Expand Up @@ -99,10 +101,12 @@ const CacheItem = styled.li<{ isSelected: boolean }>`
`;

const CacheItemButton = styled.button`
display: inline-block;
display: flex;
align-items: center;
gap: 2px;
width: 100%;
height: 100%;
padding: 0.5rem 0;
padding: 0.2rem 0;
border: none;
background: transparent;
color: var(--swr-devtools-text-color);
Expand Down
18 changes: 15 additions & 3 deletions packages/swr-devtools-panel/src/devtools-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Cache } from "swr";
import {
injectSWRCache,
isMetaCache,
isErrorCache,
getErrorCacheKey,
SWRCacheData,
} from "swr-devtools/lib/swr-cache";

Expand Down Expand Up @@ -62,15 +64,25 @@ const sortCacheDataFromLatest = (cacheData: Map<string, any>) => {
.reverse();
};

// TODO: this is a draft implementation
const toJSON = (value: any) => {
return value instanceof Error ? { message: value.message } : value;
};

const retrieveCache = (
key: string,
key_: string,
value: any
): [SWRCacheData[], SWRCacheData[]] => {
const date = new Date();

const data = toJSON(value);

const isErrorCache_ = isErrorCache(key_);
const key = isErrorCache_ ? getErrorCacheKey(key_) : key_;

const payload = isErrorCache_ ? { key, error: data } : { key, data };
currentCacheData.set(key, {
key,
data: value,
...payload,
timestamp: date,
timestampString: formatTime(date),
});
Expand Down
9 changes: 7 additions & 2 deletions packages/swr-devtools/src/SWRDevTools.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useLayoutEffect } from "react";
import { useSWRConfig, SWRConfig, Middleware, Cache } from "swr";

import { injectSWRCache, isMetaCache } from "./swr-cache";
import { injectSWRCache, isMetaCache, isErrorCache } from "./swr-cache";

const injected = new WeakSet();

Expand All @@ -17,6 +17,11 @@ export type DevToolsMessage =
type: "initialized";
};

// TOOD: we have to support more types
const convertToSerializableObject = (value: any) => {
return value instanceof Error ? { message: value.message } : value;
};

const inject = (cache: Cache) =>
injectSWRCache(cache, (key: string, value: any) => {
if (isMetaCache(key)) {
Expand All @@ -27,7 +32,7 @@ const inject = (cache: Cache) =>
type: "updated_swr_cache",
payload: {
key,
value,
value: convertToSerializableObject(value),
},
},
"*"
Expand Down
11 changes: 10 additions & 1 deletion packages/swr-devtools/src/swr-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,16 @@ export const injectSWRCache = (
};

export const isMetaCache = (key: string) => {
return /^\$(?:req|err|ctx|len)\$/.test(key);
return /^\$(?:req|ctx|len)\$/.test(key);
};

export const isErrorCache = (key: string) => {
return /^\$err\$/.test(key);
};

export const getErrorCacheKey = (key: string) => {
const match = key.match(/^\$err\$(?<cacheKey>.*)?/);
return match?.groups?.cacheKey ?? key;
};

export const isInfiniteCache = (key: string) => {
Expand Down

1 comment on commit 7f7f672

@vercel
Copy link

@vercel vercel bot commented on 7f7f672 Dec 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.