Skip to content

SWR

SeungAh Hong2min read

What is SWR?

  • A React Hooks library for remote data fetching, created by zeit, the group behind Next.js
  • SWR is a strategy that first returns the data from cache (stale), then sends the fetch request (revalidate), and finally arrives at the up-to-date data

SWR Features

  • Simplifies the data fetching logic in your project down to a single line of code
  • Fast, lightweight, and reusable data fetching
  • Built-in cache and request deduplication
  • Real-time experience
  • Transport and protocol agnostic
  • SSR / ISR / SSG support
  • TypeScript ready
  • React Native

SWR covers every aspect of speed, correctness, and stability so you can build a better experience

  • Fast page navigation
  • Interval polling
  • Data dependency
  • Revalidation on focus
  • Revalidation on network recovery
  • Local mutation (Optimistic UI)
  • Smart error retry
  • Pagination and scroll position recovery
  • React Suspense

Key Features

  • Solving props drilling
// 페이지 컴포넌트
function Page() {
  const [user, setUser] = useState(null);
 
  // 데이터 가져오기
  useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(data => setUser(data));
  }, []);
 
  // 전역 로딩 상태
  if (!user) return <Spinner />;
 
  return (
    <div>
      <Navbar user={user} />
      <Content user={user} />
    </div>
  );
}
 
// 자식 컴포넌트
 
function Navbar({ user }) {
  return (
    <div>
      ...
      <Avatar user={user} />
    </div>
  );
}
 
--swr;
 
function Content() {
  const { user, isLoading } = useSWR('/api/user');
  if (isLoading) return <Spinner />;
  return <h1>Welcome back, {user.name}</h1>;
}
 
function Avatar() {
  const { user, isLoading } = useSWR('/api/user');
  if (isLoading) return <Spinner />;
  return <img src={user.avatar} alt={user.name} />;
}
  • Global options configuration
<SWRConfig
  value={
    refreshInterval: 3000,
    fetcher: (resource, init) => fetch(resource, init).then(res => res.json()),
  }
>
  <Dashboard />
</SWRConfig>
  • Revalidation on focus / interval polling / revalidation on network recovery
useSWR('/api/user', fetcher, {
  revalidateOnFocus: true,  revalidateOnReconnect : true,   refreshInterval: 1000   
}
  • Local mutation (Optimistic UI)
import useSWR, { useSWRConfig } from 'swr';
 
function App() {
  const { mutate } = useSWRConfig();
 
  return (
    <div>
      <Profile />
      <button
        onClick={() => {
          // set the cookie as expired
          document.cookie =
            'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
 
          // tell all SWRs with this key to revalidate
          mutate('/api/user');
        }}
      >
        Logout
      </button>
    </div>
  );
}
  • Smart error retry
useSWR('/api/user', fetcher, {
  onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
    // Never retry on 404.
    if (error.status === 404) return
 
    // Never retry for a specific key.
    if (key === '/api/user') return
 
    // Only retry up to 10 times.
    if (retryCount >= 10) return
 
    // Retry after 5 seconds.
    setTimeout(() => revalidate({ retryCount }), 5000)
  }
}
  • Next.js SSG and SSR
export async function getStaticProps() {
  // `getStaticProps` is executed on the server side.
  const article = await getArticleFromAPI();
  return {
    props: {
      fallback: {
        '/api/article': article,
      },
    },
  };
}
 
function Article() {
  // `data` will always be available as it's in `fallback`.
  const { data } = useSWR('/api/article', fetcher);
  return <h1>{data.title}</h1>;
}
 
export default function Page({ fallback }) {
  // SWR hooks inside the `SWRConfig` boundary will use those values.
  return (
    <SWRConfig value={fallback}>
      <Article />
    </SWRConfig>
  );
}
  • React Suspense / ErrorBoundary
import { Suspense } from 'react';
import useSWR from 'swr';
 
function Profile() {
  const { data } = useSWR('/api/user', fetcher, { suspense: true });
  return <div>hello, {data.name}</div>;
}
 
function App() {
  return (
    <ErrorBoundary FallbackComponent={<div>error....</div>}>
      <Suspense fallback={<div>loading...</div>}>
        <Profile />
      </Suspense>
    </ErrorBoundary>
  );
}
  • Caching
  • TypeScript
  • React Native

React Query vs. SWR Comparison

  • Comparison site: https://react-query.tanstack.com/comparison

  • Code comparison Sample code: https://github.com/seungahhong/states-todos

    -- services
    export const fetcher = (...args) => axios.get(...args);
    export const fetchTodos = (id) => `https://jsonplaceholder.typicode.com/todos${id ? `/${id}` : ''}`;
     
    -- react query
    export const useFetchTodo = ({ id, suspense } ) => {
      const { data, error } = useQuery(fetchTodos(id), () => fetcher(fetchTodos(id)), { suspense: !!suspense, useErrorBoundary: true });
     
      return {
        data,
        isLoading: !data && !error,
        error,
      }
    }
     
    -- swr
    useSWR(fetchTodos(id), () => fetcher(fetchTodos(id)))
    useSWR(fetchTodos(id)', url => fetcher(url))
    useSWR(fetchTodos(id), fetcher)
     
    export const useFetchTodo = ({ id, suspense } ) => {
      const { data, error } = useSWR(fetchTodos(id), fetcher, { suspense: !!suspense });
     
      return {
        data,
        isLoading: !data && !error,
        error,
      }
    }
     
    const { data: response } = useFetchTodo({ id: 1, suspense: true });
    // const { data: response } = useFetchTodo({ id: 1 });
    // if (isLoading) return <div>loading...</div>;
    // if (error) return <div>error....</div>;

Reference pages

https://swr.vercel.app/ko/docs/getting-started

https://swr.vercel.app/ko