Elysia Tools

Parcourez outils, exemples, explications visuelles et guides thématiques dans un espace clair.

Catégories populaires

Dossiers

Langue
ZHESPTRUFRDEEN

Elysia Tools

Navigation mobile

Récupération de données

Exemples TanStack Query

Exemples TanStack Query (anciennement React Query) incluant la récupération de données, la mise en cache, les mutations, les mises à jour optimistes et les patterns avancés

Exemples

Entrées de cette collection

Code sourcetypescriptSimple

Utilisation de Base TanStack Query

Patterns fondamentaux TanStack Query incluant requêtes, mutations, gestion d'erreurs et états de chargement

Difficulté

3/10

Temps estimé

25 min

Étiquettes

tanstack-query, react, data-fetching, state-management

Prérequis

React hooks, TypeScript, Async/await, REST APIs

typescript
// TanStack Query Basic Usage Examples
// TanStack Query is a powerful data fetching and state management library for React

import { useQuery, useMutation, useQueryClient, QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState, useEffect, useCallback, useMemo } from 'react'

// 1. Setup Query Client
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutes
      retry: 3,
      refetchOnWindowFocus: false,
    },
  },
})

// Provider component
export function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <UserProfile />
      <TodoList />
      <PostList />
    </QueryClientProvider>
  )
}

// 2. Basic Data Fetching with useQuery
interface User {
  id: number
  name: string
  email: string
  username: string
  address: {
    street: string
    city: string
    zipcode: string
  }
}

// Fetch function
const fetchUser = async (id: number): Promise<User> => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)

  if (!response.ok) {
    throw new Error('Failed to fetch user')
  }

  return response.json()
}

// User component with useQuery
function UserProfile() {
  const [userId, setUserId] = useState(1)

  const {
    data: user,
    isLoading,
    isError,
    error,
    refetch,
    isFetching,
  } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
    enabled: !!userId, // Only fetch when userId exists
    staleTime: 1000 * 60 * 10, // 10 minutes
  })

  const handleNextUser = () => {
    setUserId(prev => prev + 1)
  }

  const handlePrevUser = () => {
    setUserId(prev => Math.max(1, prev - 1))
  }

  return (
    <div className="p-6 border rounded-lg mb-6">
      <h2 className="text-2xl font-bold mb-4">User Profile</h2>

      <div className="mb-4 space-x-2">
        <button
          onClick={handlePrevUser}
          disabled={userId <= 1}
          className="px-3 py-1 bg-gray-500 text-white rounded disabled:opacity-50"
        >
          Previous
        </button>
        <span className="mx-2">User ID: {userId}</span>
        <button
          onClick={handleNextUser}
          className="px-3 py-1 bg-blue-500 text-white rounded"
        >
          Next
        </button>
        <button
          onClick={() => refetch()}
          disabled={isFetching}
          className="px-3 py-1 bg-green-500 text-white rounded disabled:opacity-50 ml-4"
        >
          {isFetching ? 'Refetching...' : 'Refetch'}
        </button>
      </div>

      {isLoading && <div className="text-blue-500">Loading user data...</div>}

      {isError && (
        <div className="text-red-500">
          Error: {error instanceof Error ? error.message : 'Unknown error'}
        </div>
      )}

      {user && (
        <div>
          <h3 className="text-xl font-semibold">{user.name}</h3>
          <p className="text-gray-600">@{user.username}</p>
          <p className="text-gray-800">{user.email}</p>
          <p className="text-gray-700">
            {user.address.street}, {user.address.city} {user.address.zipcode}
          </p>
        </div>
      )}
    </div>
  )
}

// 3. Mutations with useMutation
interface Todo {
  id: number
  title: string
  completed: boolean
  userId: number
}

const fetchTodos = async (): Promise<Todo[]> => {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos')
  if (!response.ok) throw new Error('Failed to fetch todos')
  return response.json()
}

const createTodo = async (newTodo: { title: string; userId: number }): Promise<Todo> => {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      title: newTodo.title,
      userId: newTodo.userId,
      completed: false,
    }),
  })

  if (!response.ok) throw new Error('Failed to create todo')
  return response.json()
}

const toggleTodo = async (todoId: number, completed: boolean): Promise<Todo> => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ completed }),
  })

  if (!response.ok) throw new Error('Failed to update todo')
  return response.json()
}

function TodoList() {
  const queryClient = useQueryClient()
  const [newTodoTitle, setNewTodoTitle] = useState('')

  // Fetch todos
  const {
    data: todos = [],
    isLoading,
    isError,
    error,
  } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    staleTime: 1000 * 60 * 5, // 5 minutes
  })

  // Create todo mutation
  const createTodoMutation = useMutation({
    mutationFn: createTodo,
    onSuccess: (newTodo) => {
      // Add new todo to cache
      queryClient.setQueryData(['todos'], (oldTodos: Todo[] = []) => [
        ...oldTodos,
        newTodo,
      ])
      setNewTodoTitle('') // Clear input
    },
    onError: (error) => {
      console.error('Failed to create todo:', error)
    },
  })

  // Toggle todo mutation
  const toggleTodoMutation = useMutation({
    mutationFn: ({ id, completed }: { id: number; completed: boolean }) =>
      toggleTodo(id, completed),
    onSuccess: (updatedTodo) => {
      // Update todo in cache
      queryClient.setQueryData(['todos'], (oldTodos: Todo[] = []) =>
        oldTodos.map(todo =>
          todo.id === updatedTodo.id ? updatedTodo : todo
        )
      )
    },
  })

  const handleCreateTodo = (e: React.FormEvent) => {
    e.preventDefault()
    if (newTodoTitle.trim()) {
      createTodoMutation.mutate({
        title: newTodoTitle.trim(),
        userId: 1, // Using fixed user ID for demo
      })
    }
  }

  const handleToggleTodo = (todoId: number, completed: boolean) => {
    toggleTodoMutation.mutate({ id: todoId, completed: !completed })
  }

  if (isLoading) return <div className="p-4">Loading todos...</div>
  if (isError) return <div className="p-4 text-red-500">Error: {error instanceof Error ? error.message : 'Unknown error'}</div>

  return (
    <div className="p-6 border rounded-lg mb-6">
      <h2 className="text-2xl font-bold mb-4">Todo List</h2>

      <form onSubmit={handleCreateTodo} className="mb-4 flex gap-2">
        <input
          type="text"
          value={newTodoTitle}
          onChange={(e) => setNewTodoTitle(e.target.value)}
          placeholder="New todo title..."
          className="flex-1 px-3 py-2 border rounded"
          disabled={createTodoMutation.isPending}
        />
        <button
          type="submit"
          disabled={createTodoMutation.isPending}
          className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
        >
          {createTodoMutation.isPending ? 'Adding...' : 'Add Todo'}
        </button>
      </form>

      <ul className="space-y-2">
        {todos.slice(0, 10).map((todo) => (
          <li key={todo.id} className="flex items-center gap-2 p-2 border rounded">
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => handleToggleTodo(todo.id, todo.completed)}
              disabled={toggleTodoMutation.isPending}
              className="w-4 h-4"
            />
            <span className={todo.completed ? 'line-through text-gray-500' : ''}>
              {todo.title}
            </span>
            {toggleTodoMutation.isPending && (
              <span className="text-sm text-gray-500">Updating...</span>
            )}
          </li>
        ))}
      </ul>
    </div>
  )
}

// 4. Dependent Queries
interface Post {
  id: number
  title: string
  body: string
  userId: number
}

interface Comment {
  id: number
  postId: number
  name: string
  email: string
  body: string
}

const fetchPosts = async (userId: number): Promise<Post[]> => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}/posts`)
  if (!response.ok) throw new Error('Failed to fetch posts')
  return response.json()
}

const fetchComments = async (postId: number): Promise<Comment[]> => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}/comments`)
  if (!response.ok) throw new Error('Failed to fetch comments')
  return response.json()
}

function PostList() {
  const [selectedUserId] = useState(1)

  // First query: fetch posts for a user
  const {
    data: posts = [],
    isLoading: postsLoading,
    error: postsError,
  } = useQuery({
    queryKey: ['posts', selectedUserId],
    queryFn: () => fetchPosts(selectedUserId),
    enabled: !!selectedUserId,
  })

  // Second query: fetch comments for the first post (dependent on first query)
  const {
    data: comments = [],
    isLoading: commentsLoading,
    error: commentsError,
  } = useQuery({
    queryKey: ['comments', posts[0]?.id],
    queryFn: () => fetchComments(posts[0]?.id),
    enabled: !!posts[0]?.id, // Only run when we have a post ID
  })

  if (postsLoading) return <div className="p-4">Loading posts...</div>
  if (postsError) return <div className="p-4 text-red-500">Error loading posts</div>

  return (
    <div className="p-6 border rounded-lg">
      <h2 className="text-2xl font-bold mb-4">Posts and Comments</h2>

      <div className="mb-6">
        <h3 className="text-xl font-semibold mb-3">User Posts</h3>
        <div className="space-y-3">
          {posts.slice(0, 3).map((post) => (
            <div key={post.id} className="p-3 border rounded">
              <h4 className="font-medium">{post.title}</h4>
              <p className="text-gray-600 text-sm mt-1">{post.body.slice(0, 100)}...</p>
            </div>
          ))}
        </div>
      </div>

      {commentsLoading ? (
        <div>Loading comments...</div>
      ) : commentsError ? (
        <div className="text-red-500">Error loading comments</div>
      ) : (
        <div>
          <h3 className="text-xl font-semibold mb-3">Comments for First Post</h3>
          <div className="space-y-2">
            {comments.slice(0, 3).map((comment) => (
              <div key={comment.id} className="p-3 bg-gray-50 rounded">
                <div className="font-medium">{comment.name}</div>
                <div className="text-sm text-gray-600">{comment.email}</div>
                <div className="text-sm mt-1">{comment.body}</div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  )
}

// 5. Pagination with Query Keys
function PaginatedComponent() {
  const [page, setPage] = useState(1)
  const pageSize = 10

  const {
    data,
    isLoading,
    isError,
    error,
  } = useQuery({
    queryKey: ['items', page, pageSize],
    queryFn: async () => {
      const response = await fetch(
        `https://jsonplaceholder.typicode.com/todos?_page=${page}&_limit=${pageSize}`
      )
      if (!response.ok) throw new Error('Failed to fetch data')
      return response.json()
    },
    keepPreviousData: true, // Keep previous data while loading new page
  })

  return (
    <div className="p-6 border rounded-lg">
      <h2 className="text-2xl font-bold mb-4">Paginated Todos</h2>

      {isLoading && <div>Loading...</div>}
      {isError && <div className="text-red-500">Error: {error instanceof Error ? error.message : 'Unknown error'}</div>}

      {data && (
        <>
          <ul className="space-y-2 mb-4">
            {data.map((item: any) => (
              <li key={item.id} className="p-2 border rounded">
                {item.title}
              </li>
            ))}
          </ul>

          <div className="flex gap-2">
            <button
              onClick={() => setPage(prev => Math.max(1, prev - 1))}
              disabled={page === 1}
              className="px-3 py-1 bg-gray-500 text-white rounded disabled:opacity-50"
            >
              Previous
            </button>
            <span className="px-3 py-1">Page {page}</span>
            <button
              onClick={() => setPage(prev => prev + 1)}
              className="px-3 py-1 bg-blue-500 text-white rounded"
            >
              Next
            </button>
          </div>
        </>
      )}
    </div>
  )
}

export { App, UserProfile, TodoList, PostList, PaginatedComponent }
Code sourcetypescriptIntermédiaire

Patterns Avancés TanStack Query

Patterns avancés TanStack Query incluant mises à jour optimistes, requêtes infinies, rechargement en arrière-plan et hooks personnalisés

Difficulté

7/10

Temps estimé

45 min

Étiquettes

tanstack-query, advanced, optimistic, infinite, realtime

Prérequis

TanStack Query basics, React hooks, TypeScript, Advanced patterns

typescript
// TanStack Query Advanced Patterns
// Advanced data fetching patterns and optimizations

import {
  useQuery,
  useMutation,
  useQueryClient,
  useInfiniteQuery,
  QueryClient,
  QueryClientProvider,
  useQueries,
  QueryObserver,
} from '@tanstack/react-query'
import { useState, useEffect } from 'react'

// 1. Optimistic Updates
interface Todo {
  id: number
  title: string
  completed: boolean
  userId: number
  createdAt: string
}

const fetchTodos = async (): Promise<Todo[]> => {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos')
  if (!response.ok) throw new Error('Failed to fetch todos')
  return response.json()
}

const updateTodo = async (id: number, updates: Partial<Todo>): Promise<Todo> => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(updates),
  })
  if (!response.ok) throw new Error('Failed to update todo')
  return response.json()
}

const deleteTodo = async (id: number): Promise<void> => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`, {
    method: 'DELETE',
  })
  if (!response.ok) throw new Error('Failed to delete todo')
}

function OptimisticTodoList() {
  const queryClient = useQueryClient()

  const {
    data: todos = [],
    isLoading,
  } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })

  const updateTodoMutation = useMutation({
    mutationFn: ({ id, updates }: { id: number; updates: Partial<Todo> }) =>
      updateTodo(id, updates),
    onMutate: async ({ id, updates }) => {
      // Cancel any outgoing refetches
      await queryClient.cancelQueries({ queryKey: ['todos'] })

      // Snapshot the previous value
      const previousTodos = queryClient.getQueryData<Todo[]>(['todos'])

      // Optimistically update to the new value
      queryClient.setQueryData<Todo[]>(['todos'], (old = []) =>
        old.map(todo =>
          todo.id === id ? { ...todo, ...updates } : todo
        )
      )

      // Return a context object with the snapshotted value
      return { previousTodos }
    },
    onError: (error, variables, context) => {
      // If the mutation fails, use the context returned from onMutate
      if (context?.previousTodos) {
        queryClient.setQueryData(['todos'], context.previousTodos)
      }
    },
    onSettled: () => {
      // Always refetch after error or success
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })

  const deleteTodoMutation = useMutation({
    mutationFn: deleteTodo,
    onMutate: async (id) => {
      await queryClient.cancelQueries({ queryKey: ['todos'] })
      const previousTodos = queryClient.getQueryData<Todo[]>(['todos'])

      queryClient.setQueryData<Todo[]>(['todos'], (old = []) =>
        old.filter(todo => todo.id !== id)
      )

      return { previousTodos }
    },
    onError: (error, id, context) => {
      if (context?.previousTodos) {
        queryClient.setQueryData(['todos'], context.previousTodos)
      }
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })

  const handleToggleTodo = (todo: Todo) => {
    updateTodoMutation.mutate({
      id: todo.id,
      updates: { completed: !todo.completed },
    })
  }

  const handleDeleteTodo = (id: number) => {
    deleteTodoMutation.mutate(id)
  }

  if (isLoading) return <div>Loading...</div>

  return (
    <div className="p-6 border rounded-lg">
      <h2 className="text-2xl font-bold mb-4">Optimistic Todo List</h2>
      <p className="text-gray-600 mb-4">Changes appear instantly and rollback on errors</p>

      <ul className="space-y-2">
        {todos.slice(0, 10).map((todo) => (
          <li
            key={todo.id}
            className={`flex items-center gap-2 p-3 border rounded ${
              updateTodoMutation.isPending && updateTodoMutation.variables?.id === todo.id
                ? 'opacity-50'
                : ''
            }`}
          >
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => handleToggleTodo(todo)}
              disabled={updateTodoMutation.isPending}
              className="w-4 h-4"
            />
            <span className={todo.completed ? 'line-through text-gray-500' : ''}>
              {todo.title}
            </span>
            <button
              onClick={() => handleDeleteTodo(todo.id)}
              disabled={deleteTodoMutation.isPending}
              className="ml-auto px-2 py-1 text-sm bg-red-500 text-white rounded"
            >
              {deleteTodoMutation.isPending ? 'Deleting...' : 'Delete'}
            </button>
          </li>
        ))}
      </ul>
    </div>
  )
}

// 2. Infinite Scroll with useInfiniteQuery
interface Post {
  id: number
  title: string
  body: string
  userId: number
}

const fetchPostsPage = async ({ pageParam = 1 }): Promise<{
  data: Post[]
  page: number
  nextPage: number | null
}> => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts?_page=${pageParam}&_limit=10`
  )

  if (!response.ok) throw new Error('Failed to fetch posts')

  const posts = await response.json()
  return {
    data: posts,
    page: pageParam,
    nextPage: posts.length === 10 ? pageParam + 1 : null,
  }
}

function InfinitePostList() {
  const {
    data,
    isLoading,
    isError,
    error,
    hasNextPage,
    isFetchingNextPage,
    fetchNextPage,
  } = useInfiniteQuery({
    queryKey: ['posts-infinite'],
    queryFn: fetchPostsPage,
    getNextPageParam: (lastPage) => lastPage.nextPage,
    initialPageParam: 1,
  })

  const posts = data?.pages.flatMap(page => page.data) ?? []

  return (
    <div className="p-6 border rounded-lg">
      <h2 className="text-2xl font-bold mb-4">Infinite Scroll Posts</h2>

      {isLoading && <div>Loading initial posts...</div>}
      {isError && <div className="text-red-500">Error: {error instanceof Error ? error.message : 'Unknown error'}</div>}

      <div className="space-y-4">
        {posts.map((post) => (
          <div key={post.id} className="p-4 border rounded">
            <h3 className="font-semibold text-lg">{post.title}</h3>
            <p className="text-gray-600 mt-2">{post.body}</p>
          </div>
        ))}
      </div>

      <div className="mt-6 text-center">
        <button
          onClick={() => fetchNextPage()}
          disabled={!hasNextPage || isFetchingNextPage}
          className="px-6 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
        >
          {isFetchingNextPage
            ? 'Loading more...'
            : hasNextPage
            ? 'Load More'
            : 'No more posts'
          }
        </button>
      </div>
    </div>
  )
}

// 3. Multiple Queries with useQueries
function UserDashboard() {
  const [userId] = useState(1)

  const results = useQueries({
    queries: [
      {
        queryKey: ['user', userId],
        queryFn: async () => {
          const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
          if (!response.ok) throw new Error('Failed to fetch user')
          return response.json()
        },
        staleTime: 1000 * 60 * 5,
      },
      {
        queryKey: ['posts', userId],
        queryFn: async () => {
          const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}/posts`)
          if (!response.ok) throw new Error('Failed to fetch posts')
          return response.json()
        },
        staleTime: 1000 * 60 * 5,
      },
      {
        queryKey: ['todos', userId],
        queryFn: async () => {
          const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}/todos`)
          if (!response.ok) throw new Error('Failed to fetch todos')
          return response.json()
        },
        staleTime: 1000 * 60 * 5,
      },
    ],
  })

  const [userResult, postsResult, todosResult] = results
  const isLoading = results.some(result => result.isLoading)
  const isError = results.some(result => result.isError)

  if (isLoading) return <div className="p-6">Loading dashboard...</div>
  if (isError) return <div className="p-6 text-red-500">Error loading dashboard data</div>

  const user = userResult.data
  const posts = postsResult.data ?? []
  const todos = todosResult.data ?? []

  return (
    <div className="p-6 border rounded-lg">
      <h2 className="text-2xl font-bold mb-4">User Dashboard</h2>

      {user && (
        <div className="mb-6">
          <h3 className="text-xl font-semibold">{user.name}</h3>
          <p className="text-gray-600">@{user.username}</p>
          <p className="text-gray-800">{user.email}</p>
        </div>
      )}

      <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
        <div>
          <h4 className="font-semibold mb-3">Recent Posts ({posts.length})</h4>
          <div className="space-y-2">
            {posts.slice(0, 3).map((post: any) => (
              <div key={post.id} className="p-3 bg-gray-50 rounded">
                <h5 className="font-medium">{post.title}</h5>
                <p className="text-sm text-gray-600 mt-1">{post.body.slice(0, 80)}...</p>
              </div>
            ))}
          </div>
        </div>

        <div>
          <h4 className="font-semibold mb-3">Todo Status</h4>
          <div className="space-y-2">
            <div className="flex justify-between p-3 bg-green-50 rounded">
              <span>Completed</span>
              <span className="font-medium">
                {todos.filter((todo: any) => todo.completed).length}
              </span>
            </div>
            <div className="flex justify-between p-3 bg-yellow-50 rounded">
              <span>Pending</span>
              <span className="font-medium">
                {todos.filter((todo: any) => !todo.completed).length}
              </span>
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

// 4. Custom Hook Pattern
// Create a custom hook for user-related queries
function useUser(userId: number) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: async () => {
      const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
      if (!response.ok) throw new Error('Failed to fetch user')
      return response.json()
    },
    enabled: !!userId,
    staleTime: 1000 * 60 * 10, // 10 minutes
  })
}

function useUserPosts(userId: number) {
  return useQuery({
    queryKey: ['user-posts', userId],
    queryFn: async () => {
      const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}/posts`)
      if (!response.ok) throw new Error('Failed to fetch user posts')
      return response.json()
    },
    enabled: !!userId,
    staleTime: 1000 * 60 * 5, // 5 minutes
  })
}

function useCreatePost() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: async (newPost: { title: string; body: string; userId: number }) => {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newPost),
      })
      if (!response.ok) throw new Error('Failed to create post')
      return response.json()
    },
    onSuccess: (newPost, variables) => {
      // Invalidate user posts query to refetch
      queryClient.invalidateQueries({
        queryKey: ['user-posts', variables.userId]
      })
    },
  })
}

// Component using custom hooks
function UserProfileWithPosts() {
  const [userId] = useState(1)
  const [showCreateForm, setShowCreateForm] = useState(false)

  const { data: user, isLoading: userLoading, error: userError } = useUser(userId)
  const { data: posts, isLoading: postsLoading, error: postsError } = useUserPosts(userId)
  const createPostMutation = useCreatePost()

  if (userLoading || postsLoading) return <div>Loading...</div>
  if (userError || postsError) return <div>Error loading data</div>

  return (
    <div className="p-6 border rounded-lg">
      <h2 className="text-2xl font-bold mb-4">User Profile with Posts</h2>

      {user && (
        <div className="mb-6">
          <h3 className="text-xl font-semibold">{user.name}</h3>
          <p className="text-gray-600">{user.email}</p>
        </div>
      )}

      <div className="mb-4">
        <button
          onClick={() => setShowCreateForm(!showCreateForm)}
          className="px-4 py-2 bg-blue-500 text-white rounded"
        >
          {showCreateForm ? 'Hide Form' : 'Create New Post'}
        </button>
      </div>

      {showCreateForm && (
        <form
          onSubmit={(e) => {
            e.preventDefault()
            const formData = new FormData(e.currentTarget)
            createPostMutation.mutate({
              title: formData.get('title') as string,
              body: formData.get('body') as string,
              userId,
            })
          }}
          className="mb-6 p-4 border rounded bg-gray-50"
        >
          <div className="mb-3">
            <label className="block text-sm font-medium mb-1">Title</label>
            <input
              name="title"
              type="text"
              required
              className="w-full px-3 py-2 border rounded"
              disabled={createPostMutation.isPending}
            />
          </div>
          <div className="mb-3">
            <label className="block text-sm font-medium mb-1">Body</label>
            <textarea
              name="body"
              required
              rows={3}
              className="w-full px-3 py-2 border rounded"
              disabled={createPostMutation.isPending}
            />
          </div>
          <button
            type="submit"
            disabled={createPostMutation.isPending}
            className="px-4 py-2 bg-green-500 text-white rounded disabled:opacity-50"
          >
            {createPostMutation.isPending ? 'Creating...' : 'Create Post'}
          </button>
        </form>
      )}

      <div>
        <h4 className="font-semibold mb-3">Posts ({posts?.length || 0})</h4>
        <div className="space-y-3">
          {posts?.map((post: any) => (
            <div key={post.id} className="p-3 border rounded">
              <h5 className="font-medium">{post.title}</h5>
              <p className="text-gray-600 text-sm mt-1">{post.body}</p>
            </div>
          ))}
        </div>
      </div>
    </div>
  )
}

// 5. Background Refetching and Window Focus
function BackgroundRefetchExample() {
  const [enabled, setEnabled] = useState(true)
  const [refetchInterval, setRefetchInterval] = useState(5000) // 5 seconds

  const {
    data: todos = [],
    isLoading,
    error,
  } = useQuery({
    queryKey: ['todos', 'background'],
    queryFn: fetchTodos,
    refetchInterval: enabled ? refetchInterval : false,
    refetchIntervalInBackground: true,
    refetchOnWindowFocus: enabled,
    staleTime: 1000 * 30, // 30 seconds
  })

  return (
    <div className="p-6 border rounded-lg">
      <h2 className="text-2xl font-bold mb-4">Background Refetching</h2>

      <div className="mb-4 space-y-2">
        <label className="flex items-center gap-2">
          <input
            type="checkbox"
            checked={enabled}
            onChange={(e) => setEnabled(e.target.checked)}
          />
          <span>Enable background refetching</span>
        </label>

        <div>
          <label className="block text-sm font-medium mb-1">
            Refetch Interval (ms): {refetchInterval}
          </label>
          <input
            type="range"
            min="1000"
            max="30000"
            step="1000"
            value={refetchInterval}
            onChange={(e) => setRefetchInterval(Number(e.target.value))}
            className="w-full"
          />
        </div>
      </div>

      {isLoading && <div>Loading...</div>}
      {error && <div className="text-red-500">Error loading data</div>}

      <div>
        <p className="text-sm text-gray-600 mb-2">
          Last updated: {new Date().toLocaleTimeString()}
        </p>
        <div className="text-sm text-gray-600 mb-4">
          Total todos: {todos.length}
        </div>

        <div className="space-y-2">
          {todos.slice(0, 5).map((todo: any) => (
            <div key={todo.id} className="flex items-center gap-2 p-2 border rounded">
              <input
                type="checkbox"
                checked={todo.completed}
                readOnly
                className="w-4 h-4"
              />
              <span className={todo.completed ? 'line-through text-gray-500' : ''}>
                {todo.title}
              </span>
            </div>
          ))}
        </div>
      </div>
    </div>
  )
}

// 6. Query Observer for Manual Subscriptions
function useRealTimeData(queryKey: string[], queryFn: () => Promise<any>) {
  const [data, setData] = useState<any>(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<Error | null>(null)

  useEffect(() => {
    const queryClient = new QueryClient()

    const observer = new QueryObserver(queryClient, {
      queryKey,
      queryFn,
      refetchInterval: 5000, // Refetch every 5 seconds
    })

    const subscription = observer.subscribe((result) => {
      setLoading(result.isLoading)
      setError(result.error as Error | null)
      setData(result.data)
    })

    return () => {
      subscription.unsubscribe()
    }
  }, [queryKey.toString()])

  return { data, loading, error }
}

function RealTimeDataComponent() {
  const { data, loading, error } = useRealTimeData(
    ['realtime-todos'],
    fetchTodos
  )

  return (
    <div className="p-6 border rounded-lg">
      <h2 className="text-2xl font-bold mb-4">Real-time Data</h2>
      <p className="text-gray-600 mb-4">Updates every 5 seconds</p>

      {loading && <div>Loading...</div>}
      {error && <div className="text-red-500">Error: {error.message}</div>}

      {data && (
        <div>
          <div className="text-sm text-gray-600 mb-2">
            Total todos: {data.length}
          </div>
          <div className="text-sm text-green-600 mb-4">
            Last update: {new Date().toLocaleTimeString()}
          </div>
        </div>
      )}
    </div>
  )
}

export {
  OptimisticTodoList,
  InfinitePostList,
  UserDashboard,
  UserProfileWithPosts,
  BackgroundRefetchExample,
  RealTimeDataComponent,
}
Code sourcetypescriptComplexe

Gestion de Données TanStack Query

Patterns complets de gestion de données incluant stratégies de cache, synchronisation de données, limites d'erreur et optimisation des performances

Difficulté

9/10

Temps estimé

60 min

Étiquettes

tanstack-query, data-management, performance, optimization

Prérequis

Advanced TanStack Query, React patterns, TypeScript, State management

typescript
// TanStack Query Advanced Data Management
// Comprehensive data management patterns and optimizations

import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
  useQueries,
  QueryObserver,
  QueryCache,
  MutationCache,
} from '@tanstack/react-query'
import { useState, useEffect, useCallback, useMemo, createContext, useContext } from 'react'

// 1. Advanced Query Client Configuration
const createAdvancedQueryClient = () => {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 1000 * 60 * 5, // 5 minutes
        cacheTime: 1000 * 60 * 10, // 10 minutes
        retry: (failureCount, error) => {
          // Don't retry on 4xx errors
          if (error && typeof error === 'object' && 'status' in error) {
            const status = (error as any).status
            if (status >= 400 && status < 500) {
              return false
            }
          }
          return failureCount < 3
        },
        retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
        refetchOnWindowFocus: false,
        refetchOnReconnect: true,
        notifyOnChangeProps: ['data', 'error', 'isLoading'],
      },
      mutations: {
        retry: 1,
        onMutate: () => {
          // Show loading indicator globally
        },
        onSuccess: () => {
          // Show success notification
        },
        onError: () => {
          // Show error notification
        },
      },
    },
    queryCache: new QueryCache({
      onError: (error, query) => {
        // Global query error handler
        console.error('Query error:', error, 'Query:', query.queryKey)
      },
      onSuccess: (data, query) => {
        // Global query success handler
        console.log('Query success:', query.queryKey)
      },
    }),
    mutationCache: new MutationCache({
      onError: (error, variables, context, mutation) => {
        // Global mutation error handler
        console.error('Mutation error:', error)
      },
    }),
  })
}

// 2. Data Context for Global State Management
interface DataContextType {
  invalidateQueries: (queryKey: string[]) => void
  setQueryData: (queryKey: string[], data: any) => void
  getQueryData: (queryKey: string[]) => any
  prefetchQuery: (queryKey: string[], queryFn: () => Promise<any>) => void
}

const DataContext = createContext<DataDataContextType | null>(null)

export function DataProvider({ children }: { children: React.ReactNode }) {
  const queryClient = useQueryClient()

  const invalidateQueries = useCallback((queryKey: string[]) => {
    queryClient.invalidateQueries({ queryKey })
  }, [queryClient])

  const setQueryData = useCallback((queryKey: string[], data: any) => {
    queryClient.setQueryData(queryKey, data)
  }, [queryClient])

  const getQueryData = useCallback((queryKey: string[]) => {
    return queryClient.getQueryData(queryKey)
  }, [queryClient])

  const prefetchQuery = useCallback((queryKey: string[], queryFn: () => Promise<any>) => {
    queryClient.prefetchQuery({ queryKey, queryFn })
  }, [queryClient])

  const value = useMemo(() => ({
    invalidateQueries,
    setQueryData,
    getQueryData,
    prefetchQuery,
  }), [invalidateQueries, setQueryData, getQueryData, prefetchQuery])

  return (
    <DataContext.Provider value={value}>
      {children}
    </DataContext.Provider>
  )
}

export function useDataContext() {
  const context = useContext(DataContext)
  if (!context) {
    throw new Error('useDataContext must be used within a DataProvider')
  }
  return context
}

// 3. Smart Data Fetching Hook with Caching
interface UseSmartFetchOptions {
  enabled?: boolean
  staleTime?: number
  cacheTime?: number
  refetchOnWindowFocus?: boolean
  retry?: boolean | number
}

function useSmartFetch<T>(
  queryKey: string[],
  queryFn: () => Promise<T>,
  options: UseSmartFetchOptions = {}
) {
  const {
    enabled = true,
    staleTime = 1000 * 60 * 5,
    cacheTime = 1000 * 60 * 10,
    refetchOnWindowFocus = false,
    retry = 3,
  } = options

  return useQuery({
    queryKey,
    queryFn,
    enabled,
    staleTime,
    cacheTime,
    refetchOnWindowFocus,
    retry,
    select: useCallback((data: T) => {
      // Transform data if needed
      return data
    }, []),
  })
}

// 4. Offline-aware Data Management
function useOfflineAwareQuery<T>(
  queryKey: string[],
  queryFn: () => Promise<T>,
  options: any = {}
) {
  const [isOnline, setIsOnline] = useState(navigator.onLine)

  useEffect(() => {
    const handleOnline = () => setIsOnline(true)
    const handleOffline = () => setIsOnline(false)

    window.addEventListener('online', handleOnline)
    window.addEventListener('offline', handleOffline)

    return () => {
      window.removeEventListener('online', handleOnline)
      window.removeEventListener('offline', handleOffline)
    }
  }, [])

  return useQuery({
    queryKey,
    queryFn,
    enabled: isOnline && (options.enabled ?? true),
    retry: (failureCount, error) => {
      // Retry more aggressively when coming back online
      if (isOnline && failureCount < 5) {
        return true
      }
      return false
    },
    refetchOnWindowFocus: isOnline,
    ...options,
  })
}

// 5. Data Synchronization Patterns
class DataSyncManager {
  private queryClient: QueryClient
  private syncQueue: Array<() => Promise<void>> = []
  private isSyncing = false

  constructor(queryClient: QueryClient) {
    this.queryClient = queryClient
    this.setupOnlineListener()
  }

  private setupOnlineListener() {
    window.addEventListener('online', () => {
      this.processSyncQueue()
    })
  }

  addToSyncQueue(syncFunction: () => Promise<void>) {
    this.syncQueue.push(syncFunction)
    if (navigator.onLine && !this.isSyncing) {
      this.processSyncQueue()
    }
  }

  private async processSyncQueue() {
    if (this.isSyncing || this.syncQueue.length === 0) return

    this.isSyncing = true
    console.log('Starting sync process...')

    while (this.syncQueue.length > 0) {
      const syncFunction = this.syncQueue.shift()
      try {
        await syncFunction()
      } catch (error) {
        console.error('Sync operation failed:', error)
        // Re-queue failed operations
        if (syncFunction) {
          this.syncQueue.push(syncFunction)
        }
      }
    }

    this.isSyncing = false
    console.log('Sync process completed')
  }

  async forceSync() {
    await this.processSyncQueue()
  }
}

// 6. Cache Management Utilities
function useCacheManager() {
  const queryClient = useQueryClient()

  const clearCache = useCallback(() => {
    queryClient.clear()
  }, [queryClient])

  const clearSpecificCache = useCallback((queryKey: string[]) => {
    queryClient.removeQueries({ queryKey })
  }, [queryClient])

  const getCacheSize = useCallback(() => {
    return queryClient.getQueryCache().getAll().length
  }, [queryClient])

  const prefetchMultipleQueries = useCallback((queries: Array<{
    queryKey: string[]
    queryFn: () => Promise<any>
  }>) => {
    queries.forEach(({ queryKey, queryFn }) => {
      queryClient.prefetchQuery({ queryKey, queryFn })
    })
  }, [queryClient])

  return {
    clearCache,
    clearSpecificCache,
    getCacheSize,
    prefetchMultipleQueries,
  }
}

// 7. Advanced Error Handling with Retry Logic
function useResilientQuery<T>(
  queryKey: string[],
  queryFn: () => Promise<T>,
  options: {
    maxRetries?: number
    retryDelay?: number
    fallbackData?: T
    exponentialBackoff?: boolean
  } = {}
) {
  const {
    maxRetries = 3,
    retryDelay = 1000,
    fallbackData,
    exponentialBackoff = true,
  } = options

  return useQuery({
    queryKey,
    queryFn,
    retry: (failureCount, error: any) => {
      if (failureCount >= maxRetries) return false

      // Don't retry certain error types
      if (error?.status === 401 || error?.status === 403) {
        return false
      }

      return true
    },
    retryDelay: (attemptIndex) => {
      if (exponentialBackoff) {
        return Math.min(1000 * 2 ** attemptIndex, 30000)
      }
      return retryDelay
    },
    onError: (error) => {
      console.error('Query failed:', error)
    },
    fallbackData,
  })
}

// 8. Data Validation and Type Safety
function useValidatedQuery<T, R>(
  queryKey: string[],
  queryFn: () => Promise<T>,
  validator: (data: unknown) => R,
  options: any = {}
) {
  return useQuery({
    queryKey,
    queryFn: async () => {
      try {
        const data = await queryFn()
        return validator(data)
      } catch (error) {
        throw new Error(`Data validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
      }
    },
    ...options,
  })
}

// 9. Memory-efficient Data Management
function usePaginatedData<T>(
  baseQueryKey: string[],
  fetchPage: (page: number) => Promise<{ data: T[]; totalPages: number }>,
  options: {
    pageSize?: number
    cachePages?: number
  } = {}
) {
  const [currentPage, setCurrentPage] = useState(1)
  const { pageSize = 20, cachePages = 3 } = options

  const queryClient = useQueryClient()

  const {
    data,
    isLoading,
    error,
  } = useQuery({
    queryKey: [...baseQueryKey, 'page', currentPage],
    queryFn: () => fetchPage(currentPage),
    keepPreviousData: true,
    staleTime: 1000 * 60 * 5,
  })

  // Prefetch next and previous pages
  useEffect(() => {
    if (data && currentPage < data.totalPages) {
      queryClient.prefetchQuery({
        queryKey: [...baseQueryKey, 'page', currentPage + 1],
        queryFn: () => fetchPage(currentPage + 1),
      })
    }

    if (currentPage > 1) {
      queryClient.prefetchQuery({
        queryKey: [...baseQueryKey, 'page', currentPage - 1],
        queryFn: () => fetchPage(currentPage - 1),
      })
    }
  }, [currentPage, data, baseQueryKey, queryClient])

  return {
    data: data?.data ?? [],
    totalPages: data?.totalPages ?? 0,
    currentPage,
    setCurrentPage,
    isLoading,
    error,
  }
}

// 10. Real-time Data Integration
function useRealtimeSubscription<T>(
  queryKey: string[],
  initialData: T,
  subscribeToUpdates: (callback: (data: T) => void) => () => void
) {
  const queryClient = useQueryClient()

  const { data, isLoading, error } = useQuery({
    queryKey,
    queryFn: () => Promise.resolve(initialData),
    staleTime: Infinity, // Never stale, updated via subscription
  })

  useEffect(() => {
    const unsubscribe = subscribeToUpdates((newData) => {
      queryClient.setQueryData(queryKey, newData)
    })

    return unsubscribe
  }, [queryKey, queryClient, subscribeToUpdates])

  return { data, isLoading, error }
}

// 11. Comprehensive Example: E-commerce Product Management
interface Product {
  id: string
  name: string
  price: number
  description: string
  category: string
  stock: number
  images: string[]
  reviews: Review[]
}

interface Review {
  id: string
  productId: string
  userId: string
  rating: number
  comment: string
  createdAt: string
}

function ProductManagementApp() {
  const queryClient = useQueryClient()
  const [selectedCategory, setSelectedCategory] = useState<string>('all')
  const [searchTerm, setSearchTerm] = useState('')
  const [sortBy, setSortBy] = useState<'name' | 'price' | 'rating'>('name')

  // Fetch products with filters
  const {
    data: products = [],
    isLoading: productsLoading,
    error: productsError,
  } = useQuery({
    queryKey: ['products', { category: selectedCategory, searchTerm, sortBy }],
    queryFn: async () => {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 500))

      let filteredProducts = [
        // Mock data
        { id: '1', name: 'Laptop', price: 999, description: 'High-performance laptop', category: 'electronics', stock: 10, images: [], reviews: [] },
        { id: '2', name: 'Phone', price: 699, description: 'Smartphone', category: 'electronics', stock: 25, images: [], reviews: [] },
        { id: '3', name: 'Book', price: 19, description: 'Good book', category: 'books', stock: 100, images: [], reviews: [] },
      ]

      if (selectedCategory !== 'all') {
        filteredProducts = filteredProducts.filter(p => p.category === selectedCategory)
      }

      if (searchTerm) {
        filteredProducts = filteredProducts.filter(p =>
          p.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
          p.description.toLowerCase().includes(searchTerm.toLowerCase())
        )
      }

      return filteredProducts.sort((a, b) => {
        switch (sortBy) {
          case 'price':
            return a.price - b.price
          case 'name':
            return a.name.localeCompare(b.name)
          default:
            return 0
        }
      })
    },
    staleTime: 1000 * 60 * 5,
  })

  // Create product mutation with optimistic update
  const createProductMutation = useMutation({
    mutationFn: async (newProduct: Omit<Product, 'id' | 'reviews'>) => {
      const response = await fetch('/api/products', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newProduct),
      })
      if (!response.ok) throw new Error('Failed to create product')
      return response.json()
    },
    onMutate: async (newProduct) => {
      await queryClient.cancelQueries({ queryKey: ['products'] })

      const previousProducts = queryClient.getQueryData<Product[]>(['products'])

      const optimisticProduct: Product = {
        ...newProduct,
        id: 'temp-' + Date.now(),
        reviews: [],
      }

      queryClient.setQueryData<Product[]>(['products'], (old = []) => [
        optimisticProduct,
        ...old,
      ])

      return { previousProducts }
    },
    onError: (error, variables, context) => {
      if (context?.previousProducts) {
        queryClient.setQueryData(['products'], context.previousProducts)
      }
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['products'] })
    },
  })

  // Update product mutation
  const updateProductMutation = useMutation({
    mutationFn: async ({ id, updates }: { id: string; updates: Partial<Product> }) => {
      const response = await fetch(`/api/products/${id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updates),
      })
      if (!response.ok) throw new Error('Failed to update product')
      return response.json()
    },
    onMutate: async ({ id, updates }) => {
      await queryClient.cancelQueries({ queryKey: ['products'] })

      const previousProducts = queryClient.getQueryData<Product[]>(['products'])

      queryClient.setQueryData<Product[]>(['products'], (old = []) =>
        old.map(product =>
          product.id === id ? { ...product, ...updates } : product
        )
      )

      return { previousProducts }
    },
    onError: (error, variables, context) => {
      if (context?.previousProducts) {
        queryClient.setQueryData(['products'], context.previousProducts)
      }
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['products'] })
    },
  })

  return (
    <div className="p-6 border rounded-lg">
      <h2 className="text-2xl font-bold mb-4">Product Management</h2>

      {/* Filters and Search */}
      <div className="mb-6 grid grid-cols-1 md:grid-cols-4 gap-4">
        <input
          type="text"
          placeholder="Search products..."
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
          className="px-3 py-2 border rounded"
        />

        <select
          value={selectedCategory}
          onChange={(e) => setSelectedCategory(e.target.value)}
          className="px-3 py-2 border rounded"
        >
          <option value="all">All Categories</option>
          <option value="electronics">Electronics</option>
          <option value="books">Books</option>
        </select>

        <select
          value={sortBy}
          onChange={(e) => setSortBy(e.target.value as any)}
          className="px-3 py-2 border rounded"
        >
          <option value="name">Sort by Name</option>
          <option value="price">Sort by Price</option>
          <option value="rating">Sort by Rating</option>
        </select>

        <button
          onClick={() => createProductMutation.mutate({
            name: 'New Product',
            price: 99,
            description: 'New product description',
            category: 'electronics',
            stock: 50,
            images: [],
          })}
          disabled={createProductMutation.isPending}
          className="px-4 py-2 bg-green-500 text-white rounded disabled:opacity-50"
        >
          Add Product
        </button>
      </div>

      {/* Products List */}
      {productsLoading ? (
        <div>Loading products...</div>
      ) : productsError ? (
        <div className="text-red-500">Error loading products</div>
      ) : (
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
          {products.map((product) => (
            <div key={product.id} className="border rounded p-4">
              <h3 className="font-semibold">{product.name}</h3>
              <p className="text-gray-600">{product.description}</p>
              <p className="text-lg font-bold">${product.price}</p>
              <p className="text-sm text-gray-500">Stock: {product.stock}</p>
              <p className="text-sm text-gray-500">Category: {product.category}</p>

              <div className="mt-2 space-x-2">
                <button
                  onClick={() => updateProductMutation.mutate({
                    id: product.id,
                    updates: { price: product.price + 10 }
                  })}
                  disabled={updateProductMutation.isPending}
                  className="px-2 py-1 text-sm bg-blue-500 text-white rounded"
                >
                  Update Price
                </button>
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  )
}

export {
  createAdvancedQueryClient,
  DataProvider,
  useDataContext,
  useSmartFetch,
  useOfflineAwareQuery,
  DataSyncManager,
  useCacheManager,
  useResilientQuery,
  useValidatedQuery,
  usePaginatedData,
  useRealtimeSubscription,
  ProductManagementApp,
}

Outils

Outils souvent utilisés avec cet exemple

Associé

Collections d’exemples associées

Guide d'utilisation de l'outil

Contexte pour comprendre cette collection d’exemples

Titre

Exemples TanStack Query

Description

Exemples TanStack Query (anciennement React Query) incluant la récupération de données, la mise en cache, les mutations, les mises à jour optimistes et les patterns avancés

Aperçu

Exemples TanStack Query (anciennement React Query) incluant la récupération de données, la mise en cache, les mutations, les mises à jour optimistes et les patterns avancés

Quand l’utiliser

  • Use this sample set when you want reference inputs for Exemples TanStack Query.
  • Use it to preview output formats and compare related sample variants.

Fonctionnement

  1. 1Open a sample entry to inspect the prepared example payload or asset.
  2. 2Compare it with related tools to understand how the workflow connects.

Cas d’usage

  • Utilisation de Base TanStack Query
  • Patterns Avancés TanStack Query
  • Gestion de Données TanStack Query

FAQ

What is included in Exemples TanStack Query?

This page includes curated sample entries, related tools, and neighboring sample collections for Exemples TanStack Query.

Accueil
Outils3197
Exemples593
Visualisations461
Visionneuses de fichiers103
Dossiers229
Étiquettes333
Images, audio et vidéo655

Outils de traitement, conversion et optimisation d’images, d’audio et de vidéo

Maths, dates et finance644

Calculateurs, outils numériques, logique de dates, statistiques et finance

Design et couleur296

Outils de couleur, mise en page, graphisme, style visuel et assistance design

Texte et rédaction209

Outils de rédaction, nettoyage, formatage, extraction et analyse de texte

Développement et Web193

Utilitaires développeur, réseau, débogage Web et assistants d’automatisation

Conversion et encodage175

Outils de conversion de formats, fichiers, archives, unités et encodages

Structurer une capture d'interface, la relire et la transmettre au développement
Esquisse au piano, contrôle MIDI et livraison d'une maquette d'écoute
Revue de textures rétro et remise des ressources
Revue et remise d’un guide opératoire illustré par captures
Revue visuelle d’une refonte responsive et remise des preuves

© 2026 Elysia Tools. Tous droits réservés.

DocumentationTarifsÀ proposConditions d'utilisationPolitique de confidentialitéCookiesContact

Development

Générateur de Requêtes SQL

Construire visuellement des requêtes SQL simples

Network

Outil de Requête DNS

Interrogez les enregistrements DNS et les informations de résolution de domaine incluant A, AAAA, MX, TXT, NS et CNAME

Science & Education

Affichage des Constantes Scientifiques

Affiche les constantes scientifiques avec langue et format de sortie personnalisables

Development

Analyseur de Paramètres URL

Analyse par lots les paramètres de requête URL avec sortie de fréquence des clés, échantillons de valeurs et détection d'anomalies d'encodage

Data Processing

Studio de requetes et transformations JSONata

Previsualise des requetes et transformations de style JSONata, compare les resultats et exporte en JSON, CSV, YAML ou Markdown.

Development

Outil de requete JSONPath

Interroge nimporte quel JSON avec JSONPath, previsualise les correspondances, change le format de sortie et enregistre des modeles reutilisables

Development

Formateur et minificateur SQL

Embellit ou minifie le SQL avec contrôle total du dialecte (PostgreSQL, MySQL, SQLite, SQL Server/T-SQL, BigQuery, Snowflake, etc.), de la casse des mots-clés/identifiants/fonctions, du style d’indentation (standard, tabulaire gauche/droite) et des lignes entre instructions. Produit un résultat coloré et prêt à copier avec des statistiques de taille et d’instructions.