4.0.6 • Published 23 days ago

solid-swr v4.0.6

Weekly downloads
1
License
MIT
Repository
github
Last release
23 days ago

Introduction

Quote from vercel's SWR for react:

The name “SWR” is derived from stale-while-revalidate, a HTTP cache invalidation strategy popularized by HTTP RFC 5861. SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally come with the up-to-date data.

With SWR, components will get a stream of data updates constantly and automatically. And the UI will be always fast and reactive.

Features

  • 💙 Built for solid
  • ⚡ Blazingly fast with reconciled solid stores
  • ♻️ Reusable and lightweight data fetching
  • 📦 Built-in cache and request deduplication
  • 🔄 Local mutation (optimistic UI)
  • 😉 And much more!

Table of contents

Quick Start

Install the package

npm i solid-swr

Bare bones usage

import useSWR from "solid-swr";

function Profile() {
    const { data, error, isLoading } = useSWR(() => "/api/user/123");

    return (
        <div>
            {isLoading() && <div class="spinner" />}
            {data.v?.name}
            {error.v && <p>Oh no: {error.v}</p>}
        </div>
    );
}
function useSWR<Res = unknown, Err = unknown>(key: Accessor<Key>, _options?: Options<Res, Err>): {
    data: StoreIfy<Res | undefined>;
    error: StoreIfy<Err | undefined>;
    isLoading: Accessor<boolean>;
    hasFetched: Accessor<...>;
    mutate: (payload?: Res | ... 1 more ... | undefined, _mutationOptions?: MutationOptions) => void;
    fetcher: (abortController?: AbortController) => Promise<...> | undefined;
}

Returned values

The hook returns an object that you can destructure

  • data: a store that contains your response generic or undefined
  • error: a store that contains your error generic or undefined
  • isLoading: a signal that returns a boolean
  • mutate: a function bound to the hook's key that is used for manual changes and can be used for optimistic updates
  • hasFetched: a signal that's true when the hook received some info, helps with showing dependent hook loading states
  • fetcher: a detached fetcher -> call the finalized fetcher yourself, it gets the key passed into the hook

Options

The useSWR hook accepts options as a second parameter, the default options are shown here:

useSWR(() => "_", {
    fetcher: defaultFetcher,
    keepPreviousData: false,
    isEnabled: true,
    refreshInterval: 0,
    cache: new LRU<string, CacheItem>(5e3),
    onSuccess: noop,
    onError: noop,
    revalidateOnFocus: true,
    revalidateOnOnline: true,
});

If you want to change the passed options at runtime, please use a store

The options are merged with context, read more

API

KeyExplainDefault
fetcherThe function responsible for throwing errors and returning dataThe native fetch which parses only json and throws the response json on >=400 responses
keepPreviousDataIf cache is empty and the key changes, should we keep the old datafalse
isEnabledIs the hook enabledtrue
cacheA data source for storing fetcher resultsA simple in-memory LRU
onSuccessA callback that gets the data when it gets updated with truthy datanoop
onErrorA callback that gets the error when it gets updated with a truthy errornoop
revalidateOnFocusAutomatically revalidate when window has gotten focustrue
revalidateOnOnlineAutomatically revalidate when connection came backtrue

Options with context

Provide your own default settings for hooks

import { SWROptionsProvider } from "solid-swr";

const yourOwnFetcher = async (x: string, { signal }: { signal: AbortSignal }) => {};

function Root() {
    return (
        <SWROptionsProvider
            value={{
                fetcher: yourOwnFetcher,
            }}
        >
            <App />
        </SWROptionsProvider>
    );
}

Note: providers merge their options with the parent provider, so you can have 1 options provider at the root of your app, for example with only the fetcher, and another provider deeper in the app tree with some specific options and it will preserve the fetcher from the parent provider

API

Refer to the options api

Mutation

Bound mutation

This refers to using the mutate function returned by individual hooks

It actually uses the global mutation hook under the hood

Basic usage goes like this:

import useSWR from "solid-swr";

function Profile() {
    const { data, mutate } = useSWR(() => "/api/user");

    return (
        <div>
            <h1>My name is {data.v?.name}.</h1>
            <button
                onClick={async () => {
                    const newName = data.v?.name.toUpperCase();

                    mutate(
                        // you can use a function here as well
                        // it gets latest data
                        { ...data.v?, name: newName },
                        {
                            // this is false by default
                            revalidate: false,
                        }
                    );

                    // send a request to the API to update the data
                    await requestUpdateUsername(newName);
                }}
            >
                Uppercase my name!
            </button>
        </div>
    );
}

Global mutation

There is an exported hook useMatchMutate using which you can filter all keys and mutate them at once

import { useMatchMutate } from "solid-swr";

function onClickOrWhatever() {
    const mutate = useMatchMutate();
    mutate(
        // all keys
        key => true,
        // payload
        undefined,
        // settings
        { revalidate: true }
    );
}

Options

Options are passed as a second parameter to mutate:

mutate(x => x, {
    // here
});

And as a third parameter when using useMatchMutate:

mutate(x => true, payload, {
    // here
});

Currently only 1 option is available:

  • revalidate: Should the hook refetch the data after the mutation? If the payload is undefined it will always refetch

API

KeyExplainDefault
revalidateShould the hook refetch the data after the mutation? If the payload is undefined it will always refetchfalse

SSR

For SSR there is another context SWRFallback which as you can guess by the name let's you add fallback data for specific keys

Example usage:

import useSWR, { SWRFallback } from "solid-js";

const key = "foo";

function Root(props: { fallback: any }) {
    return (
        <SWRFallback.Provider
            value={{
                [key]: fallback,
            }}
        >
            <App />
        </SWRFallback.Provider>
    );
}

function App() {
    const { data } = useSWR(() => key);
    console.log(data.v); // already set here
    return <></>;
}

useSWRInfinite

A hook for infinite loading behavior

This is a wrapper around the normal useSWR, so it automatically gets all of its features

The differences between it and useSWR are:

  • bound mutation is removed (mutate with global mutation)
  • data is a store with an array of responses

Basic usage goes like this

import { useSWRInfinite } from "solid-swr";

function App() {
    const { data, error, index, setIndex, isLoading } = useSWRInfinite((index, prevData) => {
        if (prevData && prevData.next === false) return undefined;
        return `https://example.com?page=${index + 1}`;
    });

    return (
        <div>
            <For each={data.v}>{(_, item) => <div>{item}</div>}</For>
        </div>
    );
}

useSWRMutation

A helper hook for remote mutations that wraps the global mutation hook useMatchMutate

Basic usage

import useSWR, { useSWRMutation } from "solid-swr";

function App() {
    const key = () => "foo";
    const swr = useSWR(key);

    const mutation = useSWRMutation(
        k => k === key(),
        async (arg: any) => {
            return await updateUser(arg);
        }
    );

    async function onClick(arg: any) {
        try {
            const response = await mutation.trigger(arg);

            // do you want to just revalidate?
            mutation.populateCache();

            // or do optimistic updates ?
            // current is useSWR data
            mutation.populateCache((key, referenceToPrev) => {
                if (current === undefined) {
                    // ...
                    return;
                }

                // do something with reference to cache item, you should clone it to be safe
                return aNewItem;
            });
        } catch (err) {
            // handle error
            // or don't, the mutation.error() is there
        }
    }

    return (
        <div>
            {swr.data.v}
            {mutation.isTriggering()}
            {mutation.error.v}
        </div>
    );
}

The populateCache returned method is just a wrapper for the global useMatchMutate hook, read up on it here

API

function useSWRMutation<Pld, Res = unknown, Err = unknown, Arg = unknown>(
    filter: FilterKeyFn,
    fetcher: Fetcher<Res, Arg>
): {
    isTriggering: Accessor<boolean>;
    trigger: (arg: Arg) => Promise<Res>;
    populateCache: (payload: Payload<Pld>, mutationOptions?: MutationOptions) => void;
    error: Accessor<Err | undefined>;
};

Aborting requests

The core useSWR fetcher always gets an AbortSignal which will abort the older request if a new one comes in

The default fetcher utilizes this mechanic

The signal is passed to the fetcher as the second parameter in an object:

const fetcher = async (key: string, { signal }: { signal: AbortSignal }) => {
    return await fetch("...", { signal });
};

Note

The signal is only passed in the core effect of swr

Structuring your hooks

This is how I structure my swr hooks after a ton of refactoring and different edge case considerations

// /client/hooks/swr/types/utils.d.ts
export type SwrArg<T> = Accessor<T | undefined>;

// /client/hooks/swr/utils/createSwrKey.ts
export default function createSwrKey<T>(base: string, arg: Accessor<T | undefined>) {
    const key = createMemo(() => {
        const a = arg();
        if (!a) return;
        return urlbat(base, a);
    });

    return key;
}

// /client/hooks/swr/user/useUser.ts
const base = "/user";

type Arg = {
    // anything
};

export function useUser(arg: SwrArg<Arg> = () => ({}), options?: Options<Data, unknown>) {
    const key = createSwrKey(base, arg);

    const swr = useSWR<Data>(key, options);
    const actions = useActions(key);

    return [swr, actions] as const;
}

function useActions(key: Accessor<string | undefined>) {
    const login = useSWRMutation(
        x => x === key(),
        async (arg: LoginBody) => {
            // only if you need to append to base
            const k = key();
            if (!k) return;

            await axios.post(urlbat(k, "login"), arg);
        }
    );

    return { login };
}

And the example usage

function UserInfo() {
    const [{ data: user }, { login }] = useUser();

    const onLogin = async () => {
        try {
            await login.trigger({
                /** something */
            });

            // do optimistic updates, or just revalidate
            login.populateCache();
        } catch {
            // show toaster, idk ur choice
        }
    };

    return (
        <div>
            <div>{user.v?.name}</div>
            <button>Login</button>
        </div>
    );
}
4.0.6

23 days ago

4.0.5

23 days ago

4.0.4

23 days ago

4.0.3

1 month ago

4.0.2

1 month ago

4.0.1

1 month ago

4.0.0

2 months ago

3.1.2

2 months ago

3.1.1

3 months ago

3.1.0

3 months ago

3.0.3

3 months ago

3.0.2

3 months ago

3.0.1

3 months ago

3.0.0

3 months ago

2.1.0

3 months ago

2.0.3

3 months ago

2.0.2

3 months ago

2.0.1

3 months ago

2.0.0

3 months ago

1.1.3-9ba3f8d

3 months ago

1.1.3-e1ce543

3 months ago

1.1.3-4c9b950

3 months ago

1.1.3-05e428f

3 months ago

1.1.3-ef4460c

3 months ago

1.1.3-5c47333

3 months ago

1.1.3

3 months ago

1.1.3-078549d

3 months ago

1.1.2

4 months ago

1.1.1

4 months ago

1.1.0-85694cb

4 months ago

1.0.3-3c07207

4 months ago

1.1.0

4 months ago

1.1.0-9d88bef

4 months ago

1.0.2-2e19add

6 months ago

1.0.3

6 months ago

1.0.2-f38cff4

6 months ago

1.0.2-f763131

6 months ago

1.0.2

7 months ago

1.0.1

7 months ago

1.0.0

7 months ago

0.16.0-ee4ef9ab

7 months ago

0.15.0-c79f5605

7 months ago

0.15.0-0b079b41

7 months ago

1.0.0-69f1ac4

7 months ago

0.16.0-7c1aab0

7 months ago

1.0.0-ca5df21

7 months ago

0.16.0-5a3244e

7 months ago

0.16.0

7 months ago

0.15.0-b479af75

7 months ago

0.15.0-526371de

7 months ago

1.0.0-3c31df3

7 months ago

0.15.0-a99c323a

7 months ago

0.15.0-772576a0

7 months ago

0.15.0-804fb754

7 months ago

0.15.0-30b10ee9

7 months ago

0.10.0

8 months ago

0.3.0

9 months ago

0.11.0

8 months ago

0.9.0

8 months ago

0.8.1

8 months ago

0.12.0

8 months ago

0.11.1

8 months ago

0.8.0

8 months ago

0.13.0

8 months ago

0.11.2

8 months ago

0.14.0

7 months ago

0.11.3

8 months ago

0.15.0

7 months ago

0.11.4

8 months ago

0.5.0

9 months ago

0.4.0

9 months ago

0.3.1

9 months ago

0.7.0

8 months ago

0.5.2

9 months ago

0.6.0

9 months ago

0.5.1

9 months ago

0.0.0-canary

3 years ago