From 240d2b9f98e61ff2ec8b9deae69503710e09e4de Mon Sep 17 00:00:00 2001 From: sidwebworks Date: Fri, 8 Apr 2022 22:15:36 +0530 Subject: [PATCH 1/6] feat(docs): added rtk-query kitchen-sink example --- examples/query/react/kitchen-sink/.env | 2 + .../query/react/kitchen-sink/package.json | 49 +++ .../react/kitchen-sink/public/index.html | 43 +++ .../react/kitchen-sink/public/manifest.json | 8 + .../kitchen-sink/public/mockServiceWorker.js | 338 ++++++++++++++++++ examples/query/react/kitchen-sink/src/App.css | 28 ++ examples/query/react/kitchen-sink/src/App.tsx | 37 ++ .../kitchen-sink/src/app/services/counter.ts | 43 +++ .../kitchen-sink/src/app/services/posts.ts | 109 ++++++ .../src/app/services/split/index.ts | 21 ++ .../src/app/services/split/post.ts | 40 +++ .../src/app/services/split/posts.ts | 11 + .../kitchen-sink/src/app/services/times.ts | 19 + .../query/react/kitchen-sink/src/app/store.ts | 37 ++ .../src/features/auth/authSlice.ts | 37 ++ .../src/features/bundleSplitting/Lazy.tsx | 10 + .../src/features/bundleSplitting/Post.tsx | 32 ++ .../features/bundleSplitting/PostsList.tsx | 35 ++ .../src/features/bundleSplitting/index.ts | 7 + .../src/features/common/Container.tsx | 5 + .../src/features/counter/Counter.module.css | 75 ++++ .../src/features/counter/Counter.tsx | 32 ++ .../src/features/counter/CounterList.tsx | 30 ++ .../src/features/polling/PollingToggles.tsx | 47 +++ .../src/features/polling/pollingSlice.ts | 67 ++++ .../src/features/posts/PostDetail.tsx | 105 ++++++ .../src/features/posts/PostsManager.css | 10 + .../src/features/posts/PostsManager.tsx | 111 ++++++ .../src/features/time/TimeList.tsx | 142 ++++++++ .../query/react/kitchen-sink/src/index.tsx | 26 ++ .../react/kitchen-sink/src/mocks/browser.ts | 4 + .../react/kitchen-sink/src/mocks/handlers.ts | 110 ++++++ .../kitchen-sink/src/mocks/mockServer.ts | 9 + .../kitchen-sink/src/mocks/setupTests.tsx | 37 ++ .../react/kitchen-sink/src/react-app-env.d.ts | 1 + .../react/kitchen-sink/src/setupTests.ts | 5 + .../query/react/kitchen-sink/src/styles.css | 4 + .../query/react/kitchen-sink/tsconfig.json | 25 ++ 38 files changed, 1751 insertions(+) create mode 100644 examples/query/react/kitchen-sink/.env create mode 100644 examples/query/react/kitchen-sink/package.json create mode 100644 examples/query/react/kitchen-sink/public/index.html create mode 100644 examples/query/react/kitchen-sink/public/manifest.json create mode 100644 examples/query/react/kitchen-sink/public/mockServiceWorker.js create mode 100644 examples/query/react/kitchen-sink/src/App.css create mode 100644 examples/query/react/kitchen-sink/src/App.tsx create mode 100644 examples/query/react/kitchen-sink/src/app/services/counter.ts create mode 100644 examples/query/react/kitchen-sink/src/app/services/posts.ts create mode 100644 examples/query/react/kitchen-sink/src/app/services/split/index.ts create mode 100644 examples/query/react/kitchen-sink/src/app/services/split/post.ts create mode 100644 examples/query/react/kitchen-sink/src/app/services/split/posts.ts create mode 100644 examples/query/react/kitchen-sink/src/app/services/times.ts create mode 100644 examples/query/react/kitchen-sink/src/app/store.ts create mode 100644 examples/query/react/kitchen-sink/src/features/auth/authSlice.ts create mode 100644 examples/query/react/kitchen-sink/src/features/bundleSplitting/Lazy.tsx create mode 100644 examples/query/react/kitchen-sink/src/features/bundleSplitting/Post.tsx create mode 100644 examples/query/react/kitchen-sink/src/features/bundleSplitting/PostsList.tsx create mode 100644 examples/query/react/kitchen-sink/src/features/bundleSplitting/index.ts create mode 100644 examples/query/react/kitchen-sink/src/features/common/Container.tsx create mode 100644 examples/query/react/kitchen-sink/src/features/counter/Counter.module.css create mode 100644 examples/query/react/kitchen-sink/src/features/counter/Counter.tsx create mode 100644 examples/query/react/kitchen-sink/src/features/counter/CounterList.tsx create mode 100644 examples/query/react/kitchen-sink/src/features/polling/PollingToggles.tsx create mode 100644 examples/query/react/kitchen-sink/src/features/polling/pollingSlice.ts create mode 100644 examples/query/react/kitchen-sink/src/features/posts/PostDetail.tsx create mode 100644 examples/query/react/kitchen-sink/src/features/posts/PostsManager.css create mode 100644 examples/query/react/kitchen-sink/src/features/posts/PostsManager.tsx create mode 100644 examples/query/react/kitchen-sink/src/features/time/TimeList.tsx create mode 100644 examples/query/react/kitchen-sink/src/index.tsx create mode 100644 examples/query/react/kitchen-sink/src/mocks/browser.ts create mode 100644 examples/query/react/kitchen-sink/src/mocks/handlers.ts create mode 100644 examples/query/react/kitchen-sink/src/mocks/mockServer.ts create mode 100644 examples/query/react/kitchen-sink/src/mocks/setupTests.tsx create mode 100644 examples/query/react/kitchen-sink/src/react-app-env.d.ts create mode 100644 examples/query/react/kitchen-sink/src/setupTests.ts create mode 100644 examples/query/react/kitchen-sink/src/styles.css create mode 100644 examples/query/react/kitchen-sink/tsconfig.json diff --git a/examples/query/react/kitchen-sink/.env b/examples/query/react/kitchen-sink/.env new file mode 100644 index 000000000..2260b43de --- /dev/null +++ b/examples/query/react/kitchen-sink/.env @@ -0,0 +1,2 @@ +SKIP_PREFLIGHT_CHECK=true +NODE_ENV=development \ No newline at end of file diff --git a/examples/query/react/kitchen-sink/package.json b/examples/query/react/kitchen-sink/package.json new file mode 100644 index 000000000..50189532f --- /dev/null +++ b/examples/query/react/kitchen-sink/package.json @@ -0,0 +1,49 @@ +{ + "name": "@examples-query-react/kitchen-sink", + "private": true, + "version": "1.0.0", + "description": "getting-started-hooks", + "keywords": [], + "main": "src/index.tsx", + "dependencies": { + "@reduxjs/toolkit": "workspace:^", + "react": "17.0.0", + "react-dom": "17.0.0", + "react-redux": "7.2.2", + "react-scripts": "4.0.2" + }, + "devDependencies": { + "@testing-library/jest-dom": "^5.11.5", + "@testing-library/react": "^12.0.0", + "@types/jest": "^26.0.23", + "@types/node": "^14.14.6", + "@types/react": "17.0.0", + "@types/react-dom": "17.0.0", + "@types/react-redux": "7.1.9", + "msw": "^0.39.2", + "typescript": "~4.2.4", + "whatwg-fetch": "^3.4.1" + }, + "eslintConfig": { + "extends": [ + "react-app" + ], + "rules": { + "react/react-in-jsx-scope": "off" + } + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --runInBand" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ], + "msw": { + "workerDirectory": "public" + } +} diff --git a/examples/query/react/kitchen-sink/public/index.html b/examples/query/react/kitchen-sink/public/index.html new file mode 100644 index 000000000..42ae2d2dc --- /dev/null +++ b/examples/query/react/kitchen-sink/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + React App + + + + +
+ + + + \ No newline at end of file diff --git a/examples/query/react/kitchen-sink/public/manifest.json b/examples/query/react/kitchen-sink/public/manifest.json new file mode 100644 index 000000000..fe404238d --- /dev/null +++ b/examples/query/react/kitchen-sink/public/manifest.json @@ -0,0 +1,8 @@ +{ + "short_name": "RTK Query Authentication using extraReducers Example", + "name": "Authentication Example", + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/query/react/kitchen-sink/public/mockServiceWorker.js b/examples/query/react/kitchen-sink/public/mockServiceWorker.js new file mode 100644 index 000000000..0966a9df4 --- /dev/null +++ b/examples/query/react/kitchen-sink/public/mockServiceWorker.js @@ -0,0 +1,338 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (0.39.2). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '02f4ad4a2797f85668baf196e553d929' +const bypassHeaderName = 'x-msw-bypass' +const activeClientIds = new Set() + +self.addEventListener('install', function () { + return self.skipWaiting() +}) + +self.addEventListener('activate', async function (event) { + return self.clients.claim() +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll() + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +// Resolve the "main" client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (client.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll() + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const clonedResponse = response.clone() + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: + clonedResponse.body === null ? null : await clonedResponse.text(), + headers: serializeHeaders(clonedResponse.headers), + redirected: clonedResponse.redirected, + }, + }) + })() + } + + return response +} + +async function getResponse(event, client, requestId) { + const { request } = event + const requestClone = request.clone() + const getOriginalResponse = () => fetch(requestClone) + + // Bypass mocking when the request client is not active. + if (!client) { + return getOriginalResponse() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return await getOriginalResponse() + } + + // Bypass requests with the explicit bypass header + if (requestClone.headers.get(bypassHeaderName) === 'true') { + const cleanRequestHeaders = serializeHeaders(requestClone.headers) + + // Remove the bypass header to comply with the CORS preflight check. + delete cleanRequestHeaders[bypassHeaderName] + + const originalRequest = new Request(requestClone, { + headers: new Headers(cleanRequestHeaders), + }) + + return fetch(originalRequest) + } + + // Send the request to the client-side MSW. + const reqHeaders = serializeHeaders(request.headers) + const body = await request.text() + + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: reqHeaders, + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body, + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }) + + switch (clientMessage.type) { + case 'MOCK_SUCCESS': { + return delayPromise( + () => respondWithMock(clientMessage), + clientMessage.payload.delay, + ) + } + + case 'MOCK_NOT_FOUND': { + return getOriginalResponse() + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.payload + const networkError = new Error(message) + networkError.name = name + + // Rejecting a request Promise emulates a network error. + throw networkError + } + + case 'INTERNAL_ERROR': { + const parsedBody = JSON.parse(clientMessage.payload.body) + + console.error( + `\ +[MSW] Uncaught exception in the request handler for "%s %s": + +${parsedBody.location} + +This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses\ +`, + request.method, + request.url, + ) + + return respondWithMock(clientMessage) + } + } + + return getOriginalResponse() +} + +self.addEventListener('fetch', function (event) { + const { request } = event + const accept = request.headers.get('accept') || '' + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return + } + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + const requestId = uuidv4() + + return event.respondWith( + handleRequest(event, requestId).catch((error) => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url, + ) + return + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}`, + ) + }), + ) +}) + +function serializeHeaders(headers) { + const reqHeaders = {} + headers.forEach((value, name) => { + reqHeaders[name] = reqHeaders[name] + ? [].concat(reqHeaders[name]).concat(value) + : value + }) + return reqHeaders +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(JSON.stringify(message), [channel.port2]) + }) +} + +function delayPromise(cb, duration) { + return new Promise((resolve) => { + setTimeout(() => resolve(cb()), duration) + }) +} + +function respondWithMock(clientMessage) { + return new Response(clientMessage.payload.body, { + ...clientMessage.payload, + headers: clientMessage.payload.headers, + }) +} + +function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0 + const v = c == 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +} diff --git a/examples/query/react/kitchen-sink/src/App.css b/examples/query/react/kitchen-sink/src/App.css new file mode 100644 index 000000000..eeb46d1ea --- /dev/null +++ b/examples/query/react/kitchen-sink/src/App.css @@ -0,0 +1,28 @@ +.row { + display: flex; + flex-wrap: wrap; + width: 100%; +} + +.column { + display: flex; + flex-direction: column; + flex-basis: 100%; + flex: 3; +} + +.column-3 { + flex: 3; +} + +.column-1 { + flex: 1; +} + +.column-2 { + flex: 2; +} + +.text-left { + text-align: left; +} diff --git a/examples/query/react/kitchen-sink/src/App.tsx b/examples/query/react/kitchen-sink/src/App.tsx new file mode 100644 index 000000000..6d34d4e0e --- /dev/null +++ b/examples/query/react/kitchen-sink/src/App.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Switch, Route, Link } from 'react-router-dom'; +import { PostsManager } from './features/posts/PostsManager'; +import { CounterList } from './features/counter/CounterList'; +import { TimeList } from './features/time/TimeList'; +import { PollingToggles } from './features/polling/PollingToggles'; +import { Lazy } from './features/bundleSplitting'; +import './App.css'; + +function App() { + return ( +
+
+
+ + Times | Posts | Counter |{' '} + Bundle Splitting + +
+
+ +
+
+
+
+ + + + + + +
+
+ ); +} + +export default App; diff --git a/examples/query/react/kitchen-sink/src/app/services/counter.ts b/examples/query/react/kitchen-sink/src/app/services/counter.ts new file mode 100644 index 000000000..cf2ed389c --- /dev/null +++ b/examples/query/react/kitchen-sink/src/app/services/counter.ts @@ -0,0 +1,43 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +interface CountResponse { + count: number +} + +export const counterApi = createApi({ + reducerPath: 'counterApi', + baseQuery: fetchBaseQuery(), + tagTypes: ['Counter'], + endpoints: (build) => ({ + getCount: build.query({ + query: () => 'count', + providesTags: ['Counter'], + }), + incrementCount: build.mutation({ + query(amount) { + return { + url: `increment`, + method: 'PUT', + body: { amount }, + } + }, + invalidatesTags: ['Counter'], + }), + decrementCount: build.mutation({ + query(amount) { + return { + url: `decrement`, + method: 'PUT', + body: { amount }, + } + }, + invalidatesTags: ['Counter'], + }), + }), +}) + +export const { + useDecrementCountMutation, + useGetCountQuery, + useIncrementCountMutation, +} = counterApi diff --git a/examples/query/react/kitchen-sink/src/app/services/posts.ts b/examples/query/react/kitchen-sink/src/app/services/posts.ts new file mode 100644 index 000000000..20786953d --- /dev/null +++ b/examples/query/react/kitchen-sink/src/app/services/posts.ts @@ -0,0 +1,109 @@ +import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react' +import { RootState } from '../store' + +export interface Post { + id: number + name: string + fetched_at: string +} + +type PostsResponse = Post[] + +export interface User { + first_name: string + last_name: string + email: string + phone: string +} + +// Create our baseQuery instance +const baseQuery = fetchBaseQuery({ + baseUrl: '/', + prepareHeaders: (headers, { getState }) => { + // By default, if we have a token in the store, let's use that for authenticated requests + const token = (getState() as RootState).auth.token + if (token) { + headers.set('authentication', `Bearer ${token}`) + } + return headers + }, +}) + +const baseQueryWithRetry = retry(baseQuery, { maxRetries: 6 }) + +export const postApi = createApi({ + reducerPath: 'postsApi', // We only specify this because there are many services. This would not be common in most applications + baseQuery: baseQueryWithRetry, + tagTypes: ['Posts'], + endpoints: (build) => ({ + login: build.mutation<{ token: string; user: User }, any>({ + query: (credentials: any) => ({ + url: 'login', + method: 'POST', + body: credentials, + }), + extraOptions: { + backoff: () => { + // We intentionally error once on login, and this breaks out of retrying. The next login attempt will succeed. + retry.fail({ fake: 'error' }) + }, + }, + }), + getPosts: build.query({ + query: () => ({ url: 'posts' }), + providesTags: (result = []) => [ + ...result.map(({ id }) => ({ type: 'Posts', id } as const)), + { type: 'Posts' as const, id: 'LIST' }, + ], + }), + addPost: build.mutation>({ + query: (body) => ({ + url: `posts`, + method: 'POST', + body, + }), + invalidatesTags: [{ type: 'Posts', id: 'LIST' }], + }), + getPost: build.query({ + query: (id) => `posts/${id}`, + providesTags: (_post, _err, id) => [{ type: 'Posts', id }], + }), + updatePost: build.mutation>({ + query(data) { + const { id, ...body } = data + return { + url: `posts/${id}`, + method: 'PUT', + body, + } + }, + invalidatesTags: (post) => [{ type: 'Posts', id: post?.id }], + }), + deletePost: build.mutation<{ success: boolean; id: number }, number>({ + query(id) { + return { + url: `posts/${id}`, + method: 'DELETE', + } + }, + invalidatesTags: (post) => [{ type: 'Posts', id: post?.id }], + }), + getErrorProne: build.query<{ success: boolean }, void>({ + query: () => 'error-prone', + }), + }), +}) + +export const { + useAddPostMutation, + useDeletePostMutation, + useGetPostQuery, + useGetPostsQuery, + useLoginMutation, + useUpdatePostMutation, + useGetErrorProneQuery, +} = postApi + +export const { + endpoints: { login, getPost }, +} = postApi diff --git a/examples/query/react/kitchen-sink/src/app/services/split/index.ts b/examples/query/react/kitchen-sink/src/app/services/split/index.ts new file mode 100644 index 000000000..c782ff872 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/app/services/split/index.ts @@ -0,0 +1,21 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +export interface Post { + id: number + name: string +} + +export type PostsResponse = Post[] + +export const emptySplitApi = createApi({ + reducerPath: 'splitApi', + baseQuery: fetchBaseQuery({ baseUrl: '/' }), + tagTypes: ['Posts'], + endpoints: () => ({}), +}) + +export const splitApi = emptySplitApi.enhanceEndpoints({ + endpoints: () => ({ + getPost: () => 'test', + }), +}) diff --git a/examples/query/react/kitchen-sink/src/app/services/split/post.ts b/examples/query/react/kitchen-sink/src/app/services/split/post.ts new file mode 100644 index 000000000..8d15449d0 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/app/services/split/post.ts @@ -0,0 +1,40 @@ +import { emptySplitApi, Post } from '.' + +export const apiWithPost = emptySplitApi.injectEndpoints({ + endpoints: (build) => ({ + addPost: build.mutation>({ + query(body) { + return { + url: `posts`, + method: 'POST', + body, + } + }, + invalidatesTags: ['Posts'], + }), + getPost: build.query({ + query: (id) => `posts/${id}`, + providesTags: (_result, _err, id) => [{ type: 'Posts', id }], + }), + updatePost: build.mutation>({ + query(data) { + const { id, ...body } = data + return { + url: `posts/${id}`, + method: 'PUT', + body, + } + }, + invalidatesTags: (post) => [{ type: 'Posts', id: post?.id }], + }), + deletePost: build.mutation<{ success: boolean; id: number }, number>({ + query(id) { + return { + url: `posts/${id}`, + method: 'DELETE', + } + }, + invalidatesTags: (post) => [{ type: 'Posts', id: post?.id }], + }), + }), +}) diff --git a/examples/query/react/kitchen-sink/src/app/services/split/posts.ts b/examples/query/react/kitchen-sink/src/app/services/split/posts.ts new file mode 100644 index 000000000..38318ffb8 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/app/services/split/posts.ts @@ -0,0 +1,11 @@ +import { emptySplitApi, PostsResponse } from '.' + +export const apiWithPosts = emptySplitApi.injectEndpoints({ + endpoints: (build) => ({ + getPosts: build.query({ + query: () => 'posts', + providesTags: (result = []) => + result.map(({ id }) => ({ type: 'Posts', id })), + }), + }), +}) diff --git a/examples/query/react/kitchen-sink/src/app/services/times.ts b/examples/query/react/kitchen-sink/src/app/services/times.ts new file mode 100644 index 000000000..55c885e9d --- /dev/null +++ b/examples/query/react/kitchen-sink/src/app/services/times.ts @@ -0,0 +1,19 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +interface TimeResponse { + time: string +} + +export const timeApi = createApi({ + reducerPath: 'timeApi', + baseQuery: fetchBaseQuery(), + tagTypes: ['Time'], + endpoints: (build) => ({ + getTime: build.query({ + query: (id) => `time/${id}`, + providesTags: (_result, _err, id) => [{ type: 'Time', id }], + }), + }), +}) + +export const { usePrefetch: usePrefetchTime, useGetTimeQuery } = timeApi diff --git a/examples/query/react/kitchen-sink/src/app/store.ts b/examples/query/react/kitchen-sink/src/app/store.ts new file mode 100644 index 000000000..fc35deac5 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/app/store.ts @@ -0,0 +1,37 @@ +import { configureStore, ConfigureStoreOptions } from '@reduxjs/toolkit' +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' +import { counterApi } from './services/counter' +import { postApi } from './services/posts' +import { timeApi } from './services/times' +import polling from '../features/polling/pollingSlice' +import { splitApi } from './services/split' +import auth from '../features/auth/authSlice' + +export const createStore = ( + options?: ConfigureStoreOptions['preloadedState'] | undefined +) => + configureStore({ + reducer: { + [counterApi.reducerPath]: counterApi.reducer, + [postApi.reducerPath]: postApi.reducer, + [timeApi.reducerPath]: timeApi.reducer, + [splitApi.reducerPath]: splitApi.reducer, + polling, + auth, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().concat( + counterApi.middleware, + postApi.middleware, + timeApi.middleware, + splitApi.middleware + ), + ...options, + }) + +export const store = createStore() + +export type AppDispatch = typeof store.dispatch +export const useAppDispatch = () => useDispatch() +export type RootState = ReturnType +export const useTypedSelector: TypedUseSelectorHook = useSelector diff --git a/examples/query/react/kitchen-sink/src/features/auth/authSlice.ts b/examples/query/react/kitchen-sink/src/features/auth/authSlice.ts new file mode 100644 index 000000000..a8fea72a0 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/auth/authSlice.ts @@ -0,0 +1,37 @@ +import { createSlice } from '@reduxjs/toolkit' +import { postApi, User } from '../../app/services/posts' +import { RootState } from '../../app/store' + +const initialState = { + user: null, + token: null, + isAuthenticated: false, +} as { user: null | User; token: string | null; isAuthenticated: boolean } + +const slice = createSlice({ + name: 'auth', + initialState, + reducers: { + logout: () => initialState, + }, + extraReducers: (builder) => { + builder + .addMatcher(postApi.endpoints.login.matchPending, (state, action) => { + console.log('pending', action) + }) + .addMatcher(postApi.endpoints.login.matchFulfilled, (state, action) => { + console.log('fulfilled', action) + state.user = action.payload.user + state.token = action.payload.token + }) + .addMatcher(postApi.endpoints.login.matchRejected, (state, action) => { + console.log('rejected', action) + }) + }, +}) + +export const { logout } = slice.actions +export default slice.reducer + +export const selectIsAuthenticated = (state: RootState) => + state.auth.isAuthenticated diff --git a/examples/query/react/kitchen-sink/src/features/bundleSplitting/Lazy.tsx b/examples/query/react/kitchen-sink/src/features/bundleSplitting/Lazy.tsx new file mode 100644 index 000000000..79966709f --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/bundleSplitting/Lazy.tsx @@ -0,0 +1,10 @@ +import * as React from 'react' +import { PostsList } from '.' + +export const Lazy = () => { + return ( + loading...}> + + + ) +} diff --git a/examples/query/react/kitchen-sink/src/features/bundleSplitting/Post.tsx b/examples/query/react/kitchen-sink/src/features/bundleSplitting/Post.tsx new file mode 100644 index 000000000..44fff2792 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/bundleSplitting/Post.tsx @@ -0,0 +1,32 @@ +import React from 'react' + +// import the file that injects "post" to make sure it has been loaded +import { apiWithPost } from '../../app/services/split/post' + +function assert(condition: any, msg = 'Generic Assertion'): asserts condition { + if (!condition) { + throw new Error(`Assertion failed: ${msg}`) + } +} + +const Post = ({ id }: { id: number }) => { + /* + * As accessing these conditionally will cause hooks to error out, + * it's best to assert their existence & throw otherwise. + * This missing would be a programming error that you should + * catch early anyways. + */ + assert( + apiWithPost.endpoints.getPost?.useQuery, + 'Endpoint `getPost` not loaded!' + ) + const { data, error } = apiWithPost.endpoints.getPost.useQuery(id) + return error ? ( + <>there was an error + ) : !data ? ( + <>loading + ) : ( +

{data.name}

+ ) +} +export default Post diff --git a/examples/query/react/kitchen-sink/src/features/bundleSplitting/PostsList.tsx b/examples/query/react/kitchen-sink/src/features/bundleSplitting/PostsList.tsx new file mode 100644 index 000000000..3d794b0a6 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/bundleSplitting/PostsList.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; + +import { apiWithPosts } from '../../app/services/split/posts'; +import { Post } from '.'; + +const PostsList = () => { + /** + * You can directly use the "injected" API. + * That way all the injected endpoint are typed on there, + * but no endpoints that were injected elsewhere will be typed. + * + * They *will* be available at runtime if they have been + * injected though. + */ + + const { data, error } = apiWithPosts.endpoints.getPosts.useQuery(); + const [selected, select] = React.useState(); + return error ? ( + <>there was an error + ) : !data ? ( + <>loading + ) : ( + <> + {selected && } +
    + {data.map((post) => ( +
  • + +
  • + ))} +
+ + ); +}; +export default PostsList; diff --git a/examples/query/react/kitchen-sink/src/features/bundleSplitting/index.ts b/examples/query/react/kitchen-sink/src/features/bundleSplitting/index.ts new file mode 100644 index 000000000..3f44977e2 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/bundleSplitting/index.ts @@ -0,0 +1,7 @@ +import { lazy } from 'react' + +export const PostsList = lazy(() => import('./PostsList')) + +export const Post = lazy(() => import('./Post')) + +export { Lazy } from './Lazy' diff --git a/examples/query/react/kitchen-sink/src/features/common/Container.tsx b/examples/query/react/kitchen-sink/src/features/common/Container.tsx new file mode 100644 index 000000000..00c639997 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/common/Container.tsx @@ -0,0 +1,5 @@ +import * as React from 'react'; + +export const Container: React.FC = ({ children }) => ( +
{children}
+); diff --git a/examples/query/react/kitchen-sink/src/features/counter/Counter.module.css b/examples/query/react/kitchen-sink/src/features/counter/Counter.module.css new file mode 100644 index 000000000..2ba30ae79 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/counter/Counter.module.css @@ -0,0 +1,75 @@ +.row { + display: flex; + align-items: center; + justify-content: center; +} + +.row:not(:last-child) { + margin-bottom: 16px; +} + +.value { + font-size: 78px; + padding-left: 16px; + padding-right: 16px; + margin-top: 2px; + font-family: "Courier New", Courier, monospace; +} + +.button { + appearance: none; + background: none; + font-size: 32px; + padding-left: 12px; + padding-right: 12px; + outline: none; + border: 2px solid transparent; + color: rgb(112, 76, 182); + padding-bottom: 4px; + cursor: pointer; + background-color: rgba(112, 76, 182, 0.1); + border-radius: 2px; + transition: all 0.15s; +} + +.textbox { + font-size: 32px; + padding: 2px; + width: 64px; + text-align: center; + margin-right: 8px; +} + +.button:hover, +.button:focus { + border: 2px solid rgba(112, 76, 182, 0.4); +} + +.button:active { + background-color: rgba(112, 76, 182, 0.2); +} + +.asyncButton { + composes: button; + position: relative; + margin-left: 8px; +} + +.asyncButton:after { + content: ""; + background-color: rgba(112, 76, 182, 0.15); + display: block; + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + opacity: 0; + transition: width 1s linear, opacity 0.5s ease 1s; +} + +.asyncButton:active:after { + width: 0%; + opacity: 1; + transition: 0s; +} diff --git a/examples/query/react/kitchen-sink/src/features/counter/Counter.tsx b/examples/query/react/kitchen-sink/src/features/counter/Counter.tsx new file mode 100644 index 000000000..c192e40ca --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/counter/Counter.tsx @@ -0,0 +1,32 @@ +import React, { useState } from 'react'; +import styles from './Counter.module.css'; +import { useDecrementCountMutation, useGetCountQuery, useIncrementCountMutation } from '../../app/services/counter'; + +export function Counter({ id, onRemove }: { id?: string; onRemove?: () => void }) { + const [pollingInterval, setPollingInterval] = useState(10000); + const { data } = useGetCountQuery(undefined, { pollingInterval }); + const [increment] = useIncrementCountMutation(); + + const [decrement] = useDecrementCountMutation(); + + return ( +
+
+ + {data?.count || 0} + + setPollingInterval(valueAsNumber)} + /> + {onRemove && } +
+
+ ); +} diff --git a/examples/query/react/kitchen-sink/src/features/counter/CounterList.tsx b/examples/query/react/kitchen-sink/src/features/counter/CounterList.tsx new file mode 100644 index 000000000..e917b7214 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/counter/CounterList.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { nanoid } from '@reduxjs/toolkit'; +import { Container } from '../common/Container'; +import { Counter } from './Counter'; + +export const CounterList = () => { + const [counters, setCounters] = React.useState([]); + + if (!counters.length) { + return ( + +
No counters, why don't you add one?
+
+ +
+
+ ); + } + + return ( + +
+ +
+ {counters.map((id) => ( + setCounters((prev) => prev.filter((el) => el !== id))} /> + ))} +
+ ); +}; diff --git a/examples/query/react/kitchen-sink/src/features/polling/PollingToggles.tsx b/examples/query/react/kitchen-sink/src/features/polling/PollingToggles.tsx new file mode 100644 index 000000000..dca03216b --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/polling/PollingToggles.tsx @@ -0,0 +1,47 @@ +import * as React from 'react'; +import { useAppDispatch, useTypedSelector } from '../../app/store'; +import { + selectGlobalPollingEnabled, + selectPollingConfigByApp, + toggleGlobalPolling, + updatePolling, +} from './pollingSlice'; + +const PollingToggleButton = ({ + enabled, + onClick, + children, +}: { + onClick: () => void; + enabled: boolean; + children?: React.ReactNode; +}) => { + return ( + + ); +}; + +export const PollingToggles = () => { + const dispatch = useAppDispatch(); + const globalPolling = useTypedSelector(selectGlobalPollingEnabled); + const timesPolling = useTypedSelector((state) => selectPollingConfigByApp(state, 'times')); + + return ( +
+ Global Polling Configs +
+ dispatch(toggleGlobalPolling())}> + Global + + dispatch(updatePolling({ app: 'times', enabled: !timesPolling.enabled }))} + > + Times + +
+
+ ); +}; diff --git a/examples/query/react/kitchen-sink/src/features/polling/pollingSlice.ts b/examples/query/react/kitchen-sink/src/features/polling/pollingSlice.ts new file mode 100644 index 000000000..ebcbef50b --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/polling/pollingSlice.ts @@ -0,0 +1,67 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { RootState } from '../../app/store'; + +type PollingConfig = { + enabled: boolean; + interval: number; +}; + +type SliceState = { + enabled: boolean; + apps: { + [key: string]: PollingConfig; + }; +}; + +const initialState: SliceState = { + enabled: true, + apps: { + counters: { + enabled: true, + interval: 0, + }, + times: { + enabled: true, + interval: 0, + }, + posts: { + enabled: true, + interval: 0, + }, + }, +}; + +type PollingAppKey = keyof typeof initialState['apps']; + +const slice = createSlice({ + name: 'polling', + initialState, + reducers: { + toggleGlobalPolling(state) { + state.enabled = !state.enabled; + }, + updatePolling( + state, + { + payload, + }: PayloadAction<{ + app: PollingAppKey; + enabled?: boolean; + interval?: number; + }> + ) { + const { app, ...rest } = payload; + state.apps[app] = { + ...state.apps[app], + ...rest, + }; + }, + }, +}); + +export const { toggleGlobalPolling, updatePolling } = slice.actions; + +export default slice.reducer; + +export const selectGlobalPollingEnabled = (state: RootState) => state.polling.enabled; +export const selectPollingConfigByApp = (state: RootState, app: PollingAppKey) => state.polling.apps[app]; diff --git a/examples/query/react/kitchen-sink/src/features/posts/PostDetail.tsx b/examples/query/react/kitchen-sink/src/features/posts/PostDetail.tsx new file mode 100644 index 000000000..ed35e7b11 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/posts/PostDetail.tsx @@ -0,0 +1,105 @@ +import * as React from 'react'; +import { useHistory, useParams } from 'react-router-dom'; +import { useTypedSelector } from '../../app/store'; +import { useDeletePostMutation, useGetPostQuery, useUpdatePostMutation } from '../../app/services/posts'; +import { selectGlobalPollingEnabled } from '../polling/pollingSlice'; + +const EditablePostName = ({ + name: initialName, + onUpdate, + onCancel, + loading = false, +}: { + name: string; + onUpdate: (name: string) => void; + onCancel: () => void; + loading?: boolean; +}) => { + const [name, setName] = React.useState(initialName); + + const handleChange = ({ target: { value } }: React.ChangeEvent) => setName(value); + + const handleUpdate = () => onUpdate(name); + const handleCancel = () => onCancel(); + + return ( +
+ + + +
+ ); +}; + +const PostJsonDetail = ({ id }: { id: number }) => { + const { data: post } = useGetPostQuery(id); + + return ( +
+
{JSON.stringify(post, null, 2)}
+
+ ); +}; + +export const PostDetail = () => { + const { id } = useParams<{ id: any }>(); + const { push } = useHistory(); + const globalPolling = useTypedSelector(selectGlobalPollingEnabled); + + const [isEditing, setIsEditing] = React.useState(false); + + const { data: post, isFetching, isLoading } = useGetPostQuery(id, { pollingInterval: globalPolling ? 3000 : 0 }); + + const [updatePost, { isLoading: isUpdating }] = useUpdatePostMutation(); + const [deletePost, { isLoading: isDeleting }] = useDeletePostMutation(); + + if (isLoading) { + return
Loading...
; + } + + if (!post) { + return
Missing post!
; + } + + return ( +
+ {isEditing ? ( + + updatePost({ id, name }) + .then((result) => { + // handle the success! + console.log('Update Result', result); + setIsEditing(false); + }) + .catch((error) => console.error('Update Error', error)) + } + onCancel={() => setIsEditing(false)} + loading={isUpdating} + /> + ) : ( + +
+
+

+ {post.name} {isFetching ? '...refetching' : ''} +

+
+ + +
+
+ )} + +
+ ); +}; diff --git a/examples/query/react/kitchen-sink/src/features/posts/PostsManager.css b/examples/query/react/kitchen-sink/src/features/posts/PostsManager.css new file mode 100644 index 000000000..4f5c7da84 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/posts/PostsManager.css @@ -0,0 +1,10 @@ +.posts-list { + display: flex; + flex-direction: column; + flex-basis: 100%; + flex: 1; + min-height: 200px; + border-right: 1px solid #eee; + padding: 20px; + text-align: left; +} diff --git a/examples/query/react/kitchen-sink/src/features/posts/PostsManager.tsx b/examples/query/react/kitchen-sink/src/features/posts/PostsManager.tsx new file mode 100644 index 000000000..0bbd0b84e --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/posts/PostsManager.tsx @@ -0,0 +1,111 @@ +import React, { useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Route, Switch, useHistory } from 'react-router-dom'; +import { + Post, + useAddPostMutation, + useGetPostsQuery, + useLoginMutation, + useGetErrorProneQuery, +} from '../../app/services/posts'; +import { selectIsAuthenticated, logout } from '../auth/authSlice'; +import { PostDetail } from './PostDetail'; +import './PostsManager.css'; + +const AddPost = () => { + const initialValue = { name: '' }; + const [post, setPost] = useState>(initialValue); + const [addPost, { isLoading }] = useAddPostMutation(); + + const handleChange = ({ target }: React.ChangeEvent) => { + setPost((prev) => ({ + ...prev, + [target.name]: target.value, + })); + }; + + const handleAddPost = () => addPost(post).then(() => setPost(initialValue)); + + return ( +
+
+ +
+
+ +
+
+ ); +}; + +const PostListItem = ({ data: { name, id }, onSelect }: { data: Post; onSelect: (id: number) => void }) => { + return ( +
  • + onSelect(id)}> + {name} + +
  • + ); +}; + +const PostList = () => { + const { data: posts, isLoading } = useGetPostsQuery(); + const { push } = useHistory(); + + if (isLoading) { + return
    Loading
    ; + } + + if (!posts) { + return
    No posts :(
    ; + } + + return ( +
    + {posts.map((post) => ( + push(`/posts/${id}`)} /> + ))} +
    + ); +}; + +export const PostsManager = () => { + const [login] = useLoginMutation(); + const [initRetries, setInitRetries] = useState(false); + const { data, error, isFetching } = useGetErrorProneQuery(undefined, { skip: !initRetries }); + const dispatch = useDispatch(); + const isAuthenticated = useSelector(selectIsAuthenticated); + + return ( +
    +

    Posts

    + {!isAuthenticated ? ( + + ) : ( + + )} + +
    +
    +
    + +
    + Posts: + +
    + List with duplicate subscription: + +
    +
    + + + +
    +
    +
    + ); +}; + +export default PostsManager; diff --git a/examples/query/react/kitchen-sink/src/features/time/TimeList.tsx b/examples/query/react/kitchen-sink/src/features/time/TimeList.tsx new file mode 100644 index 000000000..08f3b7f99 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/features/time/TimeList.tsx @@ -0,0 +1,142 @@ +import * as React from 'react'; +import { nanoid } from '@reduxjs/toolkit'; +import { useEffect } from 'react'; +import { useGetTimeQuery, usePrefetchTime } from '../../app/services/times'; +import { Container } from '../common/Container'; +import { useTypedSelector } from '../../app/store'; +import { selectGlobalPollingEnabled, selectPollingConfigByApp } from '../polling/pollingSlice'; + +const timezones: Record = { + '-12:00': '(GMT -12:00) Eniwetok, Kwajalein', + '-11:00': '(GMT -11:00) Midway Island, Samoa', + '-10:00': '(GMT -10:00) Hawaii', + '-09:50': '(GMT -9:30) Taiohae', + '-09:00': '(GMT -9:00) Alaska', + '-08:00': '(GMT -8:00) Pacific Time (US & Canada)', + '-07:00': '(GMT -7:00) Mountain Time (US & Canada)', + '-06:00': '(GMT -6:00) Central Time (US & Canada), Mexico City', + '-05:00': '(GMT -5:00) Eastern Time (US & Canada), Bogota, Lima', + '-04:50': '(GMT -4:30) Caracas', + '-04:00': '(GMT -4:00) Atlantic Time (Canada), Caracas, La Paz', + '-03:50': '(GMT -3:30) Newfoundland', + '-03:00': '(GMT -3:00) Brazil, Buenos Aires, Georgetown', + '-02:00': '(GMT -2:00) Mid-Atlantic', + '-01:00': '(GMT -1:00) Azores, Cape Verde Islands', + '+00:00': '(GMT) Western Europe Time, London, Lisbon, Casablanca', + '+01:00': '(GMT +1:00) Brussels, Copenhagen, Madrid, Paris', + '+02:00': '(GMT +2:00) Kaliningrad, South Africa', + '+03:00': '(GMT +3:00) Baghdad, Riyadh, Moscow, St. Petersburg', + '+03:50': '(GMT +3:30) Tehran', + '+04:00': '(GMT +4:00) Abu Dhabi, Muscat, Baku, Tbilisi', + '+04:50': '(GMT +4:30) Kabul', + '+05:00': '(GMT +5:00) Ekaterinburg, Islamabad, Karachi, Tashkent', + '+05:50': '(GMT +5:30) Bombay, Calcutta, Madras, New Delhi', + '+05:75': '(GMT +5:45) Kathmandu, Pokhara', + '+06:00': '(GMT +6:00) Almaty, Dhaka, Colombo', + '+06:50': '(GMT +6:30) Yangon, Mandalay', + '+07:00': '(GMT +7:00) Bangkok, Hanoi, Jakarta', + '+08:00': '(GMT +8:00) Beijing, Perth, Singapore, Hong Kong', + '+08:75': '(GMT +8:45) Eucla', + '+09:00': '(GMT +9:00) Tokyo, Seoul, Osaka, Sapporo, Yakutsk', + '+09:50': '(GMT +9:30) Adelaide, Darwin', + '+10:00': '(GMT +10:00) Eastern Australia, Guam, Vladivostok', + '+10:50': '(GMT +10:30) Lord Howe Island', + '+11:00': '(GMT +11:00) Magadan, Solomon Islands, New Caledonia', + '+11:50': '(GMT +11:30) Norfolk Island', + '+12:00': '(GMT +12:00) Auckland, Wellington, Fiji, Kamchatka', + '+12:75': '(GMT +12:45) Chatham Islands', + '+13:00': '(GMT +13:00) Apia, Nukualofa', + '+14:00': '(GMT +14:00) Line Islands, Tokelau', +}; + +const TimeZoneSelector = ({ onChange }: { onChange: (event: React.ChangeEvent) => void }) => { + return ( + + ); +}; + +const intervalOptions = [ + { label: '0 - Off', value: 0 }, + { label: '1s', value: 1000 }, + { label: '3s', value: 3000 }, + { label: '5s', value: 5000 }, + { label: '10s', value: 10000 }, + { label: '1m', value: 60000 }, +]; + +const TimeDisplay = ({ offset, label }: { offset: string; label: string }) => { + const globalPolling = useTypedSelector(selectGlobalPollingEnabled); + const { enabled: timesPolling } = useTypedSelector((state) => selectPollingConfigByApp(state, 'times')); + + const canPoll = globalPolling && timesPolling; + + const [pollingInterval, setPollingInterval] = React.useState(0); + const { data, refetch, isFetching } = useGetTimeQuery(offset, { + pollingInterval: canPoll ? pollingInterval : 0, + }); + + return ( +
    +

    + {data?.time && new Date(data.time).toLocaleTimeString()} - {label} +

    +

    + Polling Interval:{' '} + +

    +
    + ); +}; + +export const TimeList = () => { + const [times, setTimes] = React.useState<{ [key: string]: string }>({ + [nanoid()]: '-08:00', + }); + const [selectedValue, setSelectedValue] = React.useState(''); + + const prefetch = usePrefetchTime('getTime'); + + useEffect(() => { + setTimeout(() => { + setTimes((prev) => ({ ...prev, [nanoid()]: '+00:00' })); + }, 1000); + }, []); + + return ( + +

    Add some times, even duplicates, and watch them automatically refetch in sync!

    +

    + Notes: shared queries (aka multiple entries of the same time zone) will share the lowest polling interval + between them that is greater than 0. If all entries are set to 0, it will stop polling. If you have two entries + with a polling time of 5s and one with 0 - off, it will continue at 5s until they are removed or 0'd out. +
    + Any new poll starts after the last request has either finished or failed to prevent slow-running requests to + immediately double-trigger. +
    + * Background flashes green when query is running + +

    + setSelectedValue(value)} /> + +
    + {Object.entries(times).map(([key, tz]) => ( + + ))} +
    + ); +}; diff --git a/examples/query/react/kitchen-sink/src/index.tsx b/examples/query/react/kitchen-sink/src/index.tsx new file mode 100644 index 000000000..af083b4b6 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/index.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import App from './App' +import { store } from './app/store' +import { Provider } from 'react-redux' +import { BrowserRouter } from 'react-router-dom' +import { worker } from './mocks/browser' + +// Initialize the msw worker, wait for the service worker registration to resolve, then mount +async function render() { + if (process.env.NODE_ENV === 'development') { + await worker.start() + } + ReactDOM.render( + + + + + + + , + document.getElementById('root') + ) +} + +render() diff --git a/examples/query/react/kitchen-sink/src/mocks/browser.ts b/examples/query/react/kitchen-sink/src/mocks/browser.ts new file mode 100644 index 000000000..d8d2c7d7a --- /dev/null +++ b/examples/query/react/kitchen-sink/src/mocks/browser.ts @@ -0,0 +1,4 @@ +import { setupWorker } from 'msw' +import { handlers } from './handlers' + +export const worker = setupWorker(...handlers) diff --git a/examples/query/react/kitchen-sink/src/mocks/handlers.ts b/examples/query/react/kitchen-sink/src/mocks/handlers.ts new file mode 100644 index 000000000..3bdab6c5b --- /dev/null +++ b/examples/query/react/kitchen-sink/src/mocks/handlers.ts @@ -0,0 +1,110 @@ +import { rest } from 'msw'; +import { createEntityAdapter, nanoid } from '@reduxjs/toolkit'; +import { Post } from '../app/services/posts'; + +// We're just going to use a simple in-memory store for both the counter and posts +// The entity adapter will handle modifications when triggered by the MSW handlers + +let count = 0; +let startingId = 3; // Just a silly counter for usage when adding new posts + +const adapter = createEntityAdapter(); + +let state = adapter.getInitialState(); +state = adapter.setAll(state, [ + { id: 1, name: 'A sample post', fetched_at: new Date().toUTCString() }, + { id: 2, name: 'A post about rtk-query', fetched_at: new Date().toUTCString() }, +]); + +export { state }; + +// Just use a random id for an auth token +const token = nanoid(); + +export const handlers = [ + rest.get('/time/:offset', (req, res, ctx) => { + const { offset } = req.params as { offset: string }; + const date = new Date(); + const localDate = date.getTime(); // users local time + const localOffset = date.getTimezoneOffset() * 60000; + const formattedOffset = Number(offset.replace(':', '.')); + const target = localDate + localOffset + 3600000 * formattedOffset; + return res(ctx.json({ time: new Date(target).toUTCString() }), ctx.delay(400)); + }), + + rest.put<{ amount: number }>('/increment', (req, res, ctx) => { + const { amount } = req.body; + count = count += amount; + + return res(ctx.json({ count })); + }), + + rest.put<{ amount: number }>('/decrement', (req, res, ctx) => { + const { amount } = req.body; + count = count -= amount; + + return res(ctx.json({ count })); + }), + + rest.get('/count', (req, res, ctx) => { + return res(ctx.json({ count })); + }), + + rest.post('/login', (req, res, ctx) => { + return res.once(ctx.json({ message: 'i fail once' }), ctx.status(500)); + }), + rest.post('/login', (req, res, ctx) => { + return res(ctx.json({ token, user: { first_name: 'Test', last_name: 'User' } })); + }), + + rest.get('/posts', (req, res, ctx) => { + return res(ctx.json(Object.values(state.entities))); + }), + + rest.post('/posts', (req, res, ctx) => { + let post = req.body as Partial; + startingId += 1; + state = adapter.addOne(state, { ...post, id: startingId } as Post); + return res(ctx.json(Object.values(state.entities)), ctx.delay(400)); + }), + + rest.get('/posts/:id', (req, res, ctx) => { + const { id } = req.params as { id: string }; + state = adapter.updateOne(state, { id, changes: { fetched_at: new Date().toUTCString() } }); + return res(ctx.json(state.entities[id]), ctx.delay(400)); + }), + + rest.put('/posts/:id', (req, res, ctx) => { + const { id } = req.params as { id: string }; + const changes = req.body as Partial; + + state = adapter.updateOne(state, { id, changes }); + + return res(ctx.json(state.entities[id]), ctx.delay(400)); + }), + + rest.delete('/posts/:id', (req, res, ctx) => { + const { id } = req.params as { id: string }; + + state = adapter.removeOne(state, id); + + return res( + ctx.json({ + id, + success: true, + }), + ctx.delay(600) + ); + }), + + rest.get('/error-prone', (req, res, ctx) => { + if (Math.random() > 0.1) { + return res(ctx.json({ error: 'failed!' }), ctx.status(500)); + } + return res( + ctx.json({ + success: true, + }) + ); + }), +]; diff --git a/examples/query/react/kitchen-sink/src/mocks/mockServer.ts b/examples/query/react/kitchen-sink/src/mocks/mockServer.ts new file mode 100644 index 000000000..d51850b8f --- /dev/null +++ b/examples/query/react/kitchen-sink/src/mocks/mockServer.ts @@ -0,0 +1,9 @@ +import { setupServer } from 'msw/node' +import { handlers } from './handlers' +import { state } from './handlers' + +export const mockServer = () => { + const server = setupServer(...handlers) + + return { server, state } +} diff --git a/examples/query/react/kitchen-sink/src/mocks/setupTests.tsx b/examples/query/react/kitchen-sink/src/mocks/setupTests.tsx new file mode 100644 index 000000000..7bbca3f13 --- /dev/null +++ b/examples/query/react/kitchen-sink/src/mocks/setupTests.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { store } from '../app/store'; +import { Router, Route } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; +import { mockServer } from './mockServer'; +import 'whatwg-fetch'; + +export const setupTests = () => { + const { server, state: serverState } = mockServer(); + + beforeAll(() => server.listen({ onUnhandledRequest: 'warn' })); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + + interface RenderOptions { + route: string; + path?: string; + } + function renderWithProvider(children: React.ReactChild, { route, path }: RenderOptions = { route: '/', path: '' }) { + const history = createMemoryHistory(); + history.push(route); + return render( + + {path ? {children} : children} + + ); + } + + return { + store, + serverState, + server, + renderWithProvider, + }; +}; diff --git a/examples/query/react/kitchen-sink/src/react-app-env.d.ts b/examples/query/react/kitchen-sink/src/react-app-env.d.ts new file mode 100644 index 000000000..6431bc5fc --- /dev/null +++ b/examples/query/react/kitchen-sink/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/query/react/kitchen-sink/src/setupTests.ts b/examples/query/react/kitchen-sink/src/setupTests.ts new file mode 100644 index 000000000..74b1a275a --- /dev/null +++ b/examples/query/react/kitchen-sink/src/setupTests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom/extend-expect'; diff --git a/examples/query/react/kitchen-sink/src/styles.css b/examples/query/react/kitchen-sink/src/styles.css new file mode 100644 index 000000000..59b0604dd --- /dev/null +++ b/examples/query/react/kitchen-sink/src/styles.css @@ -0,0 +1,4 @@ +.App { + font-family: sans-serif; + text-align: center; +} diff --git a/examples/query/react/kitchen-sink/tsconfig.json b/examples/query/react/kitchen-sink/tsconfig.json new file mode 100644 index 000000000..d4eea2ea4 --- /dev/null +++ b/examples/query/react/kitchen-sink/tsconfig.json @@ -0,0 +1,25 @@ +{ + "include": [ + "./src/**/*" + ], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "lib": [ + "dom", + "es2015" + ], + "jsx": "react-jsx", + "target": "es5", + "allowJs": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + } +} \ No newline at end of file From 795864f4bddc7fb03d31c5cf22409b8ddda2a3f3 Mon Sep 17 00:00:00 2001 From: sidwebworks Date: Fri, 8 Apr 2022 22:21:41 +0530 Subject: [PATCH 2/6] chore(docs): update examples.mdx --- docs/rtk-query/usage/examples.mdx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/rtk-query/usage/examples.mdx b/docs/rtk-query/usage/examples.mdx index 4f3ecf5f2..55674e43c 100644 --- a/docs/rtk-query/usage/examples.mdx +++ b/docs/rtk-query/usage/examples.mdx @@ -16,22 +16,16 @@ We have a variety of examples that demonstrate various aspects of using RTK Quer These examples are not meant to be what you base your application on, but exist to show _very specific_ behaviors that you may not actually want or need in your application. For most users, the basic examples in the [Queries](./queries) and [Mutations](./mutations) sections will cover the majority of your needs. -:::info - -The examples were created as part of the standalone `@rtk-incubator/rtk-query` development cycle. We're currently working to update them as part of the process of finalizing RTK Query's integration into Redux Toolkit, so some of the imports are mismatched and not all the examples are currently in the RTK repo. However, you should be able to inspect these examples and use the logic they show as guidelines. - -::: - :::tip Please note that when playing with the examples in CodeSandbox that you can experience quirky behavior, especially if you fork them and start editing files. Hot reloading, CSB service workers and [`msw`](https://mswjs.io/) sometimes have trouble getting on the right page -- when that happens, just refresh in the CSB browser pane. ::: -## React Hooks +## Kitchen Sink From 0b38308de9fbeb4516786b2b57896d53e607df75 Mon Sep 17 00:00:00 2001 From: sidwebworks Date: Fri, 8 Apr 2022 22:34:42 +0530 Subject: [PATCH 3/6] chore: include yarn.lock --- yarn.lock | 402 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 401 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 7c3caf59f..c67f0fa18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3873,6 +3873,28 @@ __metadata: languageName: unknown linkType: soft +"@examples-query-react/kitchen-sink@workspace:examples/query/react/kitchen-sink": + version: 0.0.0-use.local + resolution: "@examples-query-react/kitchen-sink@workspace:examples/query/react/kitchen-sink" + dependencies: + "@reduxjs/toolkit": "workspace:^" + "@testing-library/jest-dom": ^5.11.5 + "@testing-library/react": ^12.0.0 + "@types/jest": ^26.0.23 + "@types/node": ^14.14.6 + "@types/react": 17.0.0 + "@types/react-dom": 17.0.0 + "@types/react-redux": 7.1.9 + msw: ^0.39.2 + react: 17.0.0 + react-dom: 17.0.0 + react-redux: 7.2.2 + react-scripts: 4.0.2 + typescript: ~4.2.4 + whatwg-fetch: ^3.4.1 + languageName: unknown + linkType: soft + "@examples-query-react/mutations@workspace:examples/query/react/mutations": version: 0.0.0-use.local resolution: "@examples-query-react/mutations@workspace:examples/query/react/mutations" @@ -5116,6 +5138,16 @@ __metadata: languageName: node linkType: hard +"@mswjs/cookies@npm:^0.2.0": + version: 0.2.0 + resolution: "@mswjs/cookies@npm:0.2.0" + dependencies: + "@types/set-cookie-parser": ^2.4.0 + set-cookie-parser: ^2.4.6 + checksum: 218d169df02cda261e68e0203812a68affa6396d0041b23c70c4865cf757bebc47ffe3f66df39b94f666f004da42cb9b56e61f089b21c59e6cb88abce8537f19 + languageName: node + linkType: hard + "@mswjs/data@npm:^0.3.0": version: 0.3.0 resolution: "@mswjs/data@npm:0.3.0" @@ -5171,6 +5203,20 @@ __metadata: languageName: node linkType: hard +"@mswjs/interceptors@npm:^0.15.1": + version: 0.15.1 + resolution: "@mswjs/interceptors@npm:0.15.1" + dependencies: + "@open-draft/until": ^1.0.3 + "@xmldom/xmldom": ^0.7.5 + debug: ^4.3.3 + headers-polyfill: ^3.0.4 + outvariant: ^1.2.1 + strict-event-emitter: ^0.2.0 + checksum: 896ebae458f92fd601ed7dd3a5dfd18b30be54e7558a57f689013627ec5d4a50e2794a1c04d5bf587f40a65f9163a930cf88e54b12fa96bfa1677e7c12c7cb59 + languageName: node + linkType: hard + "@mswjs/interceptors@npm:^0.8.0": version: 0.8.1 resolution: "@mswjs/interceptors@npm:0.8.1" @@ -5462,7 +5508,7 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@^1.6.0, @reduxjs/toolkit@^1.6.0-rc.1, @reduxjs/toolkit@^1.8.0, @reduxjs/toolkit@workspace:packages/toolkit": +"@reduxjs/toolkit@^1.6.0, @reduxjs/toolkit@^1.6.0-rc.1, @reduxjs/toolkit@^1.8.0, @reduxjs/toolkit@workspace:^, @reduxjs/toolkit@workspace:packages/toolkit": version: 0.0.0-use.local resolution: "@reduxjs/toolkit@workspace:packages/toolkit" dependencies: @@ -6064,6 +6110,23 @@ __metadata: languageName: node linkType: hard +"@testing-library/jest-dom@npm:^5.11.5": + version: 5.16.4 + resolution: "@testing-library/jest-dom@npm:5.16.4" + dependencies: + "@babel/runtime": ^7.9.2 + "@types/testing-library__jest-dom": ^5.9.1 + aria-query: ^5.0.0 + chalk: ^3.0.0 + css: ^3.0.0 + css.escape: ^1.5.1 + dom-accessibility-api: ^0.5.6 + lodash: ^4.17.15 + redent: ^3.0.0 + checksum: 4240501223b72b97a44d4e3c669f39b208c49fb645d11d08d5f178d607265c5dfad07efbe027f41a0e2458178ff1fd5bf437fc05661b9109dcd013b95a37079e + languageName: node + linkType: hard + "@testing-library/react-hooks@npm:^5.1.2": version: 5.1.3 resolution: "@testing-library/react-hooks@npm:5.1.3" @@ -6263,6 +6326,13 @@ __metadata: languageName: node linkType: hard +"@types/cookie@npm:^0.4.1": + version: 0.4.1 + resolution: "@types/cookie@npm:0.4.1" + checksum: 3275534ed69a76c68eb1a77d547d75f99fedc80befb75a3d1d03662fb08d697e6f8b1274e12af1a74c6896071b11510631ba891f64d30c78528d0ec45a9c1a18 + languageName: node + linkType: hard + "@types/eslint-scope@npm:^3.7.0": version: 3.7.0 resolution: "@types/eslint-scope@npm:3.7.0" @@ -6431,6 +6501,16 @@ __metadata: languageName: node linkType: hard +"@types/jest@npm:*": + version: 27.4.1 + resolution: "@types/jest@npm:27.4.1" + dependencies: + jest-matcher-utils: ^27.0.0 + pretty-format: ^27.0.0 + checksum: 5184f3eef4832d01ee8f59bed15eec45ccc8e29c724a5e6ce37bf74396b37bdf04f557000f45ba4fc38ae6075cf9cfcce3d7a75abc981023c61ceb27230a93e4 + languageName: node + linkType: hard + "@types/jest@npm:^24.0.11": version: 24.9.1 resolution: "@types/jest@npm:24.9.1" @@ -6457,6 +6537,13 @@ __metadata: languageName: node linkType: hard +"@types/js-levenshtein@npm:^1.1.1": + version: 1.1.1 + resolution: "@types/js-levenshtein@npm:1.1.1" + checksum: 1d1ff1ee2ad551909e47f3ce19fcf85b64dc5146d3b531c8d26fc775492d36e380b32cf5ef68ff301e812c3b00282f37aac579ebb44498b94baff0ace7509769 + languageName: node + linkType: hard + "@types/js-yaml@npm:^4.0.0": version: 4.0.1 resolution: "@types/js-yaml@npm:4.0.1" @@ -6609,6 +6696,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^14.14.6": + version: 14.18.12 + resolution: "@types/node@npm:14.18.12" + checksum: 8a0273caa0584020adb8802784fc7d4f18f05e6c205335b7f3818a91d6b0c22736b9f51da3428d5bc54076ad47f1a4d6d57990a3ce8489a520ac66b2b3ff24bc + languageName: node + linkType: hard + "@types/normalize-package-data@npm:^2.4.0": version: 2.4.0 resolution: "@types/normalize-package-data@npm:2.4.0" @@ -6901,6 +6995,15 @@ __metadata: languageName: node linkType: hard +"@types/testing-library__jest-dom@npm:^5.9.1": + version: 5.14.3 + resolution: "@types/testing-library__jest-dom@npm:5.14.3" + dependencies: + "@types/jest": "*" + checksum: 203443d0e7e5929fbc9e441146f92d85efa033b4697e427ed0164df1c40b720b6bb9bbd96a3171ea5fd8d9301b538fd0f49d4f35594d142afd0f6d2ecac25785 + languageName: node + linkType: hard + "@types/through@npm:*": version: 0.0.30 resolution: "@types/through@npm:0.0.30" @@ -7504,6 +7607,13 @@ __metadata: languageName: node linkType: hard +"@xmldom/xmldom@npm:^0.7.5": + version: 0.7.5 + resolution: "@xmldom/xmldom@npm:0.7.5" + checksum: 8d7ec35c1ef6183b4f621df08e01d7e61f244fb964a4719025e65fe6ac06fac418919be64fb40fe5908e69158ef728f2d936daa082db326fe04603012b5f2a84 + languageName: node + linkType: hard + "@xtuc/ieee754@npm:^1.2.0": version: 1.2.0 resolution: "@xtuc/ieee754@npm:1.2.0" @@ -7832,6 +7942,13 @@ __metadata: languageName: node linkType: hard +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b + languageName: node + linkType: hard + "ansi-styles@npm:^2.2.1": version: 2.2.1 resolution: "ansi-styles@npm:2.2.1" @@ -7957,6 +8074,13 @@ __metadata: languageName: node linkType: hard +"aria-query@npm:^5.0.0": + version: 5.0.0 + resolution: "aria-query@npm:5.0.0" + checksum: c41f98866c5a304561ee8cae55856711cddad6f3f85d8cb43cc5f79667078d9b8979ce32d244c1ff364e6463a4d0b6865804a33ccc717fed701b281cf7dc6296 + languageName: node + linkType: hard + "arity-n@npm:^1.0.4": version: 1.0.4 resolution: "arity-n@npm:1.0.4" @@ -9379,6 +9503,16 @@ __metadata: languageName: node linkType: hard +"chalk@npm:4.1.1": + version: 4.1.1 + resolution: "chalk@npm:4.1.1" + dependencies: + ansi-styles: ^4.1.0 + supports-color: ^7.1.0 + checksum: 036e973e665ba1a32c975e291d5f3d549bceeb7b1b983320d4598fb75d70fe20c5db5d62971ec0fe76cdbce83985a00ee42372416abfc3a5584465005a7855ed + languageName: node + linkType: hard + "chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -10258,6 +10392,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.4.2": + version: 0.4.2 + resolution: "cookie@npm:0.4.2" + checksum: a00833c998bedf8e787b4c342defe5fa419abd96b32f4464f718b91022586b8f1bafbddd499288e75c037642493c83083da426c6a9080d309e3bd90fd11baa9b + languageName: node + linkType: hard + "copy-concurrently@npm:^1.0.0": version: 1.0.5 resolution: "copy-concurrently@npm:1.0.5" @@ -10804,6 +10945,13 @@ __metadata: languageName: node linkType: hard +"css.escape@npm:^1.5.1": + version: 1.5.1 + resolution: "css.escape@npm:1.5.1" + checksum: f6d38088d870a961794a2580b2b2af1027731bb43261cfdce14f19238a88664b351cc8978abc20f06cc6bbde725699dec8deb6fe9816b139fc3f2af28719e774 + languageName: node + linkType: hard + "css@npm:^2.0.0": version: 2.2.4 resolution: "css@npm:2.2.4" @@ -10816,6 +10964,17 @@ __metadata: languageName: node linkType: hard +"css@npm:^3.0.0": + version: 3.0.0 + resolution: "css@npm:3.0.0" + dependencies: + inherits: ^2.0.4 + source-map: ^0.6.1 + source-map-resolve: ^0.6.0 + checksum: 4273ac816ddf99b99acb9c1d1a27d86d266a533cc01118369d941d8e8a78277a83cad3315e267a398c509d930fbb86504e193ea1ebc620a4a4212e06fe76e8be + languageName: node + linkType: hard + "cssdb@npm:^4.4.0": version: 4.4.0 resolution: "cssdb@npm:4.4.0" @@ -11159,6 +11318,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.3.3": + version: 4.3.4 + resolution: "debug@npm:4.3.4" + dependencies: + ms: 2.1.2 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 3dbad3f94ea64f34431a9cbf0bafb61853eda57bff2880036153438f50fb5a84f27683ba0d8e5426bf41a8c6ff03879488120cf5b3a761e77953169c0600a708 + languageName: node + linkType: hard + "decamelize@npm:^1.2.0": version: 1.2.0 resolution: "decamelize@npm:1.2.0" @@ -11491,6 +11662,13 @@ __metadata: languageName: node linkType: hard +"diff-sequences@npm:^27.5.1": + version: 27.5.1 + resolution: "diff-sequences@npm:27.5.1" + checksum: a00db5554c9da7da225db2d2638d85f8e41124eccbd56cbaefb3b276dcbb1c1c2ad851c32defe2055a54a4806f030656cbf6638105fd6ce97bb87b90b32a33ca + languageName: node + linkType: hard + "diff@npm:^4.0.1": version: 4.0.2 resolution: "diff@npm:4.0.2" @@ -14153,6 +14331,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"graphql@npm:^16.3.0": + version: 16.3.0 + resolution: "graphql@npm:16.3.0" + checksum: ba540641e9cd2a8de5b989ff1433b015f232fa73aaef478d6709c1339cd43113347917acb965a5799c004667687852fc8ff0cfaa935eb26374c91c1fd7fdaeb1 + languageName: node + linkType: hard + "gray-matter@npm:^4.0.3": version: 4.0.3 resolution: "gray-matter@npm:4.0.3" @@ -14458,6 +14643,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"headers-polyfill@npm:^3.0.4": + version: 3.0.7 + resolution: "headers-polyfill@npm:3.0.7" + checksum: ee392c1acdd2be797090837a085b14c8dc79f221e2501508afc1474667fba0627d583e06f9ab5cad57cf9dd570942d3166f791c75b92522a17c69c1e6bfcfbc4 + languageName: node + linkType: hard + "headers-utils@npm:^1.2.0": version: 1.2.5 resolution: "headers-utils@npm:1.2.5" @@ -15239,6 +15431,28 @@ fsevents@^1.2.7: languageName: node linkType: hard +"inquirer@npm:^8.2.0": + version: 8.2.2 + resolution: "inquirer@npm:8.2.2" + dependencies: + ansi-escapes: ^4.2.1 + chalk: ^4.1.1 + cli-cursor: ^3.1.0 + cli-width: ^3.0.0 + external-editor: ^3.0.3 + figures: ^3.0.0 + lodash: ^4.17.21 + mute-stream: 0.0.8 + ora: ^5.4.1 + run-async: ^2.4.0 + rxjs: ^7.5.5 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + through: ^2.3.6 + checksum: 69a2cf32f51af0e94dd66c597fdca42b890ff521b537dbfe1fd532c19a751d54893b7896523691ec30357f6212a80a2417fec7bf34411f369bbf151bdbc95ae9 + languageName: node + linkType: hard + "internal-ip@npm:^4.3.0": version: 4.3.0 resolution: "internal-ip@npm:4.3.0" @@ -15674,6 +15888,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"is-node-process@npm:^1.0.1": + version: 1.0.1 + resolution: "is-node-process@npm:1.0.1" + checksum: 3ddb8a892a00f6eb9c2aea7e7e1426b8683512d9419933d95114f4f64b5455e26601c23a31c0682463890032136dd98a326988a770ab6b4eed54a43ade8bed50 + languageName: node + linkType: hard + "is-npm@npm:^5.0.0": version: 5.0.0 resolution: "is-npm@npm:5.0.0" @@ -16206,6 +16427,18 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-diff@npm:^27.5.1": + version: 27.5.1 + resolution: "jest-diff@npm:27.5.1" + dependencies: + chalk: ^4.0.0 + diff-sequences: ^27.5.1 + jest-get-type: ^27.5.1 + pretty-format: ^27.5.1 + checksum: 8be27c1e1ee57b2bb2bef9c0b233c19621b4c43d53a3c26e2c00a4e805eb4ea11fe1694a06a9fb0e80ffdcfdc0d2b1cb0b85920b3f5c892327ecd1e7bd96b865 + languageName: node + linkType: hard + "jest-docblock@npm:^26.0.0": version: 26.0.0 resolution: "jest-docblock@npm:26.0.0" @@ -16271,6 +16504,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-get-type@npm:^27.5.1": + version: 27.5.1 + resolution: "jest-get-type@npm:27.5.1" + checksum: 63064ab70195c21007d897c1157bf88ff94a790824a10f8c890392e7d17eda9c3900513cb291ca1c8d5722cad79169764e9a1279f7c8a9c4cd6e9109ff04bbc0 + languageName: node + linkType: hard + "jest-haste-map@npm:^26.6.2": version: 26.6.2 resolution: "jest-haste-map@npm:26.6.2" @@ -16356,6 +16596,18 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-matcher-utils@npm:^27.0.0": + version: 27.5.1 + resolution: "jest-matcher-utils@npm:27.5.1" + dependencies: + chalk: ^4.0.0 + jest-diff: ^27.5.1 + jest-get-type: ^27.5.1 + pretty-format: ^27.5.1 + checksum: bb2135fc48889ff3fe73888f6cc7168ddab9de28b51b3148f820c89fdfd2effdcad005f18be67d0b9be80eda208ad47290f62f03d0a33f848db2dd0273c8217a + languageName: node + linkType: hard + "jest-message-util@npm:^24.9.0": version: 24.9.0 resolution: "jest-message-util@npm:24.9.0" @@ -18160,6 +18412,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"min-indent@npm:^1.0.0": + version: 1.0.1 + resolution: "min-indent@npm:1.0.1" + checksum: bfc6dd03c5eaf623a4963ebd94d087f6f4bbbfd8c41329a7f09706b0cb66969c4ddd336abeb587bc44bc6f08e13bf90f0b374f9d71f9f01e04adc2cd6f083ef1 + languageName: node + linkType: hard + "mini-create-react-context@npm:^0.4.0": version: 0.4.1 resolution: "mini-create-react-context@npm:0.4.1" @@ -18491,6 +18750,35 @@ fsevents@^1.2.7: languageName: node linkType: hard +"msw@npm:^0.39.2": + version: 0.39.2 + resolution: "msw@npm:0.39.2" + dependencies: + "@mswjs/cookies": ^0.2.0 + "@mswjs/interceptors": ^0.15.1 + "@open-draft/until": ^1.0.3 + "@types/cookie": ^0.4.1 + "@types/js-levenshtein": ^1.1.1 + chalk: 4.1.1 + chokidar: ^3.4.2 + cookie: ^0.4.2 + graphql: ^16.3.0 + headers-polyfill: ^3.0.4 + inquirer: ^8.2.0 + is-node-process: ^1.0.1 + js-levenshtein: ^1.1.6 + node-fetch: ^2.6.7 + path-to-regexp: ^6.2.0 + statuses: ^2.0.0 + strict-event-emitter: ^0.2.0 + type-fest: ^1.2.2 + yargs: ^17.3.1 + bin: + msw: cli/index.js + checksum: 4802f5568cbaadedd488f03b953523fb5dd7e1b8e48a85f142d7cfd1b8c25241729a0af4a06b9f2be543c18b67475c8777fa4924bdc6f1de19dbe142ea4a8405 + languageName: node + linkType: hard + "multicast-dns-service-types@npm:^1.1.0": version: 1.1.0 resolution: "multicast-dns-service-types@npm:1.1.0" @@ -19394,6 +19682,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"outvariant@npm:^1.2.1": + version: 1.3.0 + resolution: "outvariant@npm:1.3.0" + checksum: ac76ca375c1c642989e1c74f0e9ebac84c05bc9fdc8f28be949c16fae1658e9f1f2fb1133fe3cc1e98afabef78fe4298fe9360b5734baf8e6ad440c182680848 + languageName: node + linkType: hard + "p-cancelable@npm:^1.0.0": version: 1.1.0 resolution: "p-cancelable@npm:1.1.0" @@ -19833,6 +20128,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"path-to-regexp@npm:^6.2.0": + version: 6.2.0 + resolution: "path-to-regexp@npm:6.2.0" + checksum: a6aca74d2d6e2e7594d812f653cf85e9cb5054d3a8d80f099722a44ef6ad22639b02078e5ea83d11db16321c3e4359e3f1ab0274fa78dad0754a6e53f630b0fc + languageName: node + linkType: hard + "path-type@npm:^3.0.0": version: 3.0.0 resolution: "path-type@npm:3.0.0" @@ -21441,6 +21743,17 @@ fsevents@^1.2.7: languageName: node linkType: hard +"pretty-format@npm:^27.0.0, pretty-format@npm:^27.5.1": + version: 27.5.1 + resolution: "pretty-format@npm:27.5.1" + dependencies: + ansi-regex: ^5.0.1 + ansi-styles: ^5.0.0 + react-is: ^17.0.1 + checksum: cf610cffcb793885d16f184a62162f2dd0df31642d9a18edf4ca298e909a8fe80bdbf556d5c9573992c102ce8bf948691da91bf9739bee0ffb6e79c8a8a6e088 + languageName: node + linkType: hard + "pretty-format@npm:^27.0.2": version: 27.0.6 resolution: "pretty-format@npm:27.0.6" @@ -22547,6 +22860,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"redent@npm:^3.0.0": + version: 3.0.0 + resolution: "redent@npm:3.0.0" + dependencies: + indent-string: ^4.0.0 + strip-indent: ^3.0.0 + checksum: fa1ef20404a2d399235e83cc80bd55a956642e37dd197b4b612ba7327bf87fa32745aeb4a1634b2bab25467164ab4ed9c15be2c307923dd08b0fe7c52431ae6b + languageName: node + linkType: hard + "redux-persist@npm:^6.0.0": version: 6.0.0 resolution: "redux-persist@npm:6.0.0" @@ -23572,6 +23895,15 @@ resolve@~1.19.0: languageName: node linkType: hard +"rxjs@npm:^7.5.5": + version: 7.5.5 + resolution: "rxjs@npm:7.5.5" + dependencies: + tslib: ^2.1.0 + checksum: e034f60805210cce756dd2f49664a8108780b117cf5d0e2281506e9e6387f7b4f1532d974a8c8b09314fa7a16dd2f6cff3462072a5789672b5dcb45c4173f3c6 + languageName: node + linkType: hard + "sade@npm:^1.7.4": version: 1.7.4 resolution: "sade@npm:1.7.4" @@ -24419,6 +24751,16 @@ resolve@~1.19.0: languageName: node linkType: hard +"source-map-resolve@npm:^0.6.0": + version: 0.6.0 + resolution: "source-map-resolve@npm:0.6.0" + dependencies: + atob: ^2.1.2 + decode-uri-component: ^0.2.0 + checksum: fe503b9e5dac1c54be835282fcfec10879434e7b3ee08a9774f230299c724a8d403484d9531276d1670c87390e0e4d1d3f92b14cca6e4a2445ea3016b786ecd4 + languageName: node + linkType: hard + "source-map-support@npm:0.5.19": version: 0.5.19 resolution: "source-map-support@npm:0.5.19" @@ -24822,6 +25164,17 @@ resolve@~1.19.0: languageName: node linkType: hard +"string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: ^8.0.0 + is-fullwidth-code-point: ^3.0.0 + strip-ansi: ^6.0.1 + checksum: e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb + languageName: node + linkType: hard + "string.prototype.matchall@npm:^4.0.5": version: 4.0.5 resolution: "string.prototype.matchall@npm:4.0.5" @@ -24923,6 +25276,15 @@ resolve@~1.19.0: languageName: node linkType: hard +"strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: ^5.0.1 + checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c + languageName: node + linkType: hard + "strip-bom-string@npm:^1.0.0": version: 1.0.0 resolution: "strip-bom-string@npm:1.0.0" @@ -24968,6 +25330,15 @@ resolve@~1.19.0: languageName: node linkType: hard +"strip-indent@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-indent@npm:3.0.0" + dependencies: + min-indent: ^1.0.0 + checksum: 18f045d57d9d0d90cd16f72b2313d6364fd2cb4bf85b9f593523ad431c8720011a4d5f08b6591c9d580f446e78855c5334a30fb91aa1560f5d9f95ed1b4a0530 + languageName: node + linkType: hard + "strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1, strip-json-comments@npm:~3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" @@ -25955,6 +26326,13 @@ resolve@~1.19.0: languageName: node linkType: hard +"type-fest@npm:^1.2.2": + version: 1.4.0 + resolution: "type-fest@npm:1.4.0" + checksum: b011c3388665b097ae6a109a437a04d6f61d81b7357f74cbcb02246f2f5bd72b888ae33631b99871388122ba0a87f4ff1c94078e7119ff22c70e52c0ff828201 + languageName: node + linkType: hard + "type-is@npm:~1.6.17, type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -27844,6 +28222,13 @@ resolve@~1.19.0: languageName: node linkType: hard +"yargs-parser@npm:^21.0.0": + version: 21.0.1 + resolution: "yargs-parser@npm:21.0.1" + checksum: c3ea2ed12cad0377ce3096b3f138df8267edf7b1aa7d710cd502fe16af417bafe4443dd71b28158c22fcd1be5dfd0e86319597e47badf42ff83815485887323a + languageName: node + linkType: hard + "yargs@npm:^13.3.2": version: 13.3.2 resolution: "yargs@npm:13.3.2" @@ -27911,6 +28296,21 @@ resolve@~1.19.0: languageName: node linkType: hard +"yargs@npm:^17.3.1": + version: 17.4.0 + resolution: "yargs@npm:17.4.0" + dependencies: + cliui: ^7.0.2 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.3 + y18n: ^5.0.5 + yargs-parser: ^21.0.0 + checksum: 63985bddddf1fb6b9c98744591e8b70f99839591521cb84eea60903d52ec0da35ab46c42c325d151f3ab5c41935f976c10da096b5a7067c217714a91c0bd4be3 + languageName: node + linkType: hard + "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" From 5c2a6de1d5d9cfef48d2d486abc70fadd6d1d2d0 Mon Sep 17 00:00:00 2001 From: sidwebworks Date: Fri, 8 Apr 2022 22:44:14 +0530 Subject: [PATCH 4/6] chore(docs): fix codesandbox dependencies --- .../query/react/kitchen-sink/package.json | 5 ++- yarn.lock | 42 ++++++++++++++++++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/examples/query/react/kitchen-sink/package.json b/examples/query/react/kitchen-sink/package.json index 50189532f..90c7584ee 100644 --- a/examples/query/react/kitchen-sink/package.json +++ b/examples/query/react/kitchen-sink/package.json @@ -6,10 +6,12 @@ "keywords": [], "main": "src/index.tsx", "dependencies": { - "@reduxjs/toolkit": "workspace:^", + "@reduxjs/toolkit": "1.8.1", "react": "17.0.0", "react-dom": "17.0.0", "react-redux": "7.2.2", + "msw": "^0.39.2", + "react-router-dom": "5.3.0", "react-scripts": "4.0.2" }, "devDependencies": { @@ -20,7 +22,6 @@ "@types/react": "17.0.0", "@types/react-dom": "17.0.0", "@types/react-redux": "7.1.9", - "msw": "^0.39.2", "typescript": "~4.2.4", "whatwg-fetch": "^3.4.1" }, diff --git a/yarn.lock b/yarn.lock index c67f0fa18..1071fb12a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3877,7 +3877,7 @@ __metadata: version: 0.0.0-use.local resolution: "@examples-query-react/kitchen-sink@workspace:examples/query/react/kitchen-sink" dependencies: - "@reduxjs/toolkit": "workspace:^" + "@reduxjs/toolkit": 1.8.1 "@testing-library/jest-dom": ^5.11.5 "@testing-library/react": ^12.0.0 "@types/jest": ^26.0.23 @@ -3889,6 +3889,7 @@ __metadata: react: 17.0.0 react-dom: 17.0.0 react-redux: 7.2.2 + react-router-dom: 5.3.0 react-scripts: 4.0.2 typescript: ~4.2.4 whatwg-fetch: ^3.4.1 @@ -5508,7 +5509,7 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@^1.6.0, @reduxjs/toolkit@^1.6.0-rc.1, @reduxjs/toolkit@^1.8.0, @reduxjs/toolkit@workspace:^, @reduxjs/toolkit@workspace:packages/toolkit": +"@reduxjs/toolkit@1.8.1, @reduxjs/toolkit@^1.6.0, @reduxjs/toolkit@^1.6.0-rc.1, @reduxjs/toolkit@^1.8.0, @reduxjs/toolkit@workspace:packages/toolkit": version: 0.0.0-use.local resolution: "@reduxjs/toolkit@workspace:packages/toolkit" dependencies: @@ -22511,6 +22512,23 @@ fsevents@^1.2.7: languageName: node linkType: hard +"react-router-dom@npm:5.3.0": + version: 5.3.0 + resolution: "react-router-dom@npm:5.3.0" + dependencies: + "@babel/runtime": ^7.12.13 + history: ^4.9.0 + loose-envify: ^1.3.1 + prop-types: ^15.6.2 + react-router: 5.2.1 + tiny-invariant: ^1.0.2 + tiny-warning: ^1.0.0 + peerDependencies: + react: ">=15" + checksum: 47584fd629ecca52398d7888cab193b8a74057cc99a7ef44410c405d4082f618c3c0399db5325bc3524f9c511404086169570013b61a94dfa6acdfdc850d7a1f + languageName: node + linkType: hard + "react-router@npm:5.2.0, react-router@npm:^5.2.0": version: 5.2.0 resolution: "react-router@npm:5.2.0" @@ -22531,6 +22549,26 @@ fsevents@^1.2.7: languageName: node linkType: hard +"react-router@npm:5.2.1": + version: 5.2.1 + resolution: "react-router@npm:5.2.1" + dependencies: + "@babel/runtime": ^7.12.13 + history: ^4.9.0 + hoist-non-react-statics: ^3.1.0 + loose-envify: ^1.3.1 + mini-create-react-context: ^0.4.0 + path-to-regexp: ^1.7.0 + prop-types: ^15.6.2 + react-is: ^16.6.0 + tiny-invariant: ^1.0.2 + tiny-warning: ^1.0.0 + peerDependencies: + react: ">=15" + checksum: 7daae084bf64531eb619cc5f4cc40ce5ae0a541b64f71d74ec71a38cbf6130ebdbb7cf38f157303fad5846deec259401f96c4d6c7386466dcc989719e01f9aaa + languageName: node + linkType: hard + "react-scripts@npm:4.0.2": version: 4.0.2 resolution: "react-scripts@npm:4.0.2" From e986023997256050b5c7d4b7fa61de2bda0c85c9 Mon Sep 17 00:00:00 2001 From: sidwebworks Date: Fri, 8 Apr 2022 22:49:35 +0530 Subject: [PATCH 5/6] ci(docs): update browserslist --- examples/query/react/kitchen-sink/package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/query/react/kitchen-sink/package.json b/examples/query/react/kitchen-sink/package.json index 90c7584ee..dda5c07c6 100644 --- a/examples/query/react/kitchen-sink/package.json +++ b/examples/query/react/kitchen-sink/package.json @@ -7,10 +7,10 @@ "main": "src/index.tsx", "dependencies": { "@reduxjs/toolkit": "1.8.1", + "msw": "^0.39.2", "react": "17.0.0", "react-dom": "17.0.0", "react-redux": "7.2.2", - "msw": "^0.39.2", "react-router-dom": "5.3.0", "react-scripts": "4.0.2" }, diff --git a/yarn.lock b/yarn.lock index 1071fb12a..3a5f5acde 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9453,9 +9453,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30000981, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001125, caniuse-lite@npm:^1.0.30001264, caniuse-lite@npm:^1.0.30001265": - version: 1.0.30001269 - resolution: "caniuse-lite@npm:1.0.30001269" - checksum: 23a6bd029c4120a084056dae4eeecc552eba55c434035306e7ee4060aed1b6babed5f6d832f6ba359f13376356464a1673bc6c625c2932e376156f7f7fa1a3c0 + version: 1.0.30001327 + resolution: "caniuse-lite@npm:1.0.30001327" + checksum: 789076fb889bd03515c4a3e2bfa09cd5b28439645173445147eb6ddfd8105c755e46dfda3de4b75edd2b71490864188bbfe8a2efe920c7998960b4e98916f518 languageName: node linkType: hard From 036c55e852459252aac4d2795132f986b3ba689d Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 22 May 2022 14:28:49 -0400 Subject: [PATCH 6/6] Move "RTKQ Examples" up a level --- website/sidebars.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/sidebars.json b/website/sidebars.json index 402853dda..ee09b6c1a 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -75,6 +75,7 @@ "items": [ "rtk-query/overview", "rtk-query/comparison", + "rtk-query/usage/examples", "rtk-query/usage-with-typescript", { "type": "category", @@ -99,8 +100,7 @@ "rtk-query/usage/customizing-create-api", "rtk-query/usage/customizing-queries", "rtk-query/usage/usage-without-react-hooks", - "rtk-query/usage/migrating-to-rtk-query", - "rtk-query/usage/examples" + "rtk-query/usage/migrating-to-rtk-query" ] }, {