npm package: @ray-js/query
Description: English | [简体中文](./README-zh_CN.md)
Latest version: 0.0.2

English | [简体中文](./README-zh_CN.md)

# @ray-js/query

`@tanstack/react-query` v5's Ray compatible version, API and official documentation are consistent.

## Install

```bash
npm install @ray-js/query
```

## Tuya Mini Program Exclusive Features

### refetchOnAppResume

Supports automatic refetch when the mini program resumes from cache. Implemented using the `ty.onAppResume` API.

**Global Configuration:**

```tsx
import { QueryClient, QueryClientProvider } from '@ray-js/query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // Refresh when mini program resumes if data is stale
      refetchOnAppResume: true,
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  );
}
```

**Individual Query Configuration:**

```tsx
import { useQuery } from '@ray-js/query';

function DeviceStatus() {
  const { data } = useQuery({
    queryKey: ['deviceStatus'],
    queryFn: () => deviceService.getStatus(),
    staleTime: 5000, // Data is fresh within 5 seconds
    refetchOnAppResume: true, // Refresh when mini program resumes (if data is stale)
  });

  return <View>Device Status: {data?.status}</View>;
}
```

**Configuration Options:**

| Value                | Description                                                            |
| -------------------- | ---------------------------------------------------------------------- |
| `false`              | (Default) Do not refresh when mini program resumes                     |
| `true`               | Refresh when mini program resumes if data is stale (exceeds staleTime) |
| `'always'`           | Always refresh when mini program resumes, regardless of staleness      |
| `(query) => boolean` | Dynamically determine whether to refresh based on query state          |

## Usage (Refer to `@tanstack/react-query` Official Documentation)

### Basic Usage

```tsx
import { QueryClient, QueryClientProvider, useQuery } from '@ray-js/query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  );
}

function TodoList() {
  const { isLoading, data, error } = useQuery({
    queryKey: ['todos'],
    queryFn: () => todoService.getTodos(),
  });

  if (error) {
    return <View>Error fetching todos</View>;
  }

  if (isLoading) {
    return <View>Loading...</View>;
  }

  return (
    <View>
      <View>
        {data?.map(todo => (
          <View key={todo.id}>{`${todo.id}: ${todo.title}`}</View>
        ))}
      </View>
    </View>
  );
}
```

### Infinite List

```tsx
import { useInfiniteQuery } from '@ray-js/query';

async function fetchProjects({ pageParam }): Promise<Result> {
  return new Promise(resolve => {
    console.log(' ==> pageParam:', pageParam);
    const id = pageParam;
    setTimeout(() => {
      resolve({
        data: [{ id, name: `project_${id}_${Date.now()}` }],
        nextCursor: id + 1,
      });
    }, 500);
  });
}

export function InfiniteList() {
  const { data, error, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage, status } =
    useInfiniteQuery({
      queryKey: ['projects'],
      queryFn: fetchProjects,
      initialPageParam: 0,
      getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    });

  return (
    <ScrollView>
      <View>
        {status === 'pending' ? (
          <Text>Loading...</Text>
        ) : status === 'error' ? (
          <Text>Error: {error.message}</Text>
        ) : (
          <>
            {data.pages.map((group, i) => (
              <React.Fragment key={i}>
                {group.data.map(project => (
                  <Text key={project.id}>{project.name}</Text>
                ))}
              </React.Fragment>
            ))}
            <View>
              <Button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage}>
                {isFetchingNextPage
                  ? 'Loading more...'
                  : hasNextPage
                  ? 'Load More'
                  : 'Nothing more to load'}
              </Button>
            </View>
            <Text>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</Text>
          </>
        )}
      </View>
    </ScrollView>
  );
}
```

### optimization - placeholder

```tsx
import { useQuery, useQueryClient } from '@ray-js/query';

export function TodoDetail(props: { id: number }) {
  const queryClient = useQueryClient();

  const result = useQuery({
    queryKey: ['todo', props.id],
    queryFn: () => todoService.getTodoById(props.id),
    placeholderData: () => {
      const cachedData = queryClient.getQueryData<Todo[]>(['todos']);
      return cachedData?.find(d => d.id === props.id);
    },
  });

  return (
    <View style={{ display: 'flex', flexDirection: 'column' }}>
      {result.isLoading && <Text>Loading...</Text>}
      <Text>id: {props.id}</Text>
      <Text>title: {result.data.title}</Text>
      <Text>content: {result.data?.content ?? 'fetching...'}</Text>
    </View>
  );
}
```

### optimization - prefetch

```tsx
import { useQueryClient } from '@ray-js/query';

function Home() {
  const queryClient = useQueryClient();

  return (
    <View>
      <Text>Home</Text>
      <Button
        onClick={() => {
          queryClient.prefetchQuery({
            queryKey: ['todo', 1],
            queryFn: () => todoService.getTodoById(1),
          });

          router.push('/todo?id=1');
        }}
      >
        Navigate and prefetch Todo 1
      </Button>
    </View>
  );
}
```