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

(examples) - Migrate to Vite and update some examples (#1630)

+3 -3
examples/with-apq/README.md
···
This example contains:
-
- The `urql` bindings and a React app with a client set up in [`src/App.js`](src/App.js)
-
- The `persistedFetchExchange` from `@urql/exchange-persisted-fetch` in [`src/App.js`](src/App.js)
-
- A query for locations in [`src/pages/LocationsList.js`](src/pages/LocationsList.js)
+
- The `urql` bindings and a React app with a client set up in [`src/App.jsx`](src/App.jsx)
+
- The `persistedFetchExchange` from `@urql/exchange-persisted-fetch` in [`src/App.jsx`](src/App.jsx)
+
- A query for locations in [`src/LocationsList.jsx`](src/pages/LocationsList.jsx)
+27
examples/with-apq/index.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>with-apq</title>
+
<style>
+
body {
+
margin: 0;
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+
sans-serif;
+
-webkit-font-smoothing: antialiased;
+
-moz-osx-font-smoothing: grayscale;
+
}
+
+
code {
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+
monospace;
+
}
+
</style>
+
</head>
+
<body>
+
<div id="root"></div>
+
<script type="module" src="/src/index.jsx"></script>
+
</body>
+
</html>
+5 -19
examples/with-apq/package.json
···
"name": "with-apq",
"version": "0.0.0",
"private": true,
+
"scripts": {
+
"start": "vite"
+
},
"dependencies": {
"@urql/exchange-persisted-fetch": "^1.3.0",
"graphql": "^15.5.0",
···
"urql": "^2.0.2"
},
"devDependencies": {
-
"react-scripts": "4.0.3"
-
},
-
"scripts": {
-
"start": "react-scripts start",
-
"build": "react-scripts build",
-
"test": "react-scripts test",
-
"eject": "react-scripts eject"
-
},
-
"browserslist": {
-
"production": [
-
">0.2%",
-
"not dead",
-
"not op_mini all"
-
],
-
"development": [
-
"last 1 chrome version",
-
"last 1 firefox version",
-
"last 1 safari version"
-
]
+
"@vitejs/plugin-react-refresh": "^1.3.3",
+
"vite": "^2.2.4"
}
}
+5 -4
examples/with-apq/src/App.js examples/with-apq/src/App.jsx
···
+
import React from 'react';
import { createClient, Provider, fetchExchange } from 'urql';
import { persistedFetchExchange } from '@urql/exchange-persisted-fetch';
-
import LocationsList from "./pages/LocationsList";
+
import LocationsList from './LocationsList';
const client = createClient({
-
url: "https://trygql.formidable.dev/graphql/apq-weather",
+
url: 'https://trygql.formidable.dev/graphql/apq-weather',
exchanges: [
persistedFetchExchange({
preferGetForPersistedQueries: true,
}),
-
fetchExchange
-
]
+
fetchExchange,
+
],
});
function App() {
-13
examples/with-apq/src/index.css
···
-
body {
-
margin: 0;
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-
sans-serif;
-
-webkit-font-smoothing: antialiased;
-
-moz-osx-font-smoothing: grayscale;
-
}
-
-
code {
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-
monospace;
-
}
+3 -4
examples/with-apq/src/index.js examples/with-apq/src/index.jsx
···
import React from 'react';
-
import ReactDOM from 'react-dom';
-
import './index.css';
+
import { render } from 'react-dom';
+
import App from './App';
-
ReactDOM.render(
+
render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
-
+7 -4
examples/with-apq/src/pages/LocationsList.js examples/with-apq/src/LocationsList.jsx
···
-
import React from "react";
-
import { gql, useQuery } from "urql";
+
import React from 'react';
+
import { gql, useQuery } from 'urql';
const LOCATIONS_QUERY = gql`
query Locations($query: String!) {
···
`;
const LocationsList = () => {
-
const [result] = useQuery({ query: LOCATIONS_QUERY, variables: { query: "LON"} });
+
const [result] = useQuery({
+
query: LOCATIONS_QUERY,
+
variables: { query: 'LON' },
+
});
const { data, fetching, error } = result;
···
{data && (
<ul>
-
{data.locations.map((location) => (
+
{data.locations.map(location => (
<li key={location.id}>{location.name}</li>
))}
</ul>
+7
examples/with-apq/vite.config.js
···
+
import { defineConfig } from 'vite';
+
import reactRefresh from '@vitejs/plugin-react-refresh';
+
+
// https://vitejs.dev/config/
+
export default defineConfig({
+
plugins: [reactRefresh()],
+
});
+27
examples/with-graphcache-pagination/index.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>with-graphcache-pagination</title>
+
<style>
+
body {
+
margin: 0;
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+
sans-serif;
+
-webkit-font-smoothing: antialiased;
+
-moz-osx-font-smoothing: grayscale;
+
}
+
+
code {
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+
monospace;
+
}
+
</style>
+
</head>
+
<body>
+
<div id="root"></div>
+
<script type="module" src="/src/index.jsx"></script>
+
</body>
+
</html>
+5 -19
examples/with-graphcache-pagination/package.json
···
"name": "with-graphcache-pagination",
"version": "0.0.0",
"private": true,
+
"scripts": {
+
"start": "vite"
+
},
"dependencies": {
"@urql/exchange-graphcache": "^4.0.0",
"graphql": "^15.5.0",
···
"urql": "^2.0.2"
},
"devDependencies": {
-
"react-scripts": "4.0.3"
-
},
-
"scripts": {
-
"start": "react-scripts start",
-
"build": "react-scripts build",
-
"test": "react-scripts test",
-
"eject": "react-scripts eject"
-
},
-
"browserslist": {
-
"production": [
-
">0.2%",
-
"not dead",
-
"not op_mini all"
-
],
-
"development": [
-
"last 1 chrome version",
-
"last 1 firefox version",
-
"last 1 safari version"
-
]
+
"@vitejs/plugin-react-refresh": "^1.3.3",
+
"vite": "^2.2.4"
}
}
+6 -5
examples/with-graphcache-pagination/src/App.js examples/with-graphcache-pagination/src/App.jsx
···
-
import { createClient, Provider, dedupExchange, fetchExchange } from "urql";
+
import React from 'react';
+
import { createClient, Provider, dedupExchange, fetchExchange } from 'urql';
import { cacheExchange } from '@urql/exchange-graphcache';
import { relayPagination } from '@urql/exchange-graphcache/extras';
-
import PaginatedNpmSearch from "./pages/PaginatedNpmSearch";
+
import PaginatedNpmSearch from './PaginatedNpmSearch';
const client = createClient({
-
url: "https://trygql.formidable.dev/graphql/relay-npm",
+
url: 'https://trygql.formidable.dev/graphql/relay-npm',
exchanges: [
dedupExchange,
cacheExchange({
···
},
},
}),
-
fetchExchange
-
]
+
fetchExchange,
+
],
});
function App() {
-13
examples/with-graphcache-pagination/src/index.css
···
-
body {
-
margin: 0;
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-
sans-serif;
-
-webkit-font-smoothing: antialiased;
-
-moz-osx-font-smoothing: grayscale;
-
}
-
-
code {
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-
monospace;
-
}
+3 -4
examples/with-graphcache-pagination/src/index.js examples/with-graphcache-pagination/src/index.jsx
···
import React from 'react';
-
import ReactDOM from 'react-dom';
-
import './index.css';
+
import { render } from 'react-dom';
+
import App from './App';
-
ReactDOM.render(
+
render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
-
+9 -8
examples/with-graphcache-pagination/src/pages/PaginatedNpmSearch.js examples/with-graphcache-pagination/src/PaginatedNpmSearch.jsx
···
-
import React, { useState } from "react";
-
import { gql, useQuery } from "urql";
+
import React, { useState } from 'react';
+
import { gql, useQuery } from 'urql';
const limit = 5;
-
const query = "graphql";
+
const query = 'graphql';
const NPM_SEARCH = gql`
query Search($query: String!, $first: Int!, $after: String) {
···
}
`;
-
const PaginatedNpmSearch = () => {
-
const [after, setAfter] = useState("");
+
const [after, setAfter] = useState('');
const [result] = useQuery({
-
query: NPM_SEARCH,
-
variables: { query, first: limit, after }
+
query: NPM_SEARCH,
+
variables: { query, first: limit, after },
});
const { data, fetching, error } = result;
···
{searchResults && (
<>
{searchResults.edges.map(({ node }) => (
-
<div key={node.id}>{node.id}: {node.name}</div>
+
<div key={node.id}>
+
{node.id}: {node.name}
+
</div>
))}
{searchResults.pageInfo.hasNextPage && (
+7
examples/with-graphcache-pagination/vite.config.js
···
+
import { defineConfig } from 'vite';
+
import reactRefresh from '@vitejs/plugin-react-refresh';
+
+
// https://vitejs.dev/config/
+
export default defineConfig({
+
plugins: [reactRefresh()],
+
});
+3 -3
examples/with-graphcache-updates/README.md
···
This example contains:
-
- The `urql` bindings and a React app with a client set up in [`src/client/index.js`](src/client/index.js)
-
- The `cacheExchange` from `@urql/exchange-graphcache` in [`src/client/index.js`](src/client/index.js)
-
- A links list and link creation in [`src/pages/Links.js`](src/pages/Links.js)
+
- The `urql` bindings and a React app with a client set up in [`src/client.js`](src/client.js)
+
- The `cacheExchange` from `@urql/exchange-graphcache` in [`src/client.js`](src/client.js)
+
- A links list and link creation in [`src/pages/Links.jsx`](src/pages/Links.jsx)
+27
examples/with-graphcache-updates/index.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>with-graphcache-updates</title>
+
<style>
+
body {
+
margin: 0;
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+
sans-serif;
+
-webkit-font-smoothing: antialiased;
+
-moz-osx-font-smoothing: grayscale;
+
}
+
+
code {
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+
monospace;
+
}
+
</style>
+
</head>
+
<body>
+
<div id="root"></div>
+
<script type="module" src="/src/index.jsx"></script>
+
</body>
+
</html>
+5 -19
examples/with-graphcache-updates/package.json
···
"name": "with-graphcache-updates",
"version": "0.0.0",
"private": true,
+
"scripts": {
+
"start": "vite"
+
},
"dependencies": {
"@urql/exchange-auth": "^0.1.2",
"@urql/exchange-graphcache": "^4.0.0",
···
"urql": "^2.0.2"
},
"devDependencies": {
-
"react-scripts": "4.0.3"
-
},
-
"scripts": {
-
"start": "react-scripts start",
-
"build": "react-scripts build",
-
"test": "react-scripts test",
-
"eject": "react-scripts eject"
-
},
-
"browserslist": {
-
"production": [
-
">0.2%",
-
"not dead",
-
"not op_mini all"
-
],
-
"development": [
-
"last 1 chrome version",
-
"last 1 firefox version",
-
"last 1 safari version"
-
]
+
"@vitejs/plugin-react-refresh": "^1.3.3",
+
"vite": "^2.2.4"
}
}
-14
examples/with-graphcache-updates/src/App.js
···
-
import { Provider } from "urql";
-
-
import client from "./client";
-
import Home from "./pages/Home";
-
-
function App() {
-
return (
-
<Provider value={client}>
-
<Home />
-
</Provider>
-
);
-
}
-
-
export default App;
+33
examples/with-graphcache-updates/src/App.jsx
···
+
import React, { useState, useEffect } from 'react';
+
import { Provider } from 'urql';
+
+
import client from './client';
+
import Links from './pages/Links';
+
import LoginForm from './pages/LoginForm';
+
+
const Home = () => {
+
const [isLoggedIn, setIsLoggedIn] = useState(false);
+
+
const onLoginSuccess = auth => {
+
localStorage.setItem('authToken', auth.token);
+
setIsLoggedIn(true);
+
};
+
+
useEffect(() => {
+
if (localStorage.getItem('authToken')) {
+
setIsLoggedIn(true);
+
}
+
}, []);
+
+
return isLoggedIn ? <Links /> : <LoginForm onLoginSuccess={onLoginSuccess} />;
+
};
+
+
function App() {
+
return (
+
<Provider value={client}>
+
<Home />
+
</Provider>
+
);
+
}
+
+
export default App;
examples/with-graphcache-updates/src/client/index.js examples/with-graphcache-updates/src/client.js
-13
examples/with-graphcache-updates/src/index.css
···
-
body {
-
margin: 0;
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-
sans-serif;
-
-webkit-font-smoothing: antialiased;
-
-moz-osx-font-smoothing: grayscale;
-
}
-
-
code {
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-
monospace;
-
}
+3 -4
examples/with-graphcache-updates/src/index.js examples/with-graphcache-updates/src/index.jsx
···
import React from 'react';
-
import ReactDOM from 'react-dom';
-
import './index.css';
+
import { render } from 'react-dom';
+
import App from './App';
-
ReactDOM.render(
+
render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
-
-27
examples/with-graphcache-updates/src/pages/Home.js
···
-
import React, { useEffect, useState } from 'react';
-
-
import Links from './Links';
-
import LoginForm from './LoginForm';
-
-
const Home = () => {
-
const [isLoggedIn, setIsLoggedIn] = useState(false);
-
-
const onLoginSuccess = auth => {
-
localStorage.setItem("authToken", auth.token);
-
setIsLoggedIn(true);
-
};
-
-
useEffect(() => {
-
if (localStorage.getItem("authToken")) {
-
setIsLoggedIn(true);
-
}
-
}, []);
-
-
return isLoggedIn ? (
-
<Links />
-
) : (
-
<LoginForm onLoginSuccess={onLoginSuccess} />
-
);
-
};
-
-
export default Home;
-74
examples/with-graphcache-updates/src/pages/Links.js
···
-
import React, {useEffect, useState} from "react";
-
import { gql, useQuery, useMutation } from "urql";
-
-
const LINKS_QUERY = gql`
-
query Links($first: Int!) {
-
links(first: $first) {
-
nodes {
-
id
-
canonicalUrl
-
}
-
}
-
}
-
`;
-
-
const CREATE_LINK_MUTATION = gql`
-
mutation CreateLink($url: URL!) {
-
createLink(url: $url) {
-
node {
-
id
-
canonicalUrl
-
}
-
}
-
}
-
`;
-
-
const Links = () => {
-
const [newLink, setNewLink] = useState("");
-
-
const [linksResult] = useQuery({ query: LINKS_QUERY, variables: { first: 10 } });
-
const [createResult, createLink] = useMutation(CREATE_LINK_MUTATION)
-
-
const onChangeLink = evt => {
-
setNewLink(evt.target.value);
-
}
-
-
const onSubmit = evt => {
-
evt.preventDefault();
-
createLink({ url: newLink });
-
};
-
-
useEffect(() => {
-
if (createResult.data) {
-
setNewLink("");
-
}
-
}, [createResult]);
-
-
if (linksResult.fetching || createResult.fetching) {
-
return <p>Loading...</p>
-
}
-
-
return (
-
<div>
-
{linksResult.error && <p>Oh no... {linksResult.error.message}</p>}
-
-
{linksResult.data && (
-
<ul>
-
{linksResult.data.links.nodes.map((link) => (
-
<li key={link.id}>{link.canonicalUrl}</li>
-
))}
-
</ul>
-
)}
-
-
<form onSubmit={onSubmit}>
-
{createResult.error && <p>Oh no... {createResult.error.message}</p>}
-
-
<input onChange={onChangeLink} value={newLink} />
-
-
<input type="submit" title="add" />
-
</form>
-
</div>
-
);
-
};
-
-
export default Links;
+77
examples/with-graphcache-updates/src/pages/Links.jsx
···
+
import React from 'react';
+
import { gql, useQuery, useMutation } from 'urql';
+
+
const LINKS_QUERY = gql`
+
query Links($first: Int!) {
+
links(first: $first) {
+
nodes {
+
id
+
title
+
canonicalUrl
+
}
+
}
+
}
+
`;
+
+
const CREATE_LINK_MUTATION = gql`
+
mutation CreateLink($url: URL!) {
+
createLink(url: $url) {
+
node {
+
id
+
title
+
canonicalUrl
+
}
+
}
+
}
+
`;
+
+
const Links = () => {
+
const [linksResult] = useQuery({
+
query: LINKS_QUERY,
+
variables: { first: 10 },
+
});
+
const [createResult, createLink] = useMutation(CREATE_LINK_MUTATION);
+
+
const onSubmitLink = event => {
+
event.preventDefault();
+
const { target } = event;
+
createLink({ url: new FormData(target).get('link') }).then(() =>
+
target.reset()
+
);
+
};
+
+
return (
+
<div>
+
{linksResult.error && <p>Oh no... {linksResult.error.message}</p>}
+
+
{linksResult.data && (
+
<ul>
+
{linksResult.data.links.nodes.map(link => (
+
<li key={link.id}>
+
<a rel="noreferrer" href={link.canonicalUrl}>
+
{link.title}
+
</a>
+
</li>
+
))}
+
</ul>
+
)}
+
+
<form onSubmit={onSubmitLink}>
+
{createResult.fetching ? <p>Submitting...</p> : null}
+
{createResult.error ? (
+
<p>Oh no... {createResult.error.message}</p>
+
) : null}
+
+
<fieldset disabled={createResult.fetching ? 'disabled' : null}>
+
<label>
+
{'Link to Blog Post: '}
+
<input type="url" name="link" placeholder="https://..." />
+
</label>
+
<button type="submit">Add Link</button>
+
</fieldset>
+
</form>
+
</div>
+
);
+
};
+
+
export default Links;
-57
examples/with-graphcache-updates/src/pages/LoginForm.js
···
-
import React, { useEffect, useState } from 'react';
-
import { gql, useMutation } from 'urql';
-
-
const LOGIN_MUTATION = gql`
-
mutation Signin($input: LoginInput!) {
-
signin(input: $input) {
-
refreshToken
-
token
-
}
-
}
-
`;
-
-
const LoginForm = ({ onLoginSuccess }) => {
-
const [password, setPassword] = useState('');
-
const [username, setUsername] = useState('');
-
-
const [loginResult, login] = useMutation(LOGIN_MUTATION);
-
-
const { data, fetching, error } = loginResult;
-
-
const onUsernameChange = evt => {
-
setUsername(evt.target.value);
-
};
-
-
const onPasswordChange = evt => {
-
setPassword(evt.target.value);
-
};
-
-
const onSubmit = evt => {
-
evt.preventDefault();
-
login({ input: { username, password } });
-
};
-
-
useEffect(() => {
-
if (data && data.signin) {
-
onLoginSuccess(data.signin);
-
}
-
}, [onLoginSuccess, data]);
-
-
if (fetching) {
-
return <p>loading...</p>;
-
}
-
-
return (
-
<form onSubmit={onSubmit}>
-
{error && <p>Oh no... {error.message}</p>}
-
-
<input type="text" onChange={onUsernameChange} />
-
-
<input type="password" onChange={onPasswordChange} />
-
-
<input type="submit" title="login" />
-
</form>
-
);
-
};
-
-
export default LoginForm;
+101
examples/with-graphcache-updates/src/pages/LoginForm.jsx
···
+
import React from 'react';
+
import { gql, useMutation } from 'urql';
+
+
const LOGIN_MUTATION = gql`
+
mutation Login($input: LoginInput!) {
+
signin(input: $input) {
+
refreshToken
+
token
+
}
+
}
+
`;
+
+
const REGISTER_MUTATION = gql`
+
mutation Register($input: LoginInput!) {
+
register(input: $input) {
+
refreshToken
+
token
+
}
+
}
+
`;
+
+
const LoginForm = ({ onLoginSuccess }) => {
+
const [loginResult, login] = useMutation(LOGIN_MUTATION);
+
const [registerResult, register] = useMutation(REGISTER_MUTATION);
+
+
const onSubmitLogin = event => {
+
event.preventDefault();
+
const data = new FormData(event.target);
+
const username = data.get('username');
+
const password = data.get('password');
+
+
login({ input: { username, password } }).then(result => {
+
if (!result.error && result.data && result.data.signin) {
+
onLoginSuccess(result.data.signin);
+
}
+
});
+
};
+
+
const onSubmitRegister = event => {
+
event.preventDefault();
+
const data = new FormData(event.target);
+
const username = data.get('username');
+
const password = data.get('password');
+
+
register({ input: { username, password } }).then(result => {
+
if (!result.error && result.data && result.data.register) {
+
onLoginSuccess(result.data.register);
+
}
+
});
+
};
+
+
const disabled = loginResult.fetching || registerResult.fetching;
+
+
return (
+
<>
+
<form onSubmit={onSubmitLogin}>
+
{loginResult.fetching ? <p>Logging in...</p> : null}
+
{loginResult.error ? <p>Oh no... {loginResult.error.message}</p> : null}
+
+
<fieldset disabled={disabled ? 'disabled' : null}>
+
<h3>Login</h3>
+
<label>
+
Username:
+
<input name="username" type="text" />
+
</label>
+
+
<label>
+
Password:
+
<input name="password" type="password" />
+
</label>
+
+
<button type="submit">Login</button>
+
</fieldset>
+
</form>
+
+
<form onSubmit={onSubmitRegister}>
+
{registerResult.fetching ? <p>Signing up...</p> : null}
+
{registerResult.error ? (
+
<p>Oh no... {registerResult.error.message}</p>
+
) : null}
+
+
<fieldset disabled={disabled ? 'disabled' : null}>
+
<h3>Register</h3>
+
<label>
+
{'Username: '}
+
<input name="username" type="text" />
+
</label>
+
+
<label>
+
{'Password: '}
+
<input name="password" type="password" />
+
</label>
+
+
<button type="submit">Register</button>
+
</fieldset>
+
</form>
+
</>
+
);
+
};
+
+
export default LoginForm;
+7
examples/with-graphcache-updates/vite.config.js
···
+
import { defineConfig } from 'vite';
+
import reactRefresh from '@vitejs/plugin-react-refresh';
+
+
// https://vitejs.dev/config/
+
export default defineConfig({
+
plugins: [reactRefresh()],
+
});
+3 -3
examples/with-multipart/README.md
···
This example contains:
-
- The `urql` bindings and a React app with a client set up in [`src/App.js`](src/App.js)
-
- The `multipartFetchExchange` from `@urql/exchange-multipart-fetch` in [`src/App.js`](src/App.js)
-
- A basic file upload form in [`src/pages/FileUpload.js`](src/pages/FileUpload.js)
+
- The `urql` bindings and a React app with a client set up in [`src/App.jsx`](src/App.jsx)
+
- The `multipartFetchExchange` from `@urql/exchange-multipart-fetch` in [`src/App.jsx`](src/App.jsx)
+
- A basic file upload form in [`src/FileUpload.jsx`](src/FileUpload.jsx)
+27
examples/with-multipart/index.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>with-multipart</title>
+
<style>
+
body {
+
margin: 0;
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+
sans-serif;
+
-webkit-font-smoothing: antialiased;
+
-moz-osx-font-smoothing: grayscale;
+
}
+
+
code {
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+
monospace;
+
}
+
</style>
+
</head>
+
<body>
+
<div id="root"></div>
+
<script type="module" src="/src/index.jsx"></script>
+
</body>
+
</html>
+5 -19
examples/with-multipart/package.json
···
"name": "with-multipart",
"version": "0.0.0",
"private": true,
+
"scripts": {
+
"start": "vite"
+
},
"dependencies": {
"@urql/exchange-multipart-fetch": "^0.1.11",
"graphql": "^15.5.0",
···
"urql": "^2.0.2"
},
"devDependencies": {
-
"react-scripts": "4.0.3"
-
},
-
"scripts": {
-
"start": "react-scripts start",
-
"build": "react-scripts build",
-
"test": "react-scripts test",
-
"eject": "react-scripts eject"
-
},
-
"browserslist": {
-
"production": [
-
">0.2%",
-
"not dead",
-
"not op_mini all"
-
],
-
"development": [
-
"last 1 chrome version",
-
"last 1 firefox version",
-
"last 1 safari version"
-
]
+
"@vitejs/plugin-react-refresh": "^1.3.3",
+
"vite": "^2.2.4"
}
}
-20
examples/with-multipart/src/App.js
···
-
import { createClient, Provider } from "urql";
-
import { multipartFetchExchange } from "@urql/exchange-multipart-fetch";
-
-
import FileUpload from "./pages/FileUpload";
-
-
const client = createClient({
-
url: "https://trygql.formidable.dev/graphql/uploads-mock",
-
exchanges: [multipartFetchExchange],
-
],
-
});
-
-
function App() {
-
return (
-
<Provider value={client}>
-
<FileUpload />
-
</Provider>
-
);
-
}
-
-
export default App;
+20
examples/with-multipart/src/App.jsx
···
+
import React from 'react';
+
import { createClient, Provider } from 'urql';
+
import { multipartFetchExchange } from '@urql/exchange-multipart-fetch';
+
+
import FileUpload from './FileUpload';
+
+
const client = createClient({
+
url: 'https://trygql.formidable.dev/graphql/uploads-mock',
+
exchanges: [multipartFetchExchange],
+
});
+
+
function App() {
+
return (
+
<Provider value={client}>
+
<FileUpload />
+
</Provider>
+
);
+
}
+
+
export default App;
-13
examples/with-multipart/src/index.css
···
-
body {
-
margin: 0;
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-
sans-serif;
-
-webkit-font-smoothing: antialiased;
-
-moz-osx-font-smoothing: grayscale;
-
}
-
-
code {
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-
monospace;
-
}
+3 -4
examples/with-multipart/src/index.js examples/with-multipart/src/index.jsx
···
import React from 'react';
-
import ReactDOM from 'react-dom';
-
import './index.css';
+
import { render } from 'react-dom';
+
import App from './App';
-
ReactDOM.render(
+
render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
-
+8 -10
examples/with-multipart/src/pages/FileUpload.js examples/with-multipart/src/FileUpload.jsx
···
-
import React, { useState } from "react";
-
import { gql, useMutation } from "urql";
+
import React, { useState } from 'react';
+
import { gql, useMutation } from 'urql';
const UPLOAD_FILE = gql`
mutation UploadFile($file: Upload!) {
···
const { data, fetching, error } = result;
const handleFileUpload = () => {
-
uploadFile({ file: selectedFile})
-
}
+
uploadFile({ file: selectedFile });
+
};
-
const handleFileChange = (event) => {
+
const handleFileChange = event => {
setSelectedFile(event.target.files[0]);
-
}
+
};
return (
<div>
···
{data && data.uploadFile ? (
<p>File uploaded to {data.uploadFile.filename}</p>
-
): (
+
) : (
<div>
<input type="file" onChange={handleFileChange} />
-
<button onClick={handleFileUpload}>
-
Upload!
-
</button>
+
<button onClick={handleFileUpload}>Upload!</button>
</div>
)}
</div>
+7
examples/with-multipart/vite.config.js
···
+
import { defineConfig } from 'vite';
+
import reactRefresh from '@vitejs/plugin-react-refresh';
+
+
// https://vitejs.dev/config/
+
export default defineConfig({
+
plugins: [reactRefresh()],
+
});
+2 -2
examples/with-next/pages/_app.js
···
-
import { withUrqlClient } from "next-urql";
+
import { withUrqlClient } from 'next-urql';
const App = ({ Component, pageProps }) => <Component {...pageProps} />;
export default withUrqlClient(
() => ({
-
url: "https://trygql.formidable.dev/graphql/basic-pokedex"
+
url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
}),
{ ssr: false }
)(App);
+4 -7
examples/with-next/pages/index.js
···
-
import { withUrqlClient } from "next-urql";
-
import {
-
useQuery,
-
gql
-
} from "urql";
+
import { withUrqlClient } from 'next-urql';
+
import { useQuery, gql } from 'urql';
const POKEMONS_QUERY = gql`
query {
···
return (
<div>
<h1>Static</h1>
-
{res.data.pokemons.map((pokemon) => (
+
{res.data.pokemons.map(pokemon => (
<div key={pokemon.id}>
{pokemon.id} - {pokemon.name}
</div>
···
export default withUrqlClient(
() => ({
-
url: "https://trygql.formidable.dev/graphql/basic-pokedex"
+
url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
}),
{ ssr: true }
)(Index);
+12 -9
examples/with-next/pages/server.js
···
-
import { initUrqlClient } from "next-urql";
+
import { initUrqlClient } from 'next-urql';
import {
ssrExchange,
dedupExchange,
cacheExchange,
fetchExchange,
useQuery,
-
gql
-
} from "urql";
+
gql,
+
} from 'urql';
const POKEMONS_QUERY = gql`
query {
···
return (
<div>
<h1>Server-side render</h1>
-
{res.data.pokemons.map((pokemon) => (
+
{res.data.pokemons.map(pokemon => (
<div key={pokemon.id}>
{pokemon.id} - {pokemon.name}
</div>
···
export async function getServerSideProps() {
const ssrCache = ssrExchange({ isClient: false });
-
const client = initUrqlClient({
-
url: "https://trygql.formidable.dev/graphql/basic-pokedex",
-
exchanges: [dedupExchange, cacheExchange, ssrCache, fetchExchange]
-
}, false);
+
const client = initUrqlClient(
+
{
+
url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
+
exchanges: [dedupExchange, cacheExchange, ssrCache, fetchExchange],
+
},
+
false
+
);
// This query is used to populate the cache for the query
// used on this page.
···
return {
props: {
// urqlState is a keyword here so withUrqlClient can pick it up.
-
urqlState: ssrCache.extractData()
+
urqlState: ssrCache.extractData(),
},
};
}
+12 -9
examples/with-next/pages/static.js
···
-
import { initUrqlClient } from "next-urql";
+
import { initUrqlClient } from 'next-urql';
import {
ssrExchange,
dedupExchange,
cacheExchange,
fetchExchange,
useQuery,
-
gql
-
} from "urql";
+
gql,
+
} from 'urql';
const POKEMONS_QUERY = gql`
query {
···
return (
<div>
<h1>Static</h1>
-
{res.data.pokemons.map((pokemon) => (
+
{res.data.pokemons.map(pokemon => (
<div key={pokemon.id}>
{pokemon.id} - {pokemon.name}
</div>
···
export async function getStaticProps() {
const ssrCache = ssrExchange({ isClient: false });
-
const client = initUrqlClient({
-
url: "https://trygql.formidable.dev/graphql/basic-pokedex",
-
exchanges: [dedupExchange, cacheExchange, ssrCache, fetchExchange]
-
}, false);
+
const client = initUrqlClient(
+
{
+
url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
+
exchanges: [dedupExchange, cacheExchange, ssrCache, fetchExchange],
+
},
+
false
+
);
// This query is used to populate the cache for the query
// used on this page.
···
return {
props: {
// urqlState is a keyword here so withUrqlClient can pick it up.
-
urqlState: ssrCache.extractData()
+
urqlState: ssrCache.extractData(),
},
};
}
+3 -3
examples/with-pagination/README.md
···
This example contains:
-
- The `urql` bindings and a React app with a client set up in [`src/App.js`](src/App.js)
-
- A managing component called `PaginatedNpmSearch` set up to render all pages in [`src/PaginatedNpmSearch.js`](src/PaginatedNpmSearch.js)
-
- A page component called `SearchResultPage` running page queries in [`src/PaginatedNpmSearch.js`](src/PaginatedNpmSearch.js)
+
- The `urql` bindings and a React app with a client set up in [`src/App.js`](src/App.jsx)
+
- A managing component called `PaginatedNpmSearch` set up to render all pages in [`src/PaginatedNpmSearch.jss`](src/PaginatedNpmSearch.jsx)
+
- A page component called `SearchResultPage` running page queries in [`src/PaginatedNpmSearch.jsx`](src/PaginatedNpmSearch.jsx)
+27
examples/with-pagination/index.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>with-pagination</title>
+
<style>
+
body {
+
margin: 0;
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+
sans-serif;
+
-webkit-font-smoothing: antialiased;
+
-moz-osx-font-smoothing: grayscale;
+
}
+
+
code {
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+
monospace;
+
}
+
</style>
+
</head>
+
<body>
+
<div id="root"></div>
+
<script type="module" src="/src/index.jsx"></script>
+
</body>
+
</html>
+5 -19
examples/with-pagination/package.json
···
"name": "with-pagination",
"version": "0.0.0",
"private": true,
+
"scripts": {
+
"start": "vite"
+
},
"dependencies": {
"graphql": "^15.5.0",
"react": "^17.0.2",
···
"urql": "^2.0.2"
},
"devDependencies": {
-
"react-scripts": "4.0.3"
-
},
-
"scripts": {
-
"start": "react-scripts start",
-
"build": "react-scripts build",
-
"test": "react-scripts test",
-
"eject": "react-scripts eject"
-
},
-
"browserslist": {
-
"production": [
-
">0.2%",
-
"not dead",
-
"not op_mini all"
-
],
-
"development": [
-
"last 1 chrome version",
-
"last 1 firefox version",
-
"last 1 safari version"
-
]
+
"@vitejs/plugin-react-refresh": "^1.3.3",
+
"vite": "^2.2.4"
}
}
-17
examples/with-pagination/src/App.js
···
-
import { createClient, Provider } from "urql";
-
-
import PaginatedNpmSearch from "./pages/PaginatedNpmSearch";
-
-
const client = createClient({
-
url: "https://trygql.formidable.dev/graphql/relay-npm",
-
});
-
-
function App() {
-
return (
-
<Provider value={client}>
-
<PaginatedNpmSearch />
-
</Provider>
-
);
-
}
-
-
export default App;
+18
examples/with-pagination/src/App.jsx
···
+
import React from 'react';
+
import { createClient, Provider } from 'urql';
+
+
import PaginatedNpmSearch from './PaginatedNpmSearch';
+
+
const client = createClient({
+
url: 'https://trygql.formidable.dev/graphql/relay-npm',
+
});
+
+
function App() {
+
return (
+
<Provider value={client}>
+
<PaginatedNpmSearch />
+
</Provider>
+
);
+
}
+
+
export default App;
-13
examples/with-pagination/src/index.css
···
-
body {
-
margin: 0;
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-
sans-serif;
-
-webkit-font-smoothing: antialiased;
-
-moz-osx-font-smoothing: grayscale;
-
}
-
-
code {
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-
monospace;
-
}
+3 -4
examples/with-pagination/src/index.js examples/with-pagination/src/index.jsx
···
import React from 'react';
-
import ReactDOM from 'react-dom';
-
import './index.css';
+
import { render } from 'react-dom';
+
import App from './App';
-
ReactDOM.render(
+
render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
-
+19 -15
examples/with-pagination/src/pages/PaginatedNpmSearch.js examples/with-pagination/src/PaginatedNpmSearch.jsx
···
-
import React, { useState } from "react";
-
import { gql, useQuery } from "urql";
+
import React, { useState } from 'react';
+
import { gql, useQuery } from 'urql';
const limit = 5;
-
const query = "graphql";
+
const query = 'graphql';
const NPM_SEARCH = gql`
query Search($query: String!, $first: Int!, $after: String) {
···
}
`;
-
const SearchResultPage = ({ variables, onLoadMore, isLastPage }) => {
+
const SearchResultPage = ({ variables, onLoadMore, isLastPage }) => {
const [result] = useQuery({ query: NPM_SEARCH, variables });
const { data, fetching, error } = result;
···
{searchResults && (
<>
-
{searchResults.nodes.map((packageInfo) => (
-
<div key={packageInfo.id}>{packageInfo.id}: {packageInfo.name}</div>
+
{searchResults.nodes.map(packageInfo => (
+
<div key={packageInfo.id}>
+
{packageInfo.id}: {packageInfo.name}
+
</div>
))}
{isLastPage && searchResults.pageInfo.hasNextPage && (
-
<button onClick={() => onLoadMore(searchResults.pageInfo.endCursor)}>
+
<button
+
onClick={() => onLoadMore(searchResults.pageInfo.endCursor)}
+
>
load more
</button>
)}
···
const PaginatedNpmSearch = () => {
const [pageVariables, setPageVariables] = useState([
-
{
-
query,
-
first: limit,
-
after: ""
-
},
-
]);
+
{
+
query,
+
first: limit,
+
after: '',
+
},
+
]);
return (
<div>
{pageVariables.map((variables, i) => (
<SearchResultPage
-
key={"" + variables.after}
+
key={'' + variables.after}
variables={variables}
isLastPage={i === pageVariables.length - 1}
-
onLoadMore={(after) =>
+
onLoadMore={after =>
setPageVariables([...pageVariables, { after, first: limit, query }])
}
/>
+7
examples/with-pagination/vite.config.js
···
+
import { defineConfig } from 'vite';
+
import reactRefresh from '@vitejs/plugin-react-refresh';
+
+
// https://vitejs.dev/config/
+
export default defineConfig({
+
plugins: [reactRefresh()],
+
});
+2 -2
examples/with-react/README.md
···
This example contains:
-
- The `urql` bindings and a React app with a client set up in [`src/App.js`](src/App.js)
-
- A query for pokémon in [`src/pages/PokemonList.js`](src/pages/PokemonList.js)
+
- The `urql` bindings and a React app with a client set up in [`src/App.jsx`](src/App.jsx)
+
- A query for pokémon in [`src/PokemonList.jsx`](src/PokemonList.jsx)
+27
examples/with-react/index.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>with-react</title>
+
<style>
+
body {
+
margin: 0;
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+
sans-serif;
+
-webkit-font-smoothing: antialiased;
+
-moz-osx-font-smoothing: grayscale;
+
}
+
+
code {
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+
monospace;
+
}
+
</style>
+
</head>
+
<body>
+
<div id="root"></div>
+
<script type="module" src="/src/index.jsx"></script>
+
</body>
+
</html>
+5 -19
examples/with-react/package.json
···
"name": "with-react",
"version": "0.0.0",
"private": true,
+
"scripts": {
+
"start": "vite"
+
},
"dependencies": {
"graphql": "^15.5.0",
"react": "^17.0.2",
···
"urql": "^2.0.2"
},
"devDependencies": {
-
"react-scripts": "4.0.3"
-
},
-
"scripts": {
-
"start": "react-scripts start",
-
"build": "react-scripts build",
-
"test": "react-scripts test",
-
"eject": "react-scripts eject"
-
},
-
"browserslist": {
-
"production": [
-
">0.2%",
-
"not dead",
-
"not op_mini all"
-
],
-
"development": [
-
"last 1 chrome version",
-
"last 1 firefox version",
-
"last 1 safari version"
-
]
+
"@vitejs/plugin-react-refresh": "^1.3.3",
+
"vite": "^2.2.4"
}
}
-17
examples/with-react/src/App.js
···
-
import { createClient, Provider } from "urql";
-
-
import PokemonList from "./pages/PokemonList";
-
-
const client = createClient({
-
url: "https://trygql.formidable.dev/graphql/basic-pokedex",
-
});
-
-
function App() {
-
return (
-
<Provider value={client}>
-
<PokemonList />
-
</Provider>
-
);
-
}
-
-
export default App;
+18
examples/with-react/src/App.jsx
···
+
import React from 'react';
+
import { createClient, Provider } from 'urql';
+
+
import PokemonList from './PokemonList';
+
+
const client = createClient({
+
url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
+
});
+
+
function App() {
+
return (
+
<Provider value={client}>
+
<PokemonList />
+
</Provider>
+
);
+
}
+
+
export default App;
-13
examples/with-react/src/index.css
···
-
body {
-
margin: 0;
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-
sans-serif;
-
-webkit-font-smoothing: antialiased;
-
-moz-osx-font-smoothing: grayscale;
-
}
-
-
code {
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-
monospace;
-
}
+3 -4
examples/with-react/src/index.js examples/with-react/src/index.jsx
···
import React from 'react';
-
import ReactDOM from 'react-dom';
-
import './index.css';
+
import { render } from 'react-dom';
+
import App from './App';
-
ReactDOM.render(
+
render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
-
+3 -3
examples/with-react/src/pages/PokemonList.js examples/with-react/src/PokemonList.jsx
···
-
import React from "react";
-
import { gql, useQuery } from "urql";
+
import React from 'react';
+
import { gql, useQuery } from 'urql';
const POKEMONS_QUERY = gql`
query Pokemons {
···
{data && (
<ul>
-
{data.pokemons.map((pokemon) => (
+
{data.pokemons.map(pokemon => (
<li key={pokemon.id}>{pokemon.name}</li>
))}
</ul>
+7
examples/with-react/vite.config.js
···
+
import { defineConfig } from 'vite';
+
import reactRefresh from '@vitejs/plugin-react-refresh';
+
+
// https://vitejs.dev/config/
+
export default defineConfig({
+
plugins: [reactRefresh()],
+
});
+5 -5
examples/with-refresh-auth/README.md
···
This example contains:
-
- The `urql` bindings and a React app set up in [`src/App.js`](src/App.js)
-
- Some authentication glue code to store the tokens in [`src/auth/Store.js`](src/auth/Store.js)
-
- The `Client` and the `authExchange` from `@urql/exchange-auth` set up in [`src/client/index.js`](src/client/index.js)
-
- A basic login form in [`src/pages/LoginForm.js`](src/pages/LoginForm.js)
-
- And a basic login guard on [`src/pages/Home.js`](src/pages/Home.js)
+
- The `urql` bindings and a React app set up in [`src/App.jsx`](src/App.jsx)
+
- Some authentication glue code to store the tokens in [`src/authStore.js`](src/authStore.js)
+
- The `Client` and the `authExchange` from `@urql/exchange-auth` set up in [`src/client.js`](src/client.js)
+
- A basic login form in [`src/pages/LoginForm.jsx`](src/pages/LoginForm.jsx)
+
- And a basic login guard on [`src/App.jsx`](src/App.jsx)
(Note: This isn't using a query in this particular component, since this is just an example)
+27
examples/with-refresh-auth/index.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>with-refresh-auth</title>
+
<style>
+
body {
+
margin: 0;
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+
sans-serif;
+
-webkit-font-smoothing: antialiased;
+
-moz-osx-font-smoothing: grayscale;
+
}
+
+
code {
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+
monospace;
+
}
+
</style>
+
</head>
+
<body>
+
<div id="root"></div>
+
<script type="module" src="/src/index.jsx"></script>
+
</body>
+
</html>
+5 -19
examples/with-refresh-auth/package.json
···
"name": "with-refresh-auth",
"version": "0.0.0",
"private": true,
+
"scripts": {
+
"start": "vite"
+
},
"dependencies": {
"@urql/exchange-auth": "^0.1.2",
"graphql": "^15.5.0",
···
"urql": "^2.0.2"
},
"devDependencies": {
-
"react-scripts": "4.0.3"
-
},
-
"scripts": {
-
"start": "react-scripts start",
-
"build": "react-scripts build",
-
"test": "react-scripts test",
-
"eject": "react-scripts eject"
-
},
-
"browserslist": {
-
"production": [
-
">0.2%",
-
"not dead",
-
"not op_mini all"
-
],
-
"development": [
-
"last 1 chrome version",
-
"last 1 firefox version",
-
"last 1 safari version"
-
]
+
"@vitejs/plugin-react-refresh": "^1.3.3",
+
"vite": "^2.2.4"
}
}
-15
examples/with-refresh-auth/src/App.js
···
-
import React from 'react';
-
import { Provider } from 'urql';
-
-
import client from './client';
-
import Home from './pages/Home';
-
-
function App() {
-
return (
-
<Provider value={client}>
-
<Home />
-
</Provider>
-
);
-
}
-
-
export default App;
+39
examples/with-refresh-auth/src/App.jsx
···
+
import React from 'react';
+
import { Provider } from 'urql';
+
+
import client from './client';
+
+
import { getToken, saveAuthData } from './authStore';
+
import Profile from './pages/Profile';
+
import LoginForm from './pages/LoginForm';
+
+
const Home = () => {
+
const [isLoggedIn, setIsLoggedIn] = useState(false);
+
+
const onLoginSuccess = auth => {
+
saveAuthData(auth);
+
setIsLoggedIn(true);
+
};
+
+
useEffect(() => {
+
if (getToken()) {
+
setIsLoggedIn(true);
+
}
+
}, []);
+
+
return isLoggedIn ? (
+
<Profile />
+
) : (
+
<LoginForm onLoginSuccess={onLoginSuccess} />
+
);
+
};
+
+
function App() {
+
return (
+
<Provider value={client}>
+
<Home />
+
</Provider>
+
);
+
}
+
+
export default App;
examples/with-refresh-auth/src/auth/Store.js examples/with-refresh-auth/src/authStore.js
+116
examples/with-refresh-auth/src/client.js
···
+
import {
+
makeOperation,
+
createClient,
+
dedupExchange,
+
fetchExchange,
+
cacheExchange,
+
gql,
+
} from 'urql';
+
+
import { authExchange } from '@urql/exchange-auth';
+
+
import {
+
getRefreshToken,
+
getToken,
+
saveAuthData,
+
clearStorage,
+
} from './authStore';
+
+
const REFRESH_TOKEN_MUTATION = gql`
+
mutation RefreshCredentials($refreshToken: String!) {
+
refreshCredentials(refreshToken: $refreshToken) {
+
refreshToken
+
token
+
}
+
}
+
`;
+
+
const client = createClient({
+
url: 'https://trygql.formidable.dev/graphql/web-collections',
+
exchanges: [
+
dedupExchange,
+
cacheExchange,
+
authExchange({
+
async getAuth({ authState, mutate }) {
+
if (!authState) {
+
const token = getToken();
+
const refreshToken = getRefreshToken();
+
+
if (token && refreshToken) {
+
return { token, refreshToken };
+
}
+
+
return null;
+
}
+
+
const result = await mutate(REFRESH_TOKEN_MUTATION, {
+
refreshToken: authState.refreshToken,
+
});
+
+
if (result.data?.refreshCredentials) {
+
saveAuthData(result.data.refreshCredentials);
+
+
return result.data.refreshCredentials;
+
}
+
+
// This is where auth has gone wrong and we need to clean up and redirect to a login page
+
clearStorage();
+
window.location.reload();
+
+
return null;
+
},
+
+
addAuthToOperation({ authState, operation }) {
+
if (!authState || !authState.token) {
+
return operation;
+
}
+
+
const fetchOptions =
+
typeof operation.context.fetchOptions === 'function'
+
? operation.context.fetchOptions()
+
: operation.context.fetchOptions || {};
+
+
return makeOperation(operation.kind, operation, {
+
...operation.context,
+
fetchOptions: {
+
...fetchOptions,
+
headers: {
+
...fetchOptions.headers,
+
Authorization: `Bearer ${authState.token}`,
+
},
+
},
+
});
+
},
+
+
didAuthError({ error }) {
+
return error.graphQLErrors.some(
+
e => e.extensions?.code === 'UNAUTHORIZED'
+
);
+
},
+
+
willAuthError({ operation, authState }) {
+
if (!authState) {
+
// Detect our login mutation and let this operation through:
+
return (
+
operation.kind !== 'mutation' ||
+
// Here we find any mutation definition with the "signin" field
+
!operation.query.definitions.some(definition => {
+
return (
+
definition.kind === 'OperationDefinition' &&
+
definition.selectionSet.selections.some(node => {
+
// The field name is just an example, since register may also be an exception
+
return node.kind === 'Field' && node.name.value === 'signin';
+
})
+
);
+
})
+
);
+
}
+
+
return false;
+
},
+
}),
+
fetchExchange,
+
],
+
});
+
+
export default client;
-112
examples/with-refresh-auth/src/client/index.js
···
-
import {
-
createClient,
-
dedupExchange,
-
fetchExchange,
-
cacheExchange,
-
gql,
-
} from 'urql';
-
import { makeOperation } from '@urql/core';
-
import { authExchange } from '@urql/exchange-auth';
-
import {
-
getRefreshToken,
-
getToken,
-
saveAuthData,
-
clearStorage,
-
} from '../auth/Store';
-
-
const REFRESH_TOKEN_MUTATION = gql`
-
mutation RefreshCredentials($refreshToken: String!) {
-
refreshCredentials(refreshToken: $refreshToken) {
-
refreshToken
-
token
-
}
-
}
-
`;
-
-
const getAuth = async ({ authState, mutate }) => {
-
if (!authState) {
-
const token = getToken();
-
const refreshToken = getRefreshToken();
-
-
if (token && refreshToken) {
-
return { token, refreshToken };
-
}
-
-
return null;
-
}
-
-
const result = await mutate(REFRESH_TOKEN_MUTATION, {
-
refreshToken: authState.refreshToken,
-
});
-
-
if (result.data?.refreshCredentials) {
-
saveAuthData(result.data.refreshCredentials);
-
-
return result.data.refreshCredentials;
-
}
-
-
// This is where auth has gone wrong and we need to clean up and redirect to a login page
-
clearStorage();
-
window.location.reload();
-
-
return null;
-
};
-
-
const addAuthToOperation = ({ authState, operation }) => {
-
if (!authState || !authState.token) {
-
return operation;
-
}
-
-
const fetchOptions =
-
typeof operation.context.fetchOptions === 'function'
-
? operation.context.fetchOptions()
-
: operation.context.fetchOptions || {};
-
-
return makeOperation(operation.kind, operation, {
-
...operation.context,
-
fetchOptions: {
-
...fetchOptions,
-
headers: {
-
...fetchOptions.headers,
-
Authorization: `Bearer ${authState.token}`,
-
},
-
},
-
});
-
};
-
-
const didAuthError = ({ error }) => {
-
return error.graphQLErrors.some(e => e.extensions?.code === 'UNAUTHORIZED');
-
};
-
-
const willAuthError = ({ operation, authState }) => {
-
if (!authState) {
-
// Detect our login mutation and let this operation through:
-
return (
-
operation.kind !== 'mutation' ||
-
// Here we find any mutation definition with the "signin" field
-
!operation.query.definitions.some(definition => {
-
return (
-
definition.kind === 'OperationDefinition' &&
-
definition.selectionSet.selections.some(node => {
-
// The field name is just an example, since register may also be an exception
-
return node.kind === 'Field' && node.name.value === 'signin';
-
})
-
);
-
})
-
);
-
}
-
-
return false;
-
};
-
-
const client = createClient({
-
url: 'https://trygql.formidable.dev/graphql/web-collections',
-
exchanges: [
-
dedupExchange,
-
cacheExchange,
-
authExchange({ getAuth, addAuthToOperation, didAuthError, willAuthError }),
-
fetchExchange,
-
],
-
});
-
-
export default client;
-13
examples/with-refresh-auth/src/index.css
···
-
body {
-
margin: 0;
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-
sans-serif;
-
-webkit-font-smoothing: antialiased;
-
-moz-osx-font-smoothing: grayscale;
-
}
-
-
code {
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-
monospace;
-
}
+3 -4
examples/with-refresh-auth/src/index.js examples/with-refresh-auth/src/index.jsx
···
import React from 'react';
-
import ReactDOM from 'react-dom';
-
import './index.css';
+
import { render } from 'react-dom';
+
import App from './App';
-
ReactDOM.render(
+
render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
-
-28
examples/with-refresh-auth/src/pages/Home.js
···
-
import React, { useEffect, useState } from 'react';
-
-
import { getToken, saveAuthData } from '../auth/Store';
-
import Profile from './Profile';
-
import LoginForm from './LoginForm';
-
-
const Home = () => {
-
const [isLoggedIn, setIsLoggedIn] = useState(false);
-
-
const onLoginSuccess = auth => {
-
saveAuthData(auth);
-
setIsLoggedIn(true);
-
};
-
-
useEffect(() => {
-
if (getToken()) {
-
setIsLoggedIn(true);
-
}
-
}, []);
-
-
return isLoggedIn ? (
-
<Profile />
-
) : (
-
<LoginForm onLoginSuccess={onLoginSuccess} />
-
);
-
};
-
-
export default Home;
-57
examples/with-refresh-auth/src/pages/LoginForm.js
···
-
import React, { useEffect, useState } from 'react';
-
import { gql, useMutation } from 'urql';
-
-
const LOGIN_MUTATION = gql`
-
mutation Signin($input: LoginInput!) {
-
signin(input: $input) {
-
refreshToken
-
token
-
}
-
}
-
`;
-
-
const LoginForm = ({ onLoginSuccess }) => {
-
const [password, setPassword] = useState('');
-
const [username, setUsername] = useState('');
-
-
const [loginResult, login] = useMutation(LOGIN_MUTATION);
-
-
const { data, fetching, error } = loginResult;
-
-
const onUsernameChange = evt => {
-
setUsername(evt.target.value);
-
};
-
-
const onPasswordChange = evt => {
-
setPassword(evt.target.value);
-
};
-
-
const onSubmit = evt => {
-
evt.preventDefault();
-
login({ input: { username, password } });
-
};
-
-
useEffect(() => {
-
if (data && data.signin) {
-
onLoginSuccess(data.signin);
-
}
-
}, [onLoginSuccess, data]);
-
-
if (fetching) {
-
return <p>loading...</p>;
-
}
-
-
return (
-
<form onSubmit={onSubmit}>
-
{error && <p>Oh no... {error.message}</p>}
-
-
<input type="text" onChange={onUsernameChange} />
-
-
<input type="password" onChange={onPasswordChange} />
-
-
<input type="submit" title="login" />
-
</form>
-
);
-
};
-
-
export default LoginForm;
+101
examples/with-refresh-auth/src/pages/LoginForm.jsx
···
+
import React from 'react';
+
import { gql, useMutation } from 'urql';
+
+
const LOGIN_MUTATION = gql`
+
mutation Login($input: LoginInput!) {
+
signin(input: $input) {
+
refreshToken
+
token
+
}
+
}
+
`;
+
+
const REGISTER_MUTATION = gql`
+
mutation Register($input: LoginInput!) {
+
register(input: $input) {
+
refreshToken
+
token
+
}
+
}
+
`;
+
+
const LoginForm = ({ onLoginSuccess }) => {
+
const [loginResult, login] = useMutation(LOGIN_MUTATION);
+
const [registerResult, register] = useMutation(REGISTER_MUTATION);
+
+
const onSubmitLogin = event => {
+
event.preventDefault();
+
const data = new FormData(event.target);
+
const username = data.get('username');
+
const password = data.get('password');
+
+
login({ input: { username, password } }).then(result => {
+
if (!result.error && result.data && result.data.signin) {
+
onLoginSuccess(result.data.signin);
+
}
+
});
+
};
+
+
const onSubmitRegister = event => {
+
event.preventDefault();
+
const data = new FormData(event.target);
+
const username = data.get('username');
+
const password = data.get('password');
+
+
register({ input: { username, password } }).then(result => {
+
if (!result.error && result.data && result.data.register) {
+
onLoginSuccess(result.data.register);
+
}
+
});
+
};
+
+
const disabled = loginResult.fetching || registerResult.fetching;
+
+
return (
+
<>
+
<form onSubmit={onSubmitLogin}>
+
{loginResult.fetching ? <p>Logging in...</p> : null}
+
{loginResult.error ? <p>Oh no... {loginResult.error.message}</p> : null}
+
+
<fieldset disabled={disabled ? 'disabled' : null}>
+
<h3>Login</h3>
+
<label>
+
Username:
+
<input name="username" type="text" />
+
</label>
+
+
<label>
+
Password:
+
<input name="password" type="password" />
+
</label>
+
+
<button type="submit">Login</button>
+
</fieldset>
+
</form>
+
+
<form onSubmit={onSubmitRegister}>
+
{registerResult.fetching ? <p>Signing up...</p> : null}
+
{registerResult.error ? (
+
<p>Oh no... {registerResult.error.message}</p>
+
) : null}
+
+
<fieldset disabled={disabled ? 'disabled' : null}>
+
<h3>Register</h3>
+
<label>
+
{'Username: '}
+
<input name="username" type="text" />
+
</label>
+
+
<label>
+
{'Password: '}
+
<input name="password" type="password" />
+
</label>
+
+
<button type="submit">Register</button>
+
</fieldset>
+
</form>
+
</>
+
);
+
};
+
+
export default LoginForm;
examples/with-refresh-auth/src/pages/Profile.js examples/with-refresh-auth/src/pages/Profile.jsx
+7
examples/with-refresh-auth/vite.config.js
···
+
import { defineConfig } from 'vite';
+
import reactRefresh from '@vitejs/plugin-react-refresh';
+
+
// https://vitejs.dev/config/
+
export default defineConfig({
+
plugins: [reactRefresh()],
+
});
+3 -3
examples/with-retry/README.md
···
This example contains:
-
- The `urql` bindings and a React app with a client set up in [`src/App.js`](src/App.js)
-
- The `retryExchange` from `@urql/exchange-retry` in [`src/App.js`](src/App.js)
-
- A random colour query in [`src/pages/Color.js`](src/pages/Color.js)
+
- The `urql` bindings and a React app with a client set up in [`src/App.jsx`](src/App.jsx)
+
- The `retryExchange` from `@urql/exchange-retry` in [`src/App.jsx`](src/App.jsx)
+
- A random colour query in [`src/Color.jsx`](src/pages/Color.jsx)
+27
examples/with-retry/index.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>with-retry</title>
+
<style>
+
body {
+
margin: 0;
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+
sans-serif;
+
-webkit-font-smoothing: antialiased;
+
-moz-osx-font-smoothing: grayscale;
+
}
+
+
code {
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+
monospace;
+
}
+
</style>
+
</head>
+
<body>
+
<div id="root"></div>
+
<script type="module" src="/src/index.jsx"></script>
+
</body>
+
</html>
+5 -19
examples/with-retry/package.json
···
"name": "with-retry",
"version": "0.0.0",
"private": true,
+
"scripts": {
+
"start": "vite"
+
},
"dependencies": {
"@urql/exchange-retry": "^0.2.0",
"graphql": "^15.5.0",
···
"urql": "^2.0.2"
},
"devDependencies": {
-
"react-scripts": "4.0.3"
-
},
-
"scripts": {
-
"start": "react-scripts start",
-
"build": "react-scripts build",
-
"test": "react-scripts test",
-
"eject": "react-scripts eject"
-
},
-
"browserslist": {
-
"production": [
-
">0.2%",
-
"not dead",
-
"not op_mini all"
-
],
-
"development": [
-
"last 1 chrome version",
-
"last 1 firefox version",
-
"last 1 safari version"
-
]
+
"@vitejs/plugin-react-refresh": "^1.3.3",
+
"vite": "^2.2.4"
}
}
-29
examples/with-retry/src/App.js
···
-
import { createClient, fetchExchange, Provider } from "urql";
-
import { retryExchange } from '@urql/exchange-retry';
-
-
import Color from "./pages/Color";
-
-
const client = createClient({
-
url: "https://trygql.formidable.dev/graphql/intermittent-colors",
-
exchanges: [
-
retryExchange({
-
maxNumberAttempts: 5,
-
retryIf: error => {
-
// NOTE: With this deemo schema we have a specific random error to look out for:
-
return error.graphQLErrors.some(x => x.extensions?.code === 'NO_SOUP')
-
|| !!error.networkError;
-
}
-
}),
-
fetchExchange
-
],
-
});
-
-
function App() {
-
return (
-
<Provider value={client}>
-
<Color />
-
</Provider>
-
);
-
}
-
-
export default App;
+32
examples/with-retry/src/App.jsx
···
+
import React from 'react';
+
import { createClient, fetchExchange, Provider } from 'urql';
+
import { retryExchange } from '@urql/exchange-retry';
+
+
import Color from './Color';
+
+
const client = createClient({
+
url: 'https://trygql.formidable.dev/graphql/intermittent-colors',
+
exchanges: [
+
retryExchange({
+
maxNumberAttempts: 5,
+
retryIf: error => {
+
// NOTE: With this deemo schema we have a specific random error to look out for:
+
return (
+
error.graphQLErrors.some(x => x.extensions?.code === 'NO_SOUP') ||
+
!!error.networkError
+
);
+
},
+
}),
+
fetchExchange,
+
],
+
});
+
+
function App() {
+
return (
+
<Provider value={client}>
+
<Color />
+
</Provider>
+
);
+
}
+
+
export default App;
-13
examples/with-retry/src/index.css
···
-
body {
-
margin: 0;
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-
sans-serif;
-
-webkit-font-smoothing: antialiased;
-
-moz-osx-font-smoothing: grayscale;
-
}
-
-
code {
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-
monospace;
-
}
+3 -3
examples/with-retry/src/index.js examples/with-retry/src/index.jsx
···
import React from 'react';
-
import ReactDOM from 'react-dom';
-
import './index.css';
+
import { render } from 'react-dom';
+
import App from './App';
-
ReactDOM.render(
+
render(
<React.StrictMode>
<App />
</React.StrictMode>,
+7 -4
examples/with-retry/src/pages/Color.js examples/with-retry/src/Color.jsx
···
-
import React from "react";
-
import { gql, useQuery } from "urql";
+
import React from 'react';
+
import { gql, useQuery } from 'urql';
const RANDOM_COLOR_QUERY = gql`
query RandomColor {
···
{data.randomColor.name}
</div>
)}
-
+
{result.operation && (
-
<p>We retried {result.operation.context.retryCount} times to get a result without an error.</p>
+
<p>
+
We retried {result.operation.context.retryCount} times to get a result
+
without an error.
+
</p>
)}
</div>
);
+7
examples/with-retry/vite.config.js
···
+
import { defineConfig } from 'vite';
+
import reactRefresh from '@vitejs/plugin-react-refresh';
+
+
// https://vitejs.dev/config/
+
export default defineConfig({
+
plugins: [reactRefresh()],
+
});
+1 -2
examples/with-svelte/index.html
···
<html lang="en">
<head>
<meta charset="UTF-8" />
-
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
-
<title>Svelte + Vite App</title>
+
<title>with-svelte</title>
</head>
<body>
<div id="app"></div>
+2 -2
examples/with-svelte/src/main.js
···
import App from './App.svelte';
var app = new App({
-
target: document.body
+
target: document.body,
});
-
export default app;
+
export default app;
+4 -4
examples/with-svelte/vite.config.js
···
-
import { defineConfig } from 'vite'
-
import svelte from '@sveltejs/vite-plugin-svelte'
+
import { defineConfig } from 'vite';
+
import svelte from '@sveltejs/vite-plugin-svelte';
// https://vitejs.dev/config/
export default defineConfig({
-
plugins: [svelte()]
-
})
+
plugins: [svelte()],
+
});
-1
examples/with-vue3/index.html
···
<html lang="en">
<head>
<meta charset="UTF-8">
-
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
+3 -3
examples/with-vue3/src/main.js
···
-
import { createApp } from 'vue'
-
import App from './App.vue'
+
import { createApp } from 'vue';
+
import App from './App.vue';
-
createApp(App).mount('#app')
+
createApp(App).mount('#app');
+4 -4
examples/with-vue3/vite.config.js
···
-
import { defineConfig } from 'vite'
-
import vue from '@vitejs/plugin-vue'
+
import { defineConfig } from 'vite';
+
import vue from '@vitejs/plugin-vue';
// https://vitejs.dev/config/
export default defineConfig({
-
plugins: [vue()]
-
})
+
plugins: [vue()],
+
});
+4 -1
package.json
···
],
"extends": [
"./scripts/eslint/js-preset.js"
-
]
+
],
+
"rules": {
+
"import/no-unresolved": "off"
+
}
}
],
"eslintIgnore": [
+16 -2
scripts/eslint/common.js
···
'build/',
'coverage/',
'benchmark/',
-
'example/',
-
'examples/',
'scripts/'
],
plugins: [
···
settings: {
react: {
version: 'detect',
+
},
+
'import/extensions': [
+
'.js',
+
'.jsx',
+
'.ts',
+
'.tsx',
+
],
+
'import/resolver': {
+
node: {
+
extensions: [
+
'.js',
+
'.jsx',
+
'.ts',
+
'.tsx',
+
]
+
},
},
},
};