> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/contributor/contributor-pt/guias-do-contribuidor/padroes-de-ui/rtk-query.md).

# RTK Query

RTK Query é a poderosa camada de busca e cache de dados do Redux Toolkit. Elimina a necessidade de escrever action creators assíncronos e gerencia estados de carregamento, cache e sincronização de dados automaticamente.

## Configuração básica do Client

Todos os endpoints do RTK Query estendem a partir de uma única instância base do client.

### Configuração do Base Query

```tsx
// src/services/baseQuery.ts
import { fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import type { RootState } from '@/app/store';

export const baseQuery = fetchBaseQuery({
  baseUrl: process.env.NEXT_PUBLIC_API_URL, // ex., https://api.decentraland.org
  
  prepareHeaders: (headers, { getState }) => {
    const state = getState() as RootState;
    
    // Adicionar token de autenticação
    const token = state.user.session?.authToken;
    if (token) {
      headers.set('authorization', `Bearer ${token}`);
    }
    
    // Adicionar chain ID para contexto web3
    const chainId = state.user.chainId;
    if (chainId) {
      headers.set('x-chain-id', String(chainId));
    }
    
    // Headers padrão
    headers.set('accept', 'application/json');
    headers.set('content-type', 'application/json');
    
    return headers;
  },
  
  credentials: 'omit', // ou 'include' se o backend exigir cookies
});
```

### Instância do Client

```tsx
// src/services/client.ts
import { createApi } from '@reduxjs/toolkit/query/react';
import { baseQuery } from './baseQuery';

export const client = createApi({
  reducerPath: 'client',
  baseQuery,
  
  // Defina todos os tipos de tag possíveis para invalidação de cache
  tagTypes: [
    'User',
    'Profile',
    'Parcels',
    'Estates',
    'Credits',
    'Orders',
    'Sales',
    'NFTs',
  ],
  
  // Configuração de cache
  keepUnusedDataFor: 60,         // Manter dados não usados por 60 segundos
  refetchOnFocus: true,           // Rebuscar quando a janela voltar a ter foco
  refetchOnReconnect: true,       // Rebuscar ao reconectar
  refetchOnMountOrArgChange: 30,  // Rebuscar se os dados tiverem mais de 30s
  
  // Endpoints serão injetados em arquivos de feature
  endpoints: () => ({}),
});
```

## Convenções de Tags

Tags são usadas para invalidação de cache e sincronização. Siga estas convenções:

### Nomeação de Tag

| Tipo de Recurso     | Tags de Query                        | Mutação Invalida               |
| ------------------- | ------------------------------------ | ------------------------------ |
| **Coleções**        | `'Parcels'` (plural)                 | `'Parcels'`                    |
| **Entidade única**  | `{type: 'Parcels', id: '123'}`       | `{type: 'Parcels', id: '123'}` |
| **Lista + Detalhe** | `['Parcels', {type: 'Parcels', id}]` | `['Parcels']` ou id específico |

### Exemplos de Tags

```tsx
// Coleção: fornece tag de lista
providesTags: ['Parcels']

// Entidade única: fornece tag específica + tag de lista
providesTags: (result) => 
  result 
    ? [{ type: 'Parcels', id: result.id }, 'Parcels']
    : ['Parcels']

// Mutação: invalida tanto a lista quanto a entidade específica
invalidatesTags: (result, error, arg) => [
  { type: 'Parcels', id: arg.id },
  'Parcels'
]
```

## Criando Endpoints

Endpoints DEVERIAM ficar co-localizados com sua feature em `feature.client.ts` arquivos.

### Endpoint de Query (Leitura)

```tsx
// src/features/land/land.client.ts
import { client } from '@/services/client';

export type Tile = {
  x: number;
  y: number;
  type: 'parcel' | 'road' | 'plaza';
  owner?: string;
};

export type Parcel = {
  id: string;
  x: number;
  y: number;
  owner: string;
  name?: string;
  description?: string;
};

export const landClient = client.injectEndpoints({
  endpoints: (build) => ({
    // Obter todos os tiles
    getTiles: build.query<Record<string, Tile>, void>({
      query: () => '/v1/tiles',
      providesTags: ['Parcels'],
    }),
    
    // Obter parcel por coordenadas
    getParcelByCoords: build.query<Parcel, { x: number; y: number }>({
      query: ({ x, y }) => `/v1/lands/${x}/${y}`,
      providesTags: (result, error, arg) =>
        result
          ? [{ type: 'Parcels', id: result.id }, 'Parcels']
          : ['Parcels'],
    }),
    
    // Obter parcels por proprietário
    getParcelsByOwner: build.query<Parcel[], { owner: string }>({
      query: ({ owner }) => `/v1/lands/owner/${owner}`,
      providesTags: (result) =>
        result
          ? [
              ...result.map(({ id }) => ({ type: 'Parcels' as const, id })),
              'Parcels',
            ]
          : ['Parcels'],
    }),
  }),
  overrideExisting: false,
});

// Exportar hooks
export const {
  useGetTilesQuery,
  useGetParcelByCoordsQuery,
  useGetParcelsByOwnerQuery,
} = landClient;
```

### Endpoint de Mutation (Escrita)

```tsx
// src/features/land/land.client.ts (continuação)
export const landClient = client.injectEndpoints({
  endpoints: (build) => ({
    // ... endpoints de query ...
    
    // Atualizar nome do parcel
    updateParcelName: build.mutation<
      Parcel,
      { id: string; name: string }
    >({
      query: ({ id, name }) => ({
        url: `/v1/lands/${id}`,
        method: 'PATCH',
        body: { name },
      }),
      invalidatesTags: (result, error, arg) => [
        { type: 'Parcels', id: arg.id },
        'Parcels',
      ],
    }),
    
    // Transferir parcel
    transferParcel: build.mutation<
      { ok: boolean },
      { id: string; to: string }
    >({
      query: ({ id, to }) => ({
        url: `/v1/lands/${id}/transfer`,
        method: 'POST',
        body: { to },
      }),
      invalidatesTags: (result, error, arg) => [
        { type: 'Parcels', id: arg.id },
        'Parcels', // Invalidar lista para atualizar filtros de proprietário
      ],
    }),
  }),
});

export const {
  useUpdateParcelNameMutation,
  useTransferParcelMutation,
} = landClient;
```

## Atualizações Otimistas

Use `onQueryStarted` para atualizações otimistas de UI com rollback automático em caso de falha.

```tsx
// src/features/credits/credits.client.ts
import { client } from '@/services/client';

export type CreditsBalance = {
  address: string;
  amount: number;
  lastUpdated: string;
};

export const creditsClient = client.injectEndpoints({
  endpoints: (build) => ({
    getBalance: build.query<CreditsBalance, { address: string }>({
      query: ({ address }) => `/v1/credits/${address}`,
      providesTags: (result, error, arg) => [
        { type: 'Credits', id: arg.address }
      ],
    }),
    
    grantCredits: build.mutation<
      { ok: true; newBalance: number },
      { address: string; amount: number }
    >({
      query: (body) => ({
        url: `/v1/credits/grant`,
        method: 'POST',
        body,
      }),
      
      // Atualização otimista
      async onQueryStarted({ address, amount }, { dispatch, queryFulfilled }) {
        // Atualizar o cache otimisticamente
        const patchResult = dispatch(
          client.util.updateQueryData('getBalance', { address }, (draft) => {
            draft.amount += amount;
            draft.lastUpdated = new Date().toISOString();
          })
        );
        
        try {
          // Aguardar a conclusão da mutation
          const { data } = await queryFulfilled;
          
          // Atualizar com a resposta do servidor
          dispatch(
            client.util.updateQueryData('getBalance', { address }, (draft) => {
              draft.amount = data.newBalance;
            })
          );
        } catch {
          // Rollback em caso de falha
          patchResult.undo();
        }
      },
      
      // Também invalidar para garantir consistência
      invalidatesTags: (result, error, arg) => [
        { type: 'Credits', id: arg.address }
      ],
    }),
  }),
});

export const { useGetBalanceQuery, useGrantCreditsMutation } = creditsClient;
```

## Opções Avançadas de Query

### Polling

```tsx
// Poll a cada 10 segundos
const { data } = useGetBalanceQuery(
  { address },
  { pollingInterval: 10000 }
);
```

### Pular Query

```tsx
// Pular query se o address não estiver disponível
const { data } = useGetBalanceQuery(
  { address: address! },
  { skip: !address }
);
```

### Query Preguiçosa

```tsx
const [trigger, result] = useLazyGetParcelByCoordsQuery();

// Disparar manualmente
const handleClick = () => {
  trigger({ x: 10, y: 20 });
};
```

### Transformar Response

```tsx
getParcel: build.query<Parcel, string>({
  query: (id) => `/v1/lands/${id}`,
  transformResponse: (response: ApiResponse<Parcel>) => response.data,
})
```

### Serialização Customizada

Para paginação ou busca, personalize a serialização da chave de cache:

```tsx
searchParcels: build.query<Parcel[], { q: string; owner?: string; page?: number }>({
  query: (args) => ({
    url: '/v1/parcels/search',
    params: args,
  }),
  
  // Chave de cache customizada para lidar com params opcionais
  serializeQueryArgs: ({ endpointName, queryArgs }) => {
    const { q, owner = 'any', page = 1 } = queryArgs;
    return `${endpointName}-${q}-${owner}-${page}`;
  },
  
  // Mesclar resultados para paginação
  merge(currentCache, newItems, { arg }) {
    if (arg.page === 1) {
      return newItems;
    }
    return [...currentCache, ...newItems];
  },
  
  // Forçar refetch quando os args mudarem
  forceRefetch({ currentArg, previousArg }) {
    return JSON.stringify(currentArg) !== JSON.stringify(previousArg);
  },
  
  providesTags: ['Parcels'],
})
```

## Tratamento de Erros

### Tratamento de Erro Customizado

```tsx
import { FetchBaseQueryError } from '@reduxjs/toolkit/query';

export function isFetchBaseQueryError(
  error: unknown
): error is FetchBaseQueryError {
  return typeof error === 'object' && error != null && 'status' in error;
}

export function isErrorWithMessage(
  error: unknown
): error is { message: string } {
  return (
    typeof error === 'object' &&
    error != null &&
    'message' in error &&
    typeof (error as any).message === 'string'
  );
}
```

Uso em componentes:

```tsx
const { data, error } = useGetParcelQuery({ id });

if (error) {
  if (isFetchBaseQueryError(error)) {
    const errMsg = 'error' in error ? error.error : JSON.stringify(error.data);
    return <div>Error: {errMsg}</div>;
  } else if (isErrorWithMessage(error)) {
    return <div>Error: {error.message}</div>;
  }
}
```

## Gerenciamento de Cache

### Atualizações manuais de cache

```tsx
// Atualizar cache diretamente
dispatch(
  client.util.updateQueryData('getBalance', { address }, (draft) => {
    draft.amount = 1000;
  })
);
```

### Invalidar Cache

```tsx
// Invalidar todas as queries Credits
dispatch(client.util.invalidateTags(['Credits']));

// Invalidar entidade específica
dispatch(client.util.invalidateTags([{ type: 'Credits', id: address }]));
```

### Resetar Estado do Client

```tsx
// Resetar todo o estado do client
dispatch(client.util.resetApiState());
```

### Prefetch de Dados

```tsx
// Prefetch de dados antes da navegação
dispatch(
  client.util.prefetch('getParcel', { id: '123' }, { force: false })
);
```

## Boas Práticas

### 1. Use nomes de endpoint descritivos

```tsx
// ✅ Bom
getParcelByCoords
getParcelsByOwner
updateParcelName

// ❌ Ruim
getParcel
fetch
update
```

### 2. Forneça Tags abrangentes

```tsx
// ✅ Bom: Fornece tanto tag de lista quanto tag de entidade
providesTags: (result) =>
  result
    ? [{ type: 'Parcels', id: result.id }, 'Parcels']
    : ['Parcels']

// ❌ Ruim: Fornece apenas a tag de lista
providesTags: ['Parcels']
```

### 3. Trate estados de carregamento e erro

```tsx
// ✅ Bom: Tratamento completo de estados
const { data, isLoading, isFetching, isError, error } = useGetParcelQuery({ id });

if (isLoading) return <Spinner />;
if (isError) return <Error error={error} />;
if (!data) return null;

// ❌ Ruim: Tratamento incompleto de estados
const { data } = useGetParcelQuery({ id });
return <div>{data.name}</div>; // Pode travar se data for undefined
```

### 4. Use Type Guards

```tsx
// ✅ Bom: Tratamento de erro com segurança de tipos
if (isFetchBaseQueryError(error)) {
  // Tratar erro de fetch
} else if (isErrorWithMessage(error)) {
  // Tratar erro com message
}

// ❌ Ruim: Cast inseguro de tipos
const message = (error as any).message;
```

## Próximos Passos

* Aprenda sobre [Gerenciamento de Estado](/contributor/contributor-pt/guias-do-contribuidor/padroes-de-ui/state-management.md) para estado de UI local
* Rever [Padrões de Componentes](/contributor/contributor-pt/guias-do-contribuidor/padroes-de-ui/component-patterns.md) para exemplos de uso
* Entender [Integração Web3](/contributor/contributor-pt/guias-do-contribuidor/padroes-de-ui/web3-integration.md) para dados de blockchain


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.decentraland.org/contributor/contributor-pt/guias-do-contribuidor/padroes-de-ui/rtk-query.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
