Skip to content

Commit

Permalink
Convert Context/Provider/Subscription to Typescript (#1742)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vannevelj authored and timdorr committed Jun 29, 2021
1 parent a8c84d2 commit 4f9c3b8
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 77 deletions.
2 changes: 1 addition & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import pkg from './package.json'

const env = process.env.NODE_ENV

const extensions = ['.js', '.ts', '.json']
const extensions = ['.js', '.ts', '.tsx', '.json']

const config = {
input: 'src/index.js',
Expand Down
9 changes: 0 additions & 9 deletions src/components/Context.js

This file was deleted.

17 changes: 17 additions & 0 deletions src/components/Context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'
import { Action, AnyAction, Store } from 'redux'
import type { FixTypeLater } from '../types'
import type Subscription from '../utils/Subscription'

export interface ReactReduxContextValue<SS = FixTypeLater, A extends Action = AnyAction> {
store: Store<SS, A>
subscription: Subscription
}

export const ReactReduxContext = /*#__PURE__*/ React.createContext<ReactReduxContextValue | null>(null)

if (process.env.NODE_ENV !== 'production') {
ReactReduxContext.displayName = 'ReactRedux'
}

export default ReactReduxContext
49 changes: 0 additions & 49 deletions src/components/Provider.js

This file was deleted.

52 changes: 52 additions & 0 deletions src/components/Provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { Context, ReactNode, useMemo } from 'react'
import { ReactReduxContext, ReactReduxContextValue } from './Context'
import Subscription from '../utils/Subscription'
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
import type { FixTypeLater } from '../types'
import { Action, AnyAction, Store } from 'redux'

interface ProviderProps<A extends Action = AnyAction> {
/**
* The single Redux store in your application.
*/
store: Store<FixTypeLater, A>
/**
* Optional context to be used internally in react-redux. Use React.createContext() to create a context to be used.
* If this is used, generate own connect HOC by using connectAdvanced, supplying the same context provided to the
* Provider. Initial value doesn't matter, as it is overwritten with the internal state of Provider.
*/
context?: Context<ReactReduxContextValue>
children: ReactNode
}

function Provider({ store, context, children }: ProviderProps) {
const contextValue = useMemo(() => {
const subscription = new Subscription(store)
subscription.onStateChange = subscription.notifyNestedSubs
return {
store,
subscription,
}
}, [store])

const previousState = useMemo(() => store.getState(), [store])

useIsomorphicLayoutEffect(() => {
const { subscription } = contextValue
subscription.trySubscribe()

if (previousState !== store.getState()) {
subscription.notifyNestedSubs()
}
return () => {
subscription.tryUnsubscribe()
subscription.onStateChange = undefined
}
}, [contextValue, previousState])

const Context = context || ReactReduxContext

return <Context.Provider value={contextValue}>{children}</Context.Provider>
}

export default Provider
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type FixTypeLater = any
44 changes: 27 additions & 17 deletions src/utils/Subscription.js → src/utils/Subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { getBatch } from './batch'
// well as nesting subscriptions of descendant components, so that we can ensure the
// ancestor components re-render before descendants

const nullListeners = { notify() {} }
type Listener = {
callback: () => void
next: Listener | null
prev: Listener | null
}

function createListenerCollection() {
const batch = getBatch()
let first = null
let last = null
let first: Listener | null = null
let last: Listener | null = null

return {
clear() {
Expand Down Expand Up @@ -37,10 +41,10 @@ function createListenerCollection() {
return listeners
},

subscribe(callback) {
subscribe(callback: () => void) {
let isSubscribed = true

let listener = (last = {
let listener: Listener = (last = {
callback,
next: null,
prev: last,
Expand Down Expand Up @@ -71,29 +75,35 @@ function createListenerCollection() {
}
}

type ListenerCollection = ReturnType<typeof createListenerCollection>

export default class Subscription {
constructor(store, parentSub) {
private store: any
private parentSub?: Subscription
private unsubscribe?: () => void
private listeners?: ListenerCollection
public onStateChange?: () => void

constructor(store: any, parentSub?: Subscription) {
this.store = store
this.parentSub = parentSub
this.unsubscribe = null
this.listeners = nullListeners
this.unsubscribe = undefined
this.listeners = undefined

this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
}

addNestedSub(listener) {
addNestedSub(listener: () => void) {
this.trySubscribe()
return this.listeners.subscribe(listener)
return this.listeners?.subscribe(listener)
}

notifyNestedSubs() {
this.listeners.notify()
this.listeners?.notify()
}

handleChangeWrapper() {
if (this.onStateChange) {
this.onStateChange()
}
this.onStateChange?.()
}

isSubscribed() {
Expand All @@ -113,9 +123,9 @@ export default class Subscription {
tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe()
this.unsubscribe = null
this.listeners.clear()
this.listeners = nullListeners
this.unsubscribe = undefined
this.listeners?.clear()
this.listeners = undefined
}
}
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// "lib": [], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
"emitDeclarationOnly": true,
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
Expand Down

0 comments on commit 4f9c3b8

Please sign in to comment.