-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
126 lines (111 loc) · 3.93 KB
/
Copy pathindex.js
File metadata and controls
126 lines (111 loc) · 3.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { useEffect, useRef, useState } from 'react' // alias to 'preact/hooks'
import { useMappedState } from '@app-elements/use-mapped-state'
import { request as makeRequest } from './request'
export { actions, requestsReducer } from './reducer'
const OK_TIME = 30000
const _existing = {}
const validCache = (ts, maxTime = OK_TIME) => {
if (!ts) return false
const diff = Date.now() - ts
return diff < maxTime
}
const requestPromise = ({ uid, endpoint, opts }) => {
if (_existing[uid]) {
const { promise, xhr } = _existing[uid]
if (xhr.readyState !== 4) {
return promise
}
}
const { promise, xhr } = makeRequest({ endpoint, ...opts })
_existing[uid] = { promise, xhr }
return promise
}
export function useRequest (store, endpoint, options = {}) {
// Take props out of options that don't need to be passed to the request.
// Can be undefined, as they are not required.
const { maxTime, uid = endpoint, ...opts } = options
// Get existing request object in the global store, and stay in sync.
const requestSelector = (state) => (state.requests || {})[uid] || {}
const request = useMappedState(store, requestSelector)
// We'll also track loading state locally
const [isLoading, setIsLoading] = useState(true)
// We'll need to track if the component is mounted. We'll use
// useRef which acts as instance variables without the class syntax.
// And a useEffect call with no inputs, so it's only called once on mount.
const mountedRef = useRef(false)
useEffect(() => {
mountedRef.current = true
return () => (mountedRef.current = false)
}, [])
// Only update local state if component is mounted
const safeSetIsLoading = (...args) => mountedRef.current && setIsLoading(...args)
// And some functions to manage this request in the global store
const cache = (result) => store.setState({
requests: {
...store.getState().requests || {},
[uid]: { result, timestamp: Date.now(), error: null }
}
})
const clear = () => store.setState({
requests: {
...store.getState().requests || {},
[uid]: null
}
})
const err = (error) => store.setState({
requests: {
...store.getState().requests || {},
[uid]: {
...(store.getState().requests || {})[uid] || {},
error
}
}
})
// Finally, making the actual request!
const load = () => {
safeSetIsLoading(true)
if (validCache(request.timestamp, maxTime)) {
safeSetIsLoading(false)
} else if (endpoint != null) {
const token = store.getState().token
opts.headers = opts.headers || {}
if (token && !opts.noAuth) {
opts.headers.Authorization = `Token ${token}`
}
const promise = requestPromise({ uid, endpoint, opts })
promise
.then(result => {
cache(result)
safeSetIsLoading(false)
delete _existing[uid]
})
.catch(error => {
err(error)
safeSetIsLoading(false)
delete _existing[uid]
})
}
return () => {
if (_existing[uid] && _existing[uid].xhr) {
// If the request is pre-contact with server, do not abort.
// This solves strange race-condition in chrome, where the canceled
// xhr contacts the server without the proper headers set, resulting in 415.
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState
if (_existing[uid].xhr.readyState > 1) {
_existing[uid].xhr.abort()
}
delete _existing[uid]
}
}
}
// Load data if a new uid/endpoint is passed down, or if timestamp changes.
// ie. calling `clear()` will trigger this effect to call `load`.
useEffect(load, [uid, request.timestamp])
return {
...request,
clear,
// Ensure isLoading is never false when request fields are null.
// This covers some async race conditions that can arise.
isLoading: request.result == null && request.error == null ? true : isLoading
}
}