Skip to content

Commit 7899c6b

Browse files
committed
Centralize global scope object and make it compatible with React Native.
1 parent f5261ad commit 7899c6b

File tree

7 files changed

+43
-35
lines changed

7 files changed

+43
-35
lines changed

packages/react-async-devtools/src/index.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
11
import React from "react"
2-
import { actionTypes, reducer } from "react-async"
2+
import { actionTypes, reducer, globalScope } from "react-async"
33

44
import { Root, Range, Checkbox, Label, Small, Ol, Li, Button } from "./components"
55

6-
const root =
7-
(typeof self === "object" && self.self === self && self) ||
8-
(typeof global === "object" && global.global === global && global) ||
9-
{}
10-
11-
const storage = root.sessionStorage
6+
const storage = globalScope.sessionStorage
127

138
const state = {
149
intercept: (storage && storage.getItem("intercept") === "true") || false,
1510
latency: (storage && storage.getItem("latency")) || "0",
1611
update: () => {},
1712
}
1813

19-
root.__REACT_ASYNC__ = root.__REACT_ASYNC__ || {}
20-
root.__REACT_ASYNC__.devToolsDispatcher = (action, dispatch) => {
14+
globalScope.__REACT_ASYNC__.devToolsDispatcher = (action, dispatch) => {
2115
const run = () => {
2216
dispatch(action)
2317
state.update(action)

packages/react-async/src/Async.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import React from "react"
2+
3+
import globalScope from "./globalScope"
24
import { Initial, Pending, Fulfilled, Rejected, Settled } from "./helpers"
35
import propTypes from "./propTypes"
46
import { actionTypes, init, dispatchMiddleware, reducer as asyncReducer } from "./reducer"
57

6-
const root =
7-
(typeof self === "object" && self.self === self && self) ||
8-
(typeof global === "object" && global.global === global && global)
9-
108
/**
119
* createInstance allows you to create instances of Async that are bound to a specific promise.
1210
* A unique instance also uses its own React context for better nesting capability.
@@ -48,7 +46,7 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
4846
}
4947
this.debugLabel = props.debugLabel || defaultProps.debugLabel
5048

51-
const { devToolsDispatcher } = root.__REACT_ASYNC__ || {}
49+
const { devToolsDispatcher } = globalScope.__REACT_ASYNC__
5250
const _reducer = props.reducer || defaultProps.reducer
5351
const _dispatcher = props.dispatcher || defaultProps.dispatcher || devToolsDispatcher
5452
const reducer = _reducer
@@ -104,9 +102,9 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => {
104102
}
105103

106104
start(promiseFn) {
107-
if ("AbortController" in root) {
105+
if ("AbortController" in globalScope) {
108106
this.abortController.abort()
109-
this.abortController = new root.AbortController()
107+
this.abortController = new globalScope.AbortController()
110108
}
111109
this.counter++
112110
return new Promise((resolve, reject) => {

packages/react-async/src/Async.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import "jest-dom/extend-expect"
44
import React from "react"
55
import { render, fireEvent, cleanup, waitForElement } from "@testing-library/react"
6-
import Async, { createInstance } from "./index"
6+
import Async, { createInstance, globalScope } from "./index"
77
import {
88
resolveIn,
99
resolveTo,
@@ -17,7 +17,7 @@ import {
1717
} from "./specs"
1818

1919
const abortCtrl = { abort: jest.fn() }
20-
window.AbortController = jest.fn().mockImplementation(() => abortCtrl)
20+
globalScope.AbortController = jest.fn().mockImplementation(() => abortCtrl)
2121

2222
beforeEach(abortCtrl.abort.mockClear)
2323
afterEach(cleanup)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Universal global scope object. In the browser this is `self`, in Node.js and React Native it's `global`.
3+
*/
4+
const globalScope = (() => {
5+
if (typeof self === "object" && self.self === self) return self
6+
if (typeof global === "object" && global.global === global) return global
7+
if (typeof global === "object" && global.GLOBAL === global) return global
8+
return {} // fallback that relies on imported modules to be singletons
9+
})()
10+
11+
/**
12+
* Globally available object used to connect the DevTools to all React Async instances.
13+
*/
14+
globalScope.__REACT_ASYNC__ = globalScope.__REACT_ASYNC__ || {}
15+
16+
export default globalScope

packages/react-async/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ export { createInstance } from "./Async"
33
export { default as useAsync, useFetch } from "./useAsync"
44
export default Async
55
export { statusTypes } from "./status"
6+
export { default as globalScope } from "./globalScope"
67
export * from "./helpers"
78
export * from "./reducer"

packages/react-async/src/useAsync.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { useCallback, useDebugValue, useEffect, useMemo, useRef, useReducer } from "react"
2+
3+
import globalScope from "./globalScope"
24
import { actionTypes, init, dispatchMiddleware, reducer as asyncReducer } from "./reducer"
35

46
const noop = () => {}
5-
const root =
6-
(typeof self === "object" && self.self === self && self) ||
7-
(typeof global === "object" && global.global === global && global)
87

98
const useAsync = (arg1, arg2) => {
109
const options = typeof arg1 === "function" ? { ...arg2, promiseFn: arg1 } : arg1
@@ -15,7 +14,7 @@ const useAsync = (arg1, arg2) => {
1514
const prevOptions = useRef(undefined)
1615
const abortController = useRef({ abort: noop })
1716

18-
const { devToolsDispatcher } = root.__REACT_ASYNC__ || {}
17+
const { devToolsDispatcher } = globalScope.__REACT_ASYNC__
1918
const { reducer, dispatcher = devToolsDispatcher } = options
2019
const [state, _dispatch] = useReducer(
2120
reducer ? (state, action) => reducer(state, action, asyncReducer) : asyncReducer,
@@ -51,9 +50,9 @@ const useAsync = (arg1, arg2) => {
5150
count === counter.current && setError(error, () => onReject && onReject(error))
5251

5352
const start = promiseFn => {
54-
if ("AbortController" in root) {
53+
if ("AbortController" in globalScope) {
5554
abortController.current.abort()
56-
abortController.current = new root.AbortController()
55+
abortController.current = new globalScope.AbortController()
5756
}
5857
counter.current++
5958
return new Promise((resolve, reject) => {
@@ -136,7 +135,7 @@ const useAsyncFetch = (input, init, { defer, json, ...options } = {}) => {
136135
const method = input.method || (init && init.method)
137136
const headers = input.headers || (init && init.headers) || {}
138137
const accept = headers["Accept"] || headers["accept"] || (headers.get && headers.get("accept"))
139-
const doFetch = (input, init) => root.fetch(input, init).then(parseResponse(accept, json))
138+
const doFetch = (input, init) => globalScope.fetch(input, init).then(parseResponse(accept, json))
140139
const isDefer = defer === true || ~["POST", "PUT", "PATCH", "DELETE"].indexOf(method)
141140
const fn = defer === false || !isDefer ? "promiseFn" : "deferFn"
142141
const state = useAsync({

packages/react-async/src/useAsync.spec.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import "jest-dom/extend-expect"
44
import React from "react"
55
import { render, fireEvent, cleanup, waitForElement } from "@testing-library/react"
6-
import { useAsync, useFetch } from "./index"
6+
import { useAsync, useFetch, globalScope } from "./index"
77
import {
88
sleep,
99
resolveTo,
@@ -17,13 +17,13 @@ import {
1717
import "../../../jest.setup"
1818

1919
const abortCtrl = { abort: jest.fn(), signal: "SIGNAL" }
20-
window.AbortController = jest.fn(() => abortCtrl)
20+
globalScope.AbortController = jest.fn(() => abortCtrl)
2121

2222
const json = jest.fn(() => ({}))
23-
window.fetch = jest.fn(() => Promise.resolve({ ok: true, json }))
23+
globalScope.fetch = jest.fn(() => Promise.resolve({ ok: true, json }))
2424

2525
beforeEach(abortCtrl.abort.mockClear)
26-
beforeEach(window.fetch.mockClear)
26+
beforeEach(globalScope.fetch.mockClear)
2727
beforeEach(json.mockClear)
2828
afterEach(cleanup)
2929

@@ -89,7 +89,7 @@ describe("useAsync", () => {
8989
describe("useFetch", () => {
9090
test("sets up a fetch request", () => {
9191
render(<Fetch input="/test" />)
92-
expect(window.fetch).toHaveBeenCalledWith(
92+
expect(globalScope.fetch).toHaveBeenCalledWith(
9393
"/test",
9494
expect.objectContaining({ signal: abortCtrl.signal })
9595
)
@@ -103,9 +103,9 @@ describe("useFetch", () => {
103103
</Fetch>
104104
)
105105
const { getByText } = render(component)
106-
expect(window.fetch).not.toHaveBeenCalled()
106+
expect(globalScope.fetch).not.toHaveBeenCalled()
107107
fireEvent.click(getByText("run"))
108-
expect(window.fetch).toHaveBeenCalledWith(
108+
expect(globalScope.fetch).toHaveBeenCalledWith(
109109
"/test",
110110
expect.objectContaining({ method: "POST", signal: abortCtrl.signal })
111111
)
@@ -118,9 +118,9 @@ describe("useFetch", () => {
118118
</Fetch>
119119
)
120120
const { getByText } = render(component)
121-
expect(window.fetch).not.toHaveBeenCalled()
121+
expect(globalScope.fetch).not.toHaveBeenCalled()
122122
fireEvent.click(getByText("run"))
123-
expect(window.fetch).toHaveBeenCalledWith(
123+
expect(globalScope.fetch).toHaveBeenCalledWith(
124124
"/test",
125125
expect.objectContaining({ signal: abortCtrl.signal })
126126
)
@@ -132,7 +132,7 @@ describe("useFetch", () => {
132132
{({ run }) => <button onClick={run}>run</button>}
133133
</Fetch>
134134
)
135-
expect(window.fetch).toHaveBeenCalledWith(
135+
expect(globalScope.fetch).toHaveBeenCalledWith(
136136
"/test",
137137
expect.objectContaining({ method: "POST", signal: abortCtrl.signal })
138138
)

0 commit comments

Comments
 (0)