Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

(examples) Add with-cache-updates example (#1611)

* (examples) Add with-cache-updates example

authored by

Yanko Valera and committed by
GitHub
a171bd00 1bed86d3

+380 -1
+25
examples/with-cache-updates/README.md
··· 1 + # Integrating with `@urql/exchange-graphcache`'s cacheExchange Cache Updates 2 + 3 + Integrating urql is as simple as: 4 + 5 + 1. Install packages [getting started](https://formidable.com/open-source/urql/docs/basics/react-preact/) 6 + 7 + ```sh 8 + yarn add urql graphql 9 + # or 10 + npm install --save urql graphql 11 + ``` 12 + 13 + 2. Install [graphcache](https://formidable.com/open-source/urql/docs/graphcache/) 14 + 15 + ```sh 16 + yarn add @urql/exchange-graphcache 17 + # or 18 + npm install --save @urql/exchange-graphcache 19 + ``` 20 + 21 + 3. Setting up the Client [here](src/App.js) 22 + 23 + 4. Configure the Client for handling cache updates [here](src/client/index.js#76) 24 + 25 + 5. Execute the create Mutation [here](src/pages/Links.js)
+40
examples/with-cache-updates/package.json
··· 1 + { 2 + "name": "react-urql-query", 3 + "version": "1.0.0", 4 + "private": true, 5 + "dependencies": { 6 + "@urql/exchange-auth": "^0.1.2", 7 + "@urql/exchange-graphcache": "^4.0.0", 8 + "graphql": "^15.5.0", 9 + "react": "^17.0.2", 10 + "react-dom": "^17.0.2", 11 + "urql": "^2.0.2" 12 + }, 13 + "devDependencies": { 14 + "react-scripts": "4.0.3" 15 + }, 16 + "scripts": { 17 + "start": "react-scripts start", 18 + "build": "react-scripts build", 19 + "test": "react-scripts test", 20 + "eject": "react-scripts eject" 21 + }, 22 + "eslintConfig": { 23 + "extends": [ 24 + "react-app", 25 + "react-app/jest" 26 + ] 27 + }, 28 + "browserslist": { 29 + "production": [ 30 + ">0.2%", 31 + "not dead", 32 + "not op_mini all" 33 + ], 34 + "development": [ 35 + "last 1 chrome version", 36 + "last 1 firefox version", 37 + "last 1 safari version" 38 + ] 39 + } 40 + }
+14
examples/with-cache-updates/src/App.js
··· 1 + import { Provider } from "urql"; 2 + 3 + import client from './client'; 4 + import Home from "./pages/Home"; 5 + 6 + function App() { 7 + return ( 8 + <Provider value={client}> 9 + <Home /> 10 + </Provider> 11 + ); 12 + } 13 + 14 + export default App;
+117
examples/with-cache-updates/src/client/index.js
··· 1 + import { createClient, dedupExchange, fetchExchange, gql } from 'urql'; 2 + import { makeOperation } from '@urql/core'; 3 + import { authExchange } from '@urql/exchange-auth'; 4 + import { cacheExchange } from '@urql/exchange-graphcache'; 5 + 6 + const cache = cacheExchange({ 7 + updates: { 8 + Mutation: { 9 + createLink(result, _args, cache, _info) { 10 + const LinksList = gql` 11 + query Links($first: Int!) { 12 + links(first: $first) { 13 + nodes { 14 + id 15 + } 16 + } 17 + } 18 + `; 19 + 20 + const linksPages = cache 21 + .inspectFields('Query') 22 + .filter(field => field.fieldName === 'links'); 23 + 24 + if (linksPages.length > 0) { 25 + const lastField = linksPages[linksPages.length - 1]; 26 + 27 + cache.updateQuery( 28 + { 29 + query: LinksList, 30 + variables: { first: lastField.arguments.first }, 31 + }, 32 + data => { 33 + data.links.nodes.push(result.createLink.node); 34 + return data; 35 + } 36 + ); 37 + } 38 + }, 39 + }, 40 + }, 41 + }); 42 + 43 + const client = createClient({ 44 + url: 'https://trygql.dev/graphql/web-collections', 45 + exchanges: [ 46 + dedupExchange, 47 + cache, 48 + authExchange({ 49 + getAuth: async ({ authState }) => { 50 + if (!authState) { 51 + const token = localStorage.getItem('authToken'); 52 + 53 + if (token) { 54 + return { token }; 55 + } 56 + 57 + return null; 58 + } 59 + 60 + // This is where auth has gone wrong and we need to clean up and redirect to a login page 61 + localStorage.clear(); 62 + window.location.reload(); 63 + 64 + return null; 65 + }, 66 + addAuthToOperation: ({ authState, operation }) => { 67 + if (!authState || !authState.token) { 68 + return operation; 69 + } 70 + 71 + const fetchOptions = 72 + typeof operation.context.fetchOptions === 'function' 73 + ? operation.context.fetchOptions() 74 + : operation.context.fetchOptions || {}; 75 + 76 + return makeOperation(operation.kind, operation, { 77 + ...operation.context, 78 + fetchOptions: { 79 + ...fetchOptions, 80 + headers: { 81 + ...fetchOptions.headers, 82 + Authorization: `Bearer ${authState.token}`, 83 + }, 84 + }, 85 + }); 86 + }, 87 + didAuthError: ({ error }) => { 88 + return error.graphQLErrors.some( 89 + e => e.extensions?.code === 'UNAUTHORIZED' 90 + ); 91 + }, 92 + willAuthError: ({ operation, authState }) => { 93 + if (!authState) { 94 + // Detect our login mutation and let this operation through: 95 + return ( 96 + operation.kind !== 'mutation' || 97 + // Here we find any mutation definition with the "signin" field 98 + !operation.query.definitions.some(definition => { 99 + return ( 100 + definition.kind === 'OperationDefinition' && 101 + definition.selectionSet.selections.some(node => { 102 + // The field name is just an example, since register may also be an exception 103 + return node.kind === 'Field' && node.name.value === 'signin'; 104 + }) 105 + ); 106 + }) 107 + ); 108 + } 109 + 110 + return false; 111 + }, 112 + }), 113 + fetchExchange, 114 + ], 115 + }); 116 + 117 + export default client;
+13
examples/with-cache-updates/src/index.css
··· 1 + body { 2 + margin: 0; 3 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 + sans-serif; 6 + -webkit-font-smoothing: antialiased; 7 + -moz-osx-font-smoothing: grayscale; 8 + } 9 + 10 + code { 11 + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 + monospace; 13 + }
+12
examples/with-cache-updates/src/index.js
··· 1 + import React from 'react'; 2 + import ReactDOM from 'react-dom'; 3 + import './index.css'; 4 + import App from './App'; 5 + 6 + ReactDOM.render( 7 + <React.StrictMode> 8 + <App /> 9 + </React.StrictMode>, 10 + document.getElementById('root') 11 + ); 12 +
+27
examples/with-cache-updates/src/pages/Home.js
··· 1 + import React, { useEffect, useState } from 'react'; 2 + 3 + import Links from './Links'; 4 + import LoginForm from './LoginForm'; 5 + 6 + const Home = () => { 7 + const [isLoggedIn, setIsLoggedIn] = useState(false); 8 + 9 + const onLoginSuccess = auth => { 10 + localStorage.setItem("authToken", auth.token); 11 + setIsLoggedIn(true); 12 + }; 13 + 14 + useEffect(() => { 15 + if (localStorage.getItem("authToken")) { 16 + setIsLoggedIn(true); 17 + } 18 + }, []); 19 + 20 + return isLoggedIn ? ( 21 + <Links /> 22 + ) : ( 23 + <LoginForm onLoginSuccess={onLoginSuccess} /> 24 + ); 25 + }; 26 + 27 + export default Home;
+74
examples/with-cache-updates/src/pages/Links.js
··· 1 + import React, {useEffect, useState} from "react"; 2 + import { gql, useQuery, useMutation } from "urql"; 3 + 4 + const LINKS_QUERY = gql` 5 + query Links($first: Int!) { 6 + links(first: $first) { 7 + nodes { 8 + id 9 + canonicalUrl 10 + } 11 + } 12 + } 13 + `; 14 + 15 + const CREATE_LINK_MUTATION = gql` 16 + mutation CreateLink($url: URL!) { 17 + createLink(url: $url) { 18 + node { 19 + id 20 + canonicalUrl 21 + } 22 + } 23 + } 24 + `; 25 + 26 + const Links = () => { 27 + const [newLink, setNewLink] = useState(""); 28 + 29 + const [linksResult] = useQuery({ query: LINKS_QUERY, variables: { first: 10 } }); 30 + const [createResult, createLink] = useMutation(CREATE_LINK_MUTATION) 31 + 32 + const onChangeLink = evt => { 33 + setNewLink(evt.target.value); 34 + } 35 + 36 + const onSubmit = evt => { 37 + evt.preventDefault(); 38 + createLink({ url: newLink }); 39 + }; 40 + 41 + useEffect(() => { 42 + if (createResult.data) { 43 + setNewLink(""); 44 + } 45 + }, [createResult]); 46 + 47 + if (linksResult.fetching || createResult.fetching) { 48 + return <p>Loading...</p> 49 + } 50 + 51 + return ( 52 + <div> 53 + {linksResult.error && <p>Oh no... {linksResult.error.message}</p>} 54 + 55 + {linksResult.data && ( 56 + <ul> 57 + {linksResult.data.links.nodes.map((link) => ( 58 + <li key={link.id}>{link.canonicalUrl}</li> 59 + ))} 60 + </ul> 61 + )} 62 + 63 + <form onSubmit={onSubmit}> 64 + {createResult.error && <p>Oh no... {createResult.error.message}</p>} 65 + 66 + <input onChange={onChangeLink} value={newLink} /> 67 + 68 + <input type="submit" title="add" /> 69 + </form> 70 + </div> 71 + ); 72 + }; 73 + 74 + export default Links;
+57
examples/with-cache-updates/src/pages/LoginForm.js
··· 1 + import React, { useEffect, useState } from 'react'; 2 + import { gql, useMutation } from 'urql'; 3 + 4 + const LOGIN_MUTATION = gql` 5 + mutation Signin($input: LoginInput!) { 6 + signin(input: $input) { 7 + refreshToken 8 + token 9 + } 10 + } 11 + `; 12 + 13 + const LoginForm = ({ onLoginSuccess }) => { 14 + const [password, setPassword] = useState(''); 15 + const [username, setUsername] = useState(''); 16 + 17 + const [loginResult, login] = useMutation(LOGIN_MUTATION); 18 + 19 + const { data, fetching, error } = loginResult; 20 + 21 + const onUsernameChange = evt => { 22 + setUsername(evt.target.value); 23 + }; 24 + 25 + const onPasswordChange = evt => { 26 + setPassword(evt.target.value); 27 + }; 28 + 29 + const onSubmit = evt => { 30 + evt.preventDefault(); 31 + login({ input: { username, password } }); 32 + }; 33 + 34 + useEffect(() => { 35 + if (data && data.signin) { 36 + onLoginSuccess(data.signin); 37 + } 38 + }, [onLoginSuccess, data]); 39 + 40 + if (fetching) { 41 + return <p>loading...</p>; 42 + } 43 + 44 + return ( 45 + <form onSubmit={onSubmit}> 46 + {error && <p>Oh no... {error.message}</p>} 47 + 48 + <input type="text" onChange={onUsernameChange} /> 49 + 50 + <input type="password" onChange={onPasswordChange} /> 51 + 52 + <input type="submit" title="login" /> 53 + </form> 54 + ); 55 + }; 56 + 57 + export default LoginForm;
+1 -1
examples/with-refresh-auth/src/client/index.js
··· 96 96 ); 97 97 } 98 98 99 - return true; 99 + return false; 100 100 }; 101 101 102 102 const client = createClient({