Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert Context/Provider/Subscription to Typescript #1742

Merged
merged 12 commits into from
Jun 26, 2021
Merged
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']
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know rollup so I'm not sure if this is necessary, but it seemed reasonable


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