Switched to typescript CRA, added router, global user context and apollo client

This commit is contained in:
Dmitriy Shishkov 2020-10-13 22:11:40 +05:00
parent cc63aede25
commit 16cdf92bfe
No known key found for this signature in database
GPG Key ID: D76D70029F55183E
15 changed files with 789 additions and 749 deletions

2
.gitignore vendored
View File

@ -21,3 +21,5 @@
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
*.local*

View File

@ -6,3 +6,5 @@ A frontend for QuestionForm application.
- React - React
- Graphql - Graphql
- Apollo Client
- React Router

View File

@ -1,25 +1,23 @@
{ {
"name": "front", "name": "frontend",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@testing-library/jest-dom": "^4.2.4", "@apollo/client": "^3.2.2",
"@testing-library/react": "^9.3.2", "@types/node": "^12.0.0",
"@testing-library/user-event": "^7.1.2", "@types/react": "^16.9.0",
"@types/jest": "^26.0.14", "@types/react-dom": "^16.9.0",
"@types/node": "^14.11.2", "@types/react-router-dom": "^5.1.6",
"@types/react": "^16.9.50", "graphql": "^15.3.0",
"@types/react-dom": "^16.9.8",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.3", "react-scripts": "3.4.3",
"typescript": "^4.0.3" "typescript": "~3.7.2"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build"
"test": "react-scripts test",
"eject": "react-scripts eject"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"

View File

@ -19,7 +19,6 @@
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML. Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
@ -32,10 +31,8 @@
<!-- <!--
This HTML file is a template. This HTML file is a template.
If you open it directly in the browser, you will see an empty page. If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file. You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag. The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`. To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->

View File

@ -1,7 +0,0 @@
import React from "react"
const App: React.FC = () => {
return <div className="App"></div>
}
export default App

31
src/apollo/index.ts Normal file
View File

@ -0,0 +1,31 @@
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import { LOGIN } from './queries'
const httpLink = createHttpLink({
uri: process.env.GRAPHQL_URL || process.env.REACT_APP_GRAPHQL_URL || undefined
// fetchOptions: {
// mode: 'no-cors'
// }
})
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token')
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
})
export default client
export { LOGIN }

20
src/apollo/queries.ts Normal file
View File

@ -0,0 +1,20 @@
import { gql } from '@apollo/client'
const LOGIN = gql`
mutation Login($email: String!) {
login(email: $email) {
success
}
}
`
const FORM = gql`
query Form($id: Int!) {
form(id: $id) {
id
title
}
}
`
export { LOGIN, FORM }

55
src/components/App.tsx Normal file
View File

@ -0,0 +1,55 @@
import { ApolloProvider, useMutation, useQuery } from '@apollo/client'
import React, { useContext } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import client from '../apollo'
import { FORM, LOGIN } from '../apollo/queries'
import Context from '../context'
import { useUser } from '../hooks'
const TestComponent: React.FC = () => {
const { loading, error, data } = useQuery(FORM, {
variables: {
id: 1
}
})
const { user } = useContext(Context)
const [doLogin] = useMutation(LOGIN)
if (loading) return <p>Loading...</p>
if (error) return <p>Error :(</p>
return (
<div>
<button
onClick={() => doLogin({ variables: { email: 'test@test.test' } })}
>
Click!
</button>
{user.id}
{data.form.id}
</div>
)
}
const App: React.FC = () => {
const userContext = useUser()
return (
<div className="App">
<ApolloProvider client={client}>
<Context.Provider value={userContext}>
<Router>
<Switch>
<Route path="/" component={TestComponent} />
</Switch>
</Router>
</Context.Provider>
</ApolloProvider>
</div>
)
}
export default App

27
src/context.ts Normal file
View File

@ -0,0 +1,27 @@
import { createContext } from 'react'
export type UserType = {
id: number
email: string
name: string
}
export type ContextType = {
user: UserType
setUser: (user: UserType) => void
}
export const initialUser: UserType = {
email: '',
id: 0,
name: ''
}
const initialState: ContextType = {
user: initialUser,
setUser: () => {}
}
const Context = createContext<ContextType>(initialState)
export default Context

12
src/hooks.ts Normal file
View File

@ -0,0 +1,12 @@
import { useCallback, useState } from 'react'
import { ContextType, initialUser, UserType } from './context'
export const useUser = (): ContextType => {
const [user, internalSerUser] = useState<UserType>(initialUser)
const setUser = useCallback((user: UserType): void => {
internalSerUser(user)
}, [])
return { user, setUser }
}

View File

@ -1,13 +1,13 @@
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif; sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace; monospace;
} }

View File

@ -1,17 +1,12 @@
import React from "react" import React from 'react'
import ReactDOM from "react-dom" import { render } from 'react-dom'
import "./index.css"
import App from "./App"
import * as serviceWorker from "./serviceWorker"
ReactDOM.render( import './index.css'
import App from './components/App'
render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById('root')
) )
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister()

View File

@ -12,18 +12,26 @@
const isLocalhost = Boolean( const isLocalhost = Boolean(
window.location.hostname === 'localhost' || window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address. // [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' || window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4. // 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match( window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
) )
); );
export function register(config) { type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); const publicUrl = new URL(
process.env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) { if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin // Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to // from what our page is served on. This might happen if a CDN is used to
@ -43,7 +51,7 @@ export function register(config) {
navigator.serviceWorker.ready.then(() => { navigator.serviceWorker.ready.then(() => {
console.log( console.log(
'This web app is being served cache-first by a service ' + 'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA' 'worker. To learn more, visit https://bit.ly/CRA-PWA'
); );
}); });
} else { } else {
@ -54,7 +62,7 @@ export function register(config) {
} }
} }
function registerValidSW(swUrl, config) { function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker navigator.serviceWorker
.register(swUrl) .register(swUrl)
.then(registration => { .then(registration => {
@ -71,7 +79,7 @@ function registerValidSW(swUrl, config) {
// content until all client tabs are closed. // content until all client tabs are closed.
console.log( console.log(
'New content is available and will be used when all ' + 'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
); );
// Execute callback // Execute callback
@ -98,10 +106,10 @@ function registerValidSW(swUrl, config) {
}); });
} }
function checkValidServiceWorker(swUrl, config) { function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page. // Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, { fetch(swUrl, {
headers: { 'Service-Worker': 'script' }, headers: { 'Service-Worker': 'script' }
}) })
.then(response => { .then(response => {
// Ensure service worker exists, and that we really are getting a JS file. // Ensure service worker exists, and that we really are getting a JS file.

View File

@ -1,11 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
@ -19,7 +15,5 @@
"noEmit": true, "noEmit": true,
"jsx": "react" "jsx": "react"
}, },
"include": [ "include": ["src"]
"src"
]
} }

1286
yarn.lock

File diff suppressed because it is too large Load Diff