mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-27 08:30:02 +03:00
chore(rtk-query): clean up rtk-query-imspector-monitor-demo and add post example
This commit is contained in:
parent
f145746c50
commit
d574358c5d
|
@ -70,8 +70,7 @@
|
||||||
"packages/redux-devtools/examples/counter",
|
"packages/redux-devtools/examples/counter",
|
||||||
"packages/redux-devtools/examples/todomvc",
|
"packages/redux-devtools/examples/todomvc",
|
||||||
"packages/redux-devtools/examples/rtk-query-polling",
|
"packages/redux-devtools/examples/rtk-query-polling",
|
||||||
"packages/redux-devtools-slider-monitor/examples/todomvc",
|
"packages/redux-devtools-slider-monitor/examples/todomvc"
|
||||||
"packages/redux-devtools-rtk-query-inspector-monitor/demo"
|
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
|
|
|
@ -18,13 +18,13 @@ Created by [FaberVitale](https://github.com/FaberVitale)
|
||||||
### npm
|
### npm
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm i @redux-devtools/rtk-query-inspector-monitor --save # npm
|
npm i @redux-devtools/rtk-query-inspector-monitor --save
|
||||||
```
|
```
|
||||||
|
|
||||||
### yarn
|
### yarn
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn add @redux-devtools/rtk-query-inspector-monitor # yarn
|
yarn add @redux-devtools/rtk-query-inspector-monitor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,11 +78,11 @@ See also
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [] display mutations
|
- [ ] display mutations
|
||||||
- [] filter by tags types
|
- [ ] filter by tags types
|
||||||
- [] download query.data
|
- [ ] download query.data
|
||||||
- [] upload query.data(?)
|
- [ ] upload query.data(?)
|
||||||
- [] refetch query button(?)
|
- [ ] refetch query button(?)
|
||||||
- ...suggestions are welcome
|
- ...suggestions are welcome
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,17 +6,19 @@
|
||||||
|
|
||||||
Run the following commands from redux-devtools monorepo root directory.
|
Run the following commands from redux-devtools monorepo root directory.
|
||||||
|
|
||||||
### 1. Install depedencies
|
### 1. Install monorepo depedencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Start demo
|
### 2. Install demo dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# working directory is monorepo root
|
yarn exec --cwd 'packages/redux-devtools-rtk-query-inspector-monitor/demo' yarn
|
||||||
yarn lerna run --parallel start \
|
```
|
||||||
--scope '@redux-devtools/rtk-query-inspector-monitor' \
|
### 3. Start demo
|
||||||
--scope 'rtk-query-imspector-monitor-demo'
|
|
||||||
|
```bash
|
||||||
|
yarn lerna run --stream start --scope '@redux-devtools/rtk-query-inspector-monitor'
|
||||||
```
|
```
|
||||||
|
|
|
@ -3,20 +3,27 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env SKIP_PREFLIGHT_CHECK=true react-scripts start",
|
"start": "cross-env SKIP_PREFLIGHT_CHECK=true react-scripts start",
|
||||||
"build:demo": "cross-env SKIP_PREFLIGHT_CHECK=true react-scripts build"
|
"build": "cross-env SKIP_PREFLIGHT_CHECK=true react-scripts build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@chakra-ui/react": "1.0.0",
|
||||||
|
"@emotion/react": "^11.4.0",
|
||||||
|
"@emotion/styled": "^11.3.0",
|
||||||
|
"@mswjs/data": "^0.3.0",
|
||||||
"@redux-devtools/core": "^3.9.0",
|
"@redux-devtools/core": "^3.9.0",
|
||||||
"@redux-devtools/dock-monitor": "^1.4.0",
|
"@redux-devtools/dock-monitor": "^1.4.0",
|
||||||
"@reduxjs/toolkit": "^1.6.0",
|
"@reduxjs/toolkit": "^1.6.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"devui": "^1.0.0-8",
|
"devui": "^1.0.0-8",
|
||||||
|
"framer-motion": "^2.9.5",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"msw": "0.28.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-base16-styling": "^0.8.0",
|
"react-base16-styling": "^0.8.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-json-tree": "^0.15.0",
|
"react-json-tree": "^0.15.0",
|
||||||
"react-redux": "^7.2.1",
|
"react-redux": "^7.2.1",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.2",
|
"react-scripts": "4.0.2",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-devtools-themes": "^1.0.0"
|
"redux-devtools-themes": "^1.0.0"
|
||||||
|
@ -25,6 +32,7 @@
|
||||||
"@types/react": "17.0.0",
|
"@types/react": "17.0.0",
|
||||||
"@types/react-dom": "17.0.0",
|
"@types/react-dom": "17.0.0",
|
||||||
"@types/react-redux": "7.1.9",
|
"@types/react-redux": "7.1.9",
|
||||||
|
"@types/react-router-dom": "5.1.6",
|
||||||
"typescript": "~4.0.7"
|
"typescript": "~4.0.7"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
@ -37,5 +45,8 @@
|
||||||
"not dead",
|
"not dead",
|
||||||
"not ie <= 11",
|
"not ie <= 11",
|
||||||
"not op_mini all"
|
"not op_mini all"
|
||||||
]
|
],
|
||||||
|
"msw": {
|
||||||
|
"workerDirectory": "public"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,322 @@
|
||||||
|
/**
|
||||||
|
* Mock Service Worker.
|
||||||
|
* @see https://github.com/mswjs/msw
|
||||||
|
* - Please do NOT modify this file.
|
||||||
|
* - Please do NOT serve this file on production.
|
||||||
|
*/
|
||||||
|
/* eslint-disable */
|
||||||
|
/* tslint:disable */
|
||||||
|
|
||||||
|
const INTEGRITY_CHECKSUM = '82ef9b96d8393b6da34527d1d6e19187'
|
||||||
|
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 "master" 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 resolveMasterClient(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 resolveMasterClient(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] Request handler function for "%s %s" has thrown the following exception:
|
||||||
|
|
||||||
|
${parsedBody.errorType}: ${parsedBody.message}
|
||||||
|
(see more detailed error stack trace in the mocked response body)
|
||||||
|
|
||||||
|
This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error.
|
||||||
|
If you wish to mock an error response, please refer to 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
|
||||||
|
|
||||||
|
// 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) => {
|
||||||
|
console.error(
|
||||||
|
'[MSW] Failed to mock a "%s" request to "%s": %s',
|
||||||
|
request.method,
|
||||||
|
request.url,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,58 +1,65 @@
|
||||||
|
import PokemonView from 'features/pokemon/PokemonView';
|
||||||
|
import PostsView from 'features/posts/PostsView';
|
||||||
|
import { Flex, Heading } from '@chakra-ui/react';
|
||||||
|
import { Link, UnorderedList, ListItem } from '@chakra-ui/react';
|
||||||
|
import { Code } from '@chakra-ui/react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Pokemon } from './Pokemon';
|
|
||||||
import { PokemonName, POKEMON_NAMES } from './pokemon.data';
|
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
|
|
||||||
const getRandomPokemonName = () =>
|
export function App() {
|
||||||
POKEMON_NAMES[Math.floor(Math.random() * POKEMON_NAMES.length)];
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
const [pokemon, setPokemon] = React.useState<PokemonName[]>(['bulbasaur']);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article>
|
<article>
|
||||||
<h1>RTK Query inspector monitor demo</h1>
|
<Heading as="h1">RTK Query inspector monitor demo</Heading>
|
||||||
<section className="App">
|
<PokemonView />
|
||||||
<h2>Pokemon polling demo</h2>
|
<PostsView />
|
||||||
<div className="demo-toolbar">
|
<Flex p="2" as="section" flexWrap="nowrap" flexDirection="column">
|
||||||
<button
|
<Heading as="h2">Dock controls</Heading>
|
||||||
onClick={() =>
|
|
||||||
setPokemon((prev) => [...prev, getRandomPokemonName()])
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Add random pokemon
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setPokemon((prev) => [...prev, 'bulbasaur'])}>
|
|
||||||
Add bulbasaur
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pokemon-list">
|
|
||||||
{pokemon.map((name, index) => (
|
|
||||||
<Pokemon key={index} name={name} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Dock controls</h2>
|
|
||||||
<pre>
|
<pre>
|
||||||
<code>
|
<Code>
|
||||||
{`toggleVisibilityKey="ctrl-h"\nchangePositionKey="ctrl-q"`}
|
{`toggleVisibilityKey="ctrl-h"\nchangePositionKey="ctrl-q"`}
|
||||||
</code>
|
</Code>
|
||||||
</pre>
|
</pre>
|
||||||
</section>
|
</Flex>
|
||||||
<footer>
|
<Flex p="2" as="footer">
|
||||||
<p>
|
<UnorderedList>
|
||||||
<a href="https://github.com/FaberVitale/redux-devtools/tree/feat/rtk-query-monitor/packages/redux-devtools-rtk-query-inspector-monitor/demo">
|
<ListItem>
|
||||||
|
<Link
|
||||||
|
className="link"
|
||||||
|
isExternal
|
||||||
|
href="https://github.com/FaberVitale/redux-devtools/tree/feat/rtk-query-monitor/packages/redux-devtools-rtk-query-inspector-monitor/demo"
|
||||||
|
>
|
||||||
demo source
|
demo source
|
||||||
</a>
|
</Link>
|
||||||
</p>
|
</ListItem>
|
||||||
<p>
|
<ListItem>
|
||||||
<a href="https://github.com/FaberVitale/redux-devtools/tree/feat/rtk-query-monitor/packages/redux-devtools-rtk-query-inspector-monitor">
|
<Link
|
||||||
|
className="link"
|
||||||
|
isExternal
|
||||||
|
href="https://github.com/FaberVitale/redux-devtools/tree/feat/rtk-query-monitor/packages/redux-devtools-rtk-query-inspector-monitor"
|
||||||
|
>
|
||||||
@redux-devtools/rtk-query-inspector-monitor source
|
@redux-devtools/rtk-query-inspector-monitor source
|
||||||
</a>
|
</Link>
|
||||||
</p>
|
</ListItem>
|
||||||
</footer>
|
<ListItem>
|
||||||
|
<Link
|
||||||
|
className="link"
|
||||||
|
isExternal
|
||||||
|
href="https://github.com/reduxjs/redux-toolkit/tree/master/examples/query/react/polling"
|
||||||
|
>
|
||||||
|
polling example
|
||||||
|
</Link>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Link
|
||||||
|
className="link"
|
||||||
|
isExternal
|
||||||
|
href="https://github.com/reduxjs/redux-toolkit/tree/master/examples/query/react/mutations"
|
||||||
|
>
|
||||||
|
mutations example
|
||||||
|
</Link>
|
||||||
|
</ListItem>
|
||||||
|
</UnorderedList>
|
||||||
|
</Flex>
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useGetPokemonByNameQuery } from './services/pokemon';
|
import { Button, Select } from '@chakra-ui/react';
|
||||||
import type { PokemonName } from './pokemon.data';
|
import { useGetPokemonByNameQuery } from '../../services/pokemon';
|
||||||
|
import type { PokemonName } from '../../pokemon.data';
|
||||||
|
|
||||||
const intervalOptions = [
|
const intervalOptions = [
|
||||||
{ label: 'Off', value: 0 },
|
{ label: 'Off', value: 0 },
|
||||||
|
@ -10,9 +11,6 @@ const intervalOptions = [
|
||||||
{ label: '1m', value: 60000 },
|
{ label: '1m', value: 60000 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const getRandomIntervalValue = () =>
|
|
||||||
intervalOptions[Math.floor(Math.random() * intervalOptions.length)].value;
|
|
||||||
|
|
||||||
export function Pokemon({ name }: { name: PokemonName }) {
|
export function Pokemon({ name }: { name: PokemonName }) {
|
||||||
const [pollingInterval, setPollingInterval] = useState(60000);
|
const [pollingInterval, setPollingInterval] = useState(60000);
|
||||||
|
|
||||||
|
@ -44,7 +42,7 @@ export function Pokemon({ name }: { name: PokemonName }) {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block' }}>Polling interval</label>
|
<label style={{ display: 'block' }}>Polling interval</label>
|
||||||
<select
|
<Select
|
||||||
value={pollingInterval}
|
value={pollingInterval}
|
||||||
onChange={({ target: { value } }) =>
|
onChange={({ target: { value } }) =>
|
||||||
setPollingInterval(Number(value))
|
setPollingInterval(Number(value))
|
||||||
|
@ -55,12 +53,12 @@ export function Pokemon({ name }: { name: PokemonName }) {
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button onClick={refetch} disabled={isFetching}>
|
<Button onClick={refetch} disabled={isFetching}>
|
||||||
{isFetching ? 'Loading' : 'Manually refetch'}
|
{isFetching ? 'Loading' : 'Manually refetch'}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
|
@ -0,0 +1,35 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Pokemon } from './Pokemon';
|
||||||
|
import { PokemonName, POKEMON_NAMES } from '../../pokemon.data';
|
||||||
|
import { Flex, Heading, Button } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const getRandomPokemonName = () =>
|
||||||
|
POKEMON_NAMES[Math.floor(Math.random() * POKEMON_NAMES.length)];
|
||||||
|
|
||||||
|
export default function PokemonView() {
|
||||||
|
const [pokemon, setPokemon] = React.useState<PokemonName[]>(['bulbasaur']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex p="2" as="section" flexWrap="nowrap" flexDirection="column">
|
||||||
|
<Heading as="h2">Pokemon polling demo</Heading>
|
||||||
|
<div className="demo-toolbar">
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
setPokemon((prev) => [...prev, getRandomPokemonName()])
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Add random pokemon
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setPokemon((prev) => [...prev, 'bulbasaur'])}>
|
||||||
|
Add bulbasaur
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pokemon-list">
|
||||||
|
{pokemon.map((name, index) => (
|
||||||
|
<Pokemon key={index} name={name} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
useDeletePostMutation,
|
||||||
|
useGetPostQuery,
|
||||||
|
useUpdatePostMutation,
|
||||||
|
} from 'services/posts';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
CloseButton,
|
||||||
|
Flex,
|
||||||
|
Heading,
|
||||||
|
Input,
|
||||||
|
Spacer,
|
||||||
|
Stack,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const EditablePostName = ({
|
||||||
|
name: initialName,
|
||||||
|
onUpdate,
|
||||||
|
onCancel,
|
||||||
|
isLoading = false,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
onUpdate: (name: string) => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
}) => {
|
||||||
|
const [name, setName] = useState(initialName);
|
||||||
|
|
||||||
|
const handleChange = ({
|
||||||
|
target: { value },
|
||||||
|
}: React.ChangeEvent<HTMLInputElement>) => setName(value);
|
||||||
|
|
||||||
|
const handleUpdate = () => onUpdate(name);
|
||||||
|
const handleCancel = () => onCancel();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex>
|
||||||
|
<Box flex={10}>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
onChange={handleChange}
|
||||||
|
value={name}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Spacer />
|
||||||
|
<Box>
|
||||||
|
<Stack spacing={4} direction="row" align="center">
|
||||||
|
<Button onClick={handleUpdate} isLoading={isLoading}>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
<CloseButton bg="red" onClick={handleCancel} disabled={isLoading} />
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PostJsonDetail = ({ id }: { id: string }) => {
|
||||||
|
const { data: post } = useGetPostQuery(id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box mt={5} bg="#eee">
|
||||||
|
<pre>{JSON.stringify(post, null, 2)}</pre>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PostDetail = () => {
|
||||||
|
const { id } = useParams<{ id: any }>();
|
||||||
|
const { push } = useHistory();
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
|
||||||
|
const { data: post, isLoading } = useGetPostQuery(id);
|
||||||
|
|
||||||
|
const [updatePost, { isLoading: isUpdating }] = useUpdatePostMutation();
|
||||||
|
|
||||||
|
const [deletePost, { isLoading: isDeleting }] = useDeletePostMutation();
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
return (
|
||||||
|
<Center h="200px">
|
||||||
|
<Heading size="md">
|
||||||
|
Post {id} is missing! Try reloading or selecting another post...
|
||||||
|
</Heading>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box p={4}>
|
||||||
|
{isEditing ? (
|
||||||
|
<EditablePostName
|
||||||
|
name={post.name}
|
||||||
|
onUpdate={async (name) => {
|
||||||
|
try {
|
||||||
|
await updatePost({ id, name }).unwrap();
|
||||||
|
} catch {
|
||||||
|
toast({
|
||||||
|
title: 'An error occurred',
|
||||||
|
description: "We couldn't save your changes, try again!",
|
||||||
|
status: 'error',
|
||||||
|
duration: 2000,
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsEditing(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCancel={() => setIsEditing(false)}
|
||||||
|
isLoading={isUpdating}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Flex>
|
||||||
|
<Box>
|
||||||
|
<Heading size="md">{post.name}</Heading>
|
||||||
|
</Box>
|
||||||
|
<Spacer />
|
||||||
|
<Box>
|
||||||
|
<Stack spacing={4} direction="row" align="center">
|
||||||
|
<Button
|
||||||
|
onClick={() => setIsEditing(true)}
|
||||||
|
disabled={isDeleting || isUpdating}
|
||||||
|
>
|
||||||
|
{isUpdating ? 'Updating...' : 'Edit'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => deletePost(id).then(() => push('/posts'))}
|
||||||
|
disabled={isDeleting}
|
||||||
|
colorScheme="red"
|
||||||
|
>
|
||||||
|
{isDeleting ? 'Deleting...' : 'Delete'}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
<PostJsonDetail id={post.id} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,158 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Divider,
|
||||||
|
Flex,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
Heading,
|
||||||
|
Input,
|
||||||
|
List,
|
||||||
|
ListIcon,
|
||||||
|
ListItem,
|
||||||
|
Spacer,
|
||||||
|
Stat,
|
||||||
|
StatLabel,
|
||||||
|
StatNumber,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { Route, Switch, useHistory } from 'react-router-dom';
|
||||||
|
import { MdBook } from 'react-icons/md';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Post, useAddPostMutation, useGetPostsQuery } from 'services/posts';
|
||||||
|
import { PostDetail } from './PostDetail';
|
||||||
|
|
||||||
|
const AddPost = () => {
|
||||||
|
const initialValue = { name: '' };
|
||||||
|
const [post, setPost] = useState<Pick<Post, 'name'>>(initialValue);
|
||||||
|
const [addPost, { isLoading }] = useAddPostMutation();
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const handleChange = ({ target }: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setPost((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[target.name]: target.value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddPost = async () => {
|
||||||
|
try {
|
||||||
|
await addPost(post).unwrap();
|
||||||
|
setPost(initialValue);
|
||||||
|
} catch {
|
||||||
|
toast({
|
||||||
|
title: 'An error occurred',
|
||||||
|
description: "We couldn't save your post, try again!",
|
||||||
|
status: 'error',
|
||||||
|
duration: 2000,
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex p={5}>
|
||||||
|
<Box flex={10}>
|
||||||
|
<FormControl isInvalid={Boolean(post.name.length < 3 && post.name)}>
|
||||||
|
<FormLabel htmlFor="name">Post name</FormLabel>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
placeholder="Enter post name"
|
||||||
|
value={post.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
<Spacer />
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
mt={8}
|
||||||
|
colorScheme="purple"
|
||||||
|
isLoading={isLoading}
|
||||||
|
onClick={handleAddPost}
|
||||||
|
>
|
||||||
|
Add Post
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PostList = () => {
|
||||||
|
const { data: posts, isLoading } = useGetPostsQuery();
|
||||||
|
const { push } = useHistory();
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <div>Loading</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!posts) {
|
||||||
|
return <div>No posts :(</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List spacing={3}>
|
||||||
|
{posts.map(({ id, name }) => (
|
||||||
|
<ListItem key={id} onClick={() => push(`/posts/${id}`)}>
|
||||||
|
<ListIcon as={MdBook} color="green.500" /> {name}
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PostsCountStat = () => {
|
||||||
|
const { data: posts } = useGetPostsQuery();
|
||||||
|
|
||||||
|
if (!posts) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stat>
|
||||||
|
<StatLabel>Active Posts</StatLabel>
|
||||||
|
<StatNumber>{posts?.length}</StatNumber>
|
||||||
|
</Stat>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PostsManager = () => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Flex bg="#011627" p={4} color="white">
|
||||||
|
<Box>
|
||||||
|
<Heading size="xl">Manage Posts</Heading>
|
||||||
|
</Box>
|
||||||
|
<Spacer />
|
||||||
|
<Box>
|
||||||
|
<PostsCountStat />
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Divider />
|
||||||
|
<AddPost />
|
||||||
|
<Divider />
|
||||||
|
<Flex wrap="wrap">
|
||||||
|
<Box flex={1} borderRight="1px solid #eee">
|
||||||
|
<Box p={4} borderBottom="1px solid #eee">
|
||||||
|
<Heading size="sm">Posts</Heading>
|
||||||
|
</Box>
|
||||||
|
<Box p={4}>
|
||||||
|
<PostList />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box flex={2}>
|
||||||
|
<Switch>
|
||||||
|
<Route path="/posts/:id" component={PostDetail} />
|
||||||
|
<Route>
|
||||||
|
<Center h="200px">
|
||||||
|
<Heading size="md">Select a post to edit!</Heading>
|
||||||
|
</Center>
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PostsManager;
|
|
@ -0,0 +1,17 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Switch, Route } from 'react-router-dom';
|
||||||
|
import { PostsManager } from 'features/posts/PostsManager';
|
||||||
|
import { Box, Heading } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
function PostsView() {
|
||||||
|
return (
|
||||||
|
<Box as="section" p="2">
|
||||||
|
<Heading as="h2">Posts Demo</Heading>
|
||||||
|
<Switch>
|
||||||
|
<Route path="/" component={PostsManager} />
|
||||||
|
</Switch>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PostsView;
|
|
@ -13,6 +13,7 @@ code {
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
font-weight: 700;
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,11 +41,11 @@ h6 {
|
||||||
|
|
||||||
section {
|
section {
|
||||||
display: block;
|
display: block;
|
||||||
|
max-width: 67vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pokemon-list {
|
.pokemon-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: 80vw;
|
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
@ -59,5 +60,9 @@ pre code {
|
||||||
}
|
}
|
||||||
|
|
||||||
article {
|
article {
|
||||||
padding: 0.4em;
|
padding: 0 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link.link {
|
||||||
|
color: #805ad5;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,28 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import App from './App';
|
import { ChakraProvider } from '@chakra-ui/react';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import { store } from './store';
|
import { store } from './store';
|
||||||
import DevTools from './DevTools';
|
import DevTools from './DevTools';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import { App } from 'App';
|
||||||
|
import { worker } from './mocks/browser';
|
||||||
|
|
||||||
const rootElement = document.getElementById('root');
|
function renderApp() {
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
<ChakraProvider>
|
||||||
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
<DevTools />
|
<DevTools />
|
||||||
|
</BrowserRouter>
|
||||||
|
</ChakraProvider>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
rootElement
|
rootElement
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.start({ quiet: true }).then(renderApp);
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
|
||||||
<g fill="#61DAFB">
|
|
||||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4a43.8 43.8 0 00-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9a487.8 487.8 0 00-41.6-50c32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9a467 467 0 00-63.6 11c-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4a44 44 0 0022.5 5.6c27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7a450.4 450.4 0 01-13.5 39.5 473.3 473.3 0 00-27.5-47.4c14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5a532.7 532.7 0 01-24.1 38.2 520.3 520.3 0 01-90.2.1 551.2 551.2 0 01-45-77.8 521.5 521.5 0 0144.8-78.1 520.3 520.3 0 0190.2-.1 551.2 551.2 0 0145 77.8 560 560 0 01-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8a448.8 448.8 0 01-41.2 8 552.4 552.4 0 0027.4-47.8zM421.2 430a412.3 412.3 0 01-27.8-32 619 619 0 0055.3 0c-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9a451.2 451.2 0 01-41-7.9c3.7-12.9 8.3-26.2 13.5-39.5a473.3 473.3 0 0027.5 47.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32a619 619 0 00-55.3 0c9-11.7 18.3-22.4 27.5-32zm-74 58.9a552.4 552.4 0 00-27.4 47.7c-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9a473.5 473.5 0 00-22.2 60.6c-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9a487.8 487.8 0 0041.6 50c-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9a467 467 0 0063.6-11 280 280 0 015.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9a473.5 473.5 0 0022.2-60.6c9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
|
||||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1,4 @@
|
||||||
|
import { setupWorker } from 'msw';
|
||||||
|
import { handlers } from './db';
|
||||||
|
|
||||||
|
export const worker = setupWorker(...handlers);
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { factory, primaryKey } from '@mswjs/data';
|
||||||
|
import { nanoid } from '@reduxjs/toolkit';
|
||||||
|
import { rest } from 'msw';
|
||||||
|
import { Post } from '../services/posts';
|
||||||
|
|
||||||
|
const db = factory({
|
||||||
|
post: {
|
||||||
|
id: primaryKey(String),
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
'A sample post',
|
||||||
|
'A post about RTK Query',
|
||||||
|
'How to randomly throw errors, a novella',
|
||||||
|
].forEach((name) => {
|
||||||
|
db.post.create({ id: nanoid(), name });
|
||||||
|
});
|
||||||
|
|
||||||
|
export const handlers = [
|
||||||
|
rest.post('/posts', async (req, res, ctx) => {
|
||||||
|
const { name } = req.body as Partial<Post>;
|
||||||
|
|
||||||
|
if (Math.random() < 0.3) {
|
||||||
|
return res(
|
||||||
|
ctx.json({ error: 'Oh no, there was an error, try again.' }),
|
||||||
|
ctx.status(500),
|
||||||
|
ctx.delay(300)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = db.post.create({
|
||||||
|
id: nanoid(),
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res(ctx.json(post), ctx.delay(300));
|
||||||
|
}),
|
||||||
|
rest.put('/posts/:id', (req, res, ctx) => {
|
||||||
|
const { name } = req.body as Partial<Post>;
|
||||||
|
|
||||||
|
if (Math.random() < 0.3) {
|
||||||
|
return res(
|
||||||
|
ctx.json({ error: 'Oh no, there was an error, try again.' }),
|
||||||
|
ctx.status(500),
|
||||||
|
ctx.delay(300)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = db.post.update({
|
||||||
|
where: { id: { equals: req.params.id } },
|
||||||
|
data: { name },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res(ctx.json(post), ctx.delay(300));
|
||||||
|
}),
|
||||||
|
...db.post.toHandlers('rest'),
|
||||||
|
] as const;
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||||
|
|
||||||
|
export interface Post {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostsResponse = Post[];
|
||||||
|
|
||||||
|
export const postsApi = createApi({
|
||||||
|
reducerPath: 'postsApi',
|
||||||
|
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
|
||||||
|
tagTypes: ['Post'],
|
||||||
|
endpoints: (build) => ({
|
||||||
|
getPosts: build.query<PostsResponse, void>({
|
||||||
|
query: () => 'posts',
|
||||||
|
providesTags: (result) =>
|
||||||
|
result
|
||||||
|
? [
|
||||||
|
...result.map(({ id }) => ({ type: 'Post' as const, id })),
|
||||||
|
{ type: 'Post', id: 'LIST' },
|
||||||
|
]
|
||||||
|
: [{ type: 'Post', id: 'LIST' }],
|
||||||
|
}),
|
||||||
|
addPost: build.mutation<Post, Partial<Post>>({
|
||||||
|
query: (body) => ({
|
||||||
|
url: `posts`,
|
||||||
|
method: 'POST',
|
||||||
|
body,
|
||||||
|
}),
|
||||||
|
invalidatesTags: [{ type: 'Post', id: 'LIST' }],
|
||||||
|
}),
|
||||||
|
getPost: build.query<Post, string>({
|
||||||
|
query: (id) => `posts/${id}`,
|
||||||
|
providesTags: (result, error, id) => [{ type: 'Post', id }],
|
||||||
|
}),
|
||||||
|
updatePost: build.mutation<void, Pick<Post, 'id'> & Partial<Post>>({
|
||||||
|
query: ({ id, ...patch }) => ({
|
||||||
|
url: `posts/${id}`,
|
||||||
|
method: 'PUT',
|
||||||
|
body: patch,
|
||||||
|
}),
|
||||||
|
invalidatesTags: (result, error, { id }) => [{ type: 'Post', id }],
|
||||||
|
}),
|
||||||
|
deletePost: build.mutation<{ success: boolean; id: number }, number>({
|
||||||
|
query(id) {
|
||||||
|
return {
|
||||||
|
url: `posts/${id}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
invalidatesTags: (result, error, id) => [{ type: 'Post', id }],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
useGetPostQuery,
|
||||||
|
useGetPostsQuery,
|
||||||
|
useAddPostMutation,
|
||||||
|
useUpdatePostMutation,
|
||||||
|
useDeletePostMutation,
|
||||||
|
} = postsApi;
|
|
@ -1,13 +1,18 @@
|
||||||
import { configureStore } from '@reduxjs/toolkit';
|
import { configureStore, Middleware } from '@reduxjs/toolkit';
|
||||||
import { pokemonApi } from './services/pokemon';
|
import { pokemonApi } from './services/pokemon';
|
||||||
|
import { postsApi } from 'services/posts';
|
||||||
import DevTools from './DevTools';
|
import DevTools from './DevTools';
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
[pokemonApi.reducerPath]: pokemonApi.reducer,
|
[pokemonApi.reducerPath]: pokemonApi.reducer,
|
||||||
|
[postsApi.reducerPath]: postsApi.reducer,
|
||||||
},
|
},
|
||||||
devTools: false,
|
devTools: false,
|
||||||
// adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
|
// adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware().concat(pokemonApi.middleware),
|
getDefaultMiddleware().concat([
|
||||||
|
pokemonApi.middleware,
|
||||||
|
postsApi.middleware,
|
||||||
|
] as Middleware[]),
|
||||||
enhancers: [DevTools.instrument()],
|
enhancers: [DevTools.instrument()],
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": ["dom", "es2015"],
|
"lib": ["dom", "es2015"],
|
||||||
"jsx": "react-jsx",
|
"jsx": "react",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
|
"baseUrl": "src",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -32,7 +32,9 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:types && npm run build:js",
|
"build": "npm run build:types && npm run build:js",
|
||||||
"build:types": "tsc --emitDeclarationOnly",
|
"build:types": "tsc --emitDeclarationOnly",
|
||||||
"start": "tsc -p ./tsconfig.dev.json --watch",
|
"start:demo": "cd demo && yarn start",
|
||||||
|
"start:ts": "tsc -p ./tsconfig.dev.json --watch",
|
||||||
|
"start": "run-p start:ts start:demo",
|
||||||
"build:js": "babel src --out-dir lib --extensions \".ts,.tsx\" --source-maps inline",
|
"build:js": "babel src --out-dir lib --extensions \".ts,.tsx\" --source-maps inline",
|
||||||
"clean": "rimraf lib",
|
"clean": "rimraf lib",
|
||||||
"lint": "eslint . --ext .ts,.tsx",
|
"lint": "eslint . --ext .ts,.tsx",
|
||||||
|
@ -56,6 +58,7 @@
|
||||||
"@redux-devtools/core": "^3.9.0",
|
"@redux-devtools/core": "^3.9.0",
|
||||||
"@reduxjs/toolkit": "^1.6.0",
|
"@reduxjs/toolkit": "^1.6.0",
|
||||||
"@types/react": "^16.9.46",
|
"@types/react": "^16.9.46",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"redux": "^4.0.5"
|
"redux": "^4.0.5"
|
||||||
},
|
},
|
||||||
|
|
126
yarn.lock
126
yarn.lock
|
@ -4008,6 +4008,7 @@ __metadata:
|
||||||
"@types/redux-devtools-themes": ^1.0.0
|
"@types/redux-devtools-themes": ^1.0.0
|
||||||
devui: ^1.0.0-8
|
devui: ^1.0.0-8
|
||||||
lodash.debounce: ^4.0.8
|
lodash.debounce: ^4.0.8
|
||||||
|
npm-run-all: ^4.1.5
|
||||||
prop-types: ^15.7.2
|
prop-types: ^15.7.2
|
||||||
react: ^16.13.1
|
react: ^16.13.1
|
||||||
react-json-tree: ^0.15.0
|
react-json-tree: ^0.15.0
|
||||||
|
@ -19787,6 +19788,13 @@ fsevents@^1.2.7:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"memorystream@npm:^0.3.1":
|
||||||
|
version: 0.3.1
|
||||||
|
resolution: "memorystream@npm:0.3.1"
|
||||||
|
checksum: 825bcc7d3eb8bd021a1b0f8c81e4d7a8dc2eced1f8bb79d41ec978547cf118146d6863f5e6134f02bb55ee5d963a8689793e6e82ce8eb989bac339ae782728bb
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"meow@npm:^3.3.0":
|
"meow@npm:^3.3.0":
|
||||||
version: 3.7.0
|
version: 3.7.0
|
||||||
resolution: "meow@npm:3.7.0"
|
resolution: "meow@npm:3.7.0"
|
||||||
|
@ -21092,46 +21100,24 @@ fsevents@^1.2.7:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"npm-registry-client@npm:~7.2.1":
|
"npm-run-all@npm:^4.1.5":
|
||||||
version: 7.2.1
|
version: 4.1.5
|
||||||
resolution: "npm-registry-client@npm:7.2.1"
|
resolution: "npm-run-all@npm:4.1.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
concat-stream: ^1.5.2
|
ansi-styles: ^3.2.1
|
||||||
graceful-fs: ^4.1.6
|
chalk: ^2.4.1
|
||||||
normalize-package-data: ~1.0.1 || ^2.0.0
|
cross-spawn: ^6.0.5
|
||||||
npm-package-arg: ^3.0.0 || ^4.0.0
|
memorystream: ^0.3.1
|
||||||
npmlog: ~2.0.0 || ~3.1.0
|
minimatch: ^3.0.4
|
||||||
once: ^1.3.3
|
pidtree: ^0.3.0
|
||||||
request: ^2.74.0
|
read-pkg: ^3.0.0
|
||||||
retry: ^0.10.0
|
shell-quote: ^1.6.1
|
||||||
semver: 2 >=2.2.1 || 3.x || 4 || 5
|
string.prototype.padend: ^3.0.0
|
||||||
slide: ^1.1.3
|
bin:
|
||||||
dependenciesMeta:
|
npm-run-all: bin/npm-run-all/index.js
|
||||||
npmlog:
|
run-p: bin/run-p/index.js
|
||||||
optional: true
|
run-s: bin/run-s/index.js
|
||||||
checksum: 2f172e9d4a90e7c5172a705af0db10c96d8ec55ede6141c7fe4116382b492c0e6744b759347111bff0cc1a7d9e4e8b5185007b74b2dbb912de16b1bb4331959d
|
checksum: ef1b5b5a5fe7864d2b45c13de6dbffacde956bfc265117e0d1c8b05ee34264d494e5e65474d46592228e3a00857eae58359782fe7889d73de0a8714e6f9c0e83
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"npm-registry-client@npm:~8.4.0":
|
|
||||||
version: 8.4.0
|
|
||||||
resolution: "npm-registry-client@npm:8.4.0"
|
|
||||||
dependencies:
|
|
||||||
concat-stream: ^1.5.2
|
|
||||||
graceful-fs: ^4.1.6
|
|
||||||
normalize-package-data: ~1.0.1 || ^2.0.0
|
|
||||||
npm-package-arg: ^3.0.0 || ^4.0.0 || ^5.0.0
|
|
||||||
npmlog: 2 || ^3.1.0 || ^4.0.0
|
|
||||||
once: ^1.3.3
|
|
||||||
request: ^2.74.0
|
|
||||||
retry: ^0.10.0
|
|
||||||
semver: 2 >=2.2.1 || 3.x || 4 || 5
|
|
||||||
slide: ^1.1.3
|
|
||||||
ssri: ^4.1.2
|
|
||||||
dependenciesMeta:
|
|
||||||
npmlog:
|
|
||||||
optional: true
|
|
||||||
checksum: f16d83cf8eed879080dd758a00995864c77613ed964cc36ab0e9d9ed00d246e6dc31bf2959caf09284710676efb2e8cbc90e30f8b1d30860494a17c1b83dc5f3
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
@ -22384,6 +22370,15 @@ fsevents@^1.2.7:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"pidtree@npm:^0.3.0":
|
||||||
|
version: 0.3.1
|
||||||
|
resolution: "pidtree@npm:0.3.1"
|
||||||
|
bin:
|
||||||
|
pidtree: bin/pidtree.js
|
||||||
|
checksum: 8a48f063cb60e188bc94c307a309d309e20e9a3c3ca3537a035baf66dba2315f7b175d3a13a3b816db349dad270e347877b5aeae6d763360be650b3d1b1ca9b3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"pify@npm:^2.0.0, pify@npm:^2.3.0":
|
"pify@npm:^2.0.0, pify@npm:^2.3.0":
|
||||||
version: 2.3.0
|
version: 2.3.0
|
||||||
resolution: "pify@npm:2.3.0"
|
resolution: "pify@npm:2.3.0"
|
||||||
|
@ -24392,7 +24387,7 @@ fsevents@^1.2.7:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-dom@npm:^17.0.0, react-dom@npm:^17.0.2":
|
"react-dom@npm:^17.0.0":
|
||||||
version: 17.0.2
|
version: 17.0.2
|
||||||
resolution: "react-dom@npm:17.0.2"
|
resolution: "react-dom@npm:17.0.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -24645,7 +24640,7 @@ fsevents@^1.2.7:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-redux@npm:^7.2.1, react-redux@npm:^7.2.4":
|
"react-redux@npm:^7.2.4":
|
||||||
version: 7.2.4
|
version: 7.2.4
|
||||||
resolution: "react-redux@npm:7.2.4"
|
resolution: "react-redux@npm:7.2.4"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -24890,7 +24885,7 @@ fsevents@^1.2.7:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react@npm:^17.0.0, react@npm:^17.0.2":
|
"react@npm:^17.0.0":
|
||||||
version: 17.0.2
|
version: 17.0.2
|
||||||
resolution: "react@npm:17.0.2"
|
resolution: "react@npm:17.0.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -26159,31 +26154,6 @@ resolve@^2.0.0-next.3:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"rtk-query-imspector-monitor-demo@workspace:packages/redux-devtools-rtk-query-inspector-monitor/demo":
|
|
||||||
version: 0.0.0-use.local
|
|
||||||
resolution: "rtk-query-imspector-monitor-demo@workspace:packages/redux-devtools-rtk-query-inspector-monitor/demo"
|
|
||||||
dependencies:
|
|
||||||
"@redux-devtools/core": ^3.9.0
|
|
||||||
"@redux-devtools/dock-monitor": ^1.4.0
|
|
||||||
"@reduxjs/toolkit": ^1.6.0
|
|
||||||
"@types/react": 17.0.0
|
|
||||||
"@types/react-dom": 17.0.0
|
|
||||||
"@types/react-redux": 7.1.9
|
|
||||||
cross-env: ^7.0.3
|
|
||||||
devui: ^1.0.0-8
|
|
||||||
lodash.debounce: ^4.0.8
|
|
||||||
react: ^17.0.2
|
|
||||||
react-base16-styling: ^0.8.0
|
|
||||||
react-dom: ^17.0.2
|
|
||||||
react-json-tree: ^0.15.0
|
|
||||||
react-redux: ^7.2.1
|
|
||||||
react-scripts: 4.0.2
|
|
||||||
redux: ^4.0.5
|
|
||||||
redux-devtools-themes: ^1.0.0
|
|
||||||
typescript: ~4.0.7
|
|
||||||
languageName: unknown
|
|
||||||
linkType: soft
|
|
||||||
|
|
||||||
"rtk-query-polling@workspace:packages/redux-devtools/examples/rtk-query-polling":
|
"rtk-query-polling@workspace:packages/redux-devtools/examples/rtk-query-polling":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "rtk-query-polling@workspace:packages/redux-devtools/examples/rtk-query-polling"
|
resolution: "rtk-query-polling@workspace:packages/redux-devtools/examples/rtk-query-polling"
|
||||||
|
@ -26848,7 +26818,7 @@ resolve@^2.0.0-next.3:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"shell-quote@npm:1.7.2":
|
"shell-quote@npm:1.7.2, shell-quote@npm:^1.6.1":
|
||||||
version: 1.7.2
|
version: 1.7.2
|
||||||
resolution: "shell-quote@npm:1.7.2"
|
resolution: "shell-quote@npm:1.7.2"
|
||||||
checksum: 3b3d06814ca464cde8594c27bdd57a1f4c06b26ad2988b08b5819f97ac1edfd7cb7313fda1c909da33211972c72c5a7906b7da2b62078109f9d3274d3f404fa9
|
checksum: 3b3d06814ca464cde8594c27bdd57a1f4c06b26ad2988b08b5819f97ac1edfd7cb7313fda1c909da33211972c72c5a7906b7da2b62078109f9d3274d3f404fa9
|
||||||
|
@ -29314,26 +29284,6 @@ typescript@^4.3.4:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"typescript@patch:typescript@~4.0.7#builtin<compat/typescript>":
|
|
||||||
version: 4.0.8
|
|
||||||
resolution: "typescript@patch:typescript@npm%3A4.0.8#builtin<compat/typescript>::version=4.0.8&hash=ddfc1b"
|
|
||||||
bin:
|
|
||||||
tsc: bin/tsc
|
|
||||||
tsserver: bin/tsserver
|
|
||||||
checksum: d12e73e6fb00f0ed42b10b42493d2eb907f31b8c6eb6cfb896be45d79d8fcbf46c9bc1e2aced88898f91191e3f49c5a13d3f86d01bb386ee29f502c7ccfe0b6a
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
typescript@~4.0.7:
|
|
||||||
version: 4.0.8
|
|
||||||
resolution: "typescript@npm:4.0.8"
|
|
||||||
bin:
|
|
||||||
tsc: bin/tsc
|
|
||||||
tsserver: bin/tsserver
|
|
||||||
checksum: f7789f9c531dffcf4c849a806627562ad6297f608aab85c0514d87a2ab3e060bcfadd63963735994796c45326eebeb479c004065af47e72ee44ba8c935fc9a54
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"uglify-js@npm:^3.1.4":
|
"uglify-js@npm:^3.1.4":
|
||||||
version: 3.13.9
|
version: 3.13.9
|
||||||
resolution: "uglify-js@npm:3.13.9"
|
resolution: "uglify-js@npm:3.13.9"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user