1- import React , { Component } from 'react'
1+ import React , { useState , useEffect , useLayoutEffect , useRef } from 'react'
2+ import { isContextProvider } from 'react-is'
23import PropTypes from 'prop-types'
34import { ReactReduxContext } from './Context'
4- import Subscription from '../utils/Subscription'
55
6- class Provider extends Component {
7- constructor ( props ) {
8- super ( props )
6+ // React currently throws a warning when using useLayoutEffect on the server.
7+ // To get around it, we can conditionally useEffect on the server (no-op) and
8+ // useLayoutEffect in the browser. We need useLayoutEffect to ensure the store
9+ // subscription callback always has the selector from the latest render commit
10+ // available, otherwise a store update may happen between render and the effect,
11+ // which may cause missed updates; we also must ensure the store subscription
12+ // is created synchronously, otherwise a store update may occur before the
13+ // subscription is created and an inconsistent state may be observed
14+ const useIsomorphicLayoutEffect =
15+ typeof window !== 'undefined' &&
16+ typeof window . document !== 'undefined' &&
17+ typeof window . document . createElement !== 'undefined'
18+ ? useLayoutEffect
19+ : useEffect
920
10- const { store } = props
21+ export function Provider ( { context, store, children } ) {
22+ // construct a new updater and assign it to a ref on initial render
1123
12- this . notifySubscribers = this . notifySubscribers . bind ( this )
13- const subscription = new Subscription ( store )
14- subscription . onStateChange = this . notifySubscribers
24+ let [ contextValue , setContextValue ] = useState ( ( ) => ( {
25+ state : store . getState ( ) ,
26+ store
27+ } ) )
1528
16- this . state = {
17- store,
18- subscription
29+ let mountedRef = useRef ( false )
30+ useIsomorphicLayoutEffect ( ( ) => {
31+ mountedRef . current = true
32+ return ( ) => {
33+ mountedRef . current = false
1934 }
35+ } , [ ] )
2036
21- this . previousState = store . getState ( )
22- }
23-
24- componentDidMount ( ) {
25- this . state . subscription . trySubscribe ( )
26-
27- if ( this . previousState !== this . props . store . getState ( ) ) {
28- this . state . subscription . notifyNestedSubs ( )
37+ useIsomorphicLayoutEffect ( ( ) => {
38+ let unsubscribe = store . subscribe ( ( ) => {
39+ if ( mountedRef . current ) {
40+ setContextValue ( { state : store . getState ( ) , store } )
41+ }
42+ } )
43+ if ( contextValue . state !== store . getState ( ) ) {
44+ setContextValue ( { state : store . getState ( ) , store } )
2945 }
30- }
31-
32- componentWillUnmount ( ) {
33- if ( this . unsubscribe ) this . unsubscribe ( )
34-
35- this . state . subscription . tryUnsubscribe ( )
36- }
37-
38- componentDidUpdate ( prevProps ) {
39- if ( this . props . store !== prevProps . store ) {
40- this . state . subscription . tryUnsubscribe ( )
41- const subscription = new Subscription ( this . props . store )
42- subscription . onStateChange = this . notifySubscribers
43- this . setState ( { store : this . props . store , subscription } )
46+ return ( ) => {
47+ unsubscribe ( )
4448 }
45- }
46-
47- notifySubscribers ( ) {
48- this . state . subscription . notifyNestedSubs ( )
49- }
49+ } , [ store ] )
5050
51- render ( ) {
52- const Context = this . props . context || ReactReduxContext
51+ // use context from props if one was provided
52+ const Context =
53+ context && context . Provider && isContextProvider ( < context . Provider /> )
54+ ? context
55+ : ReactReduxContext
5356
54- return (
55- < Context . Provider value = { this . state } >
56- { this . props . children }
57- </ Context . Provider >
58- )
59- }
57+ return < Context . Provider value = { contextValue } > { children } </ Context . Provider >
6058}
6159
6260Provider . propTypes = {
@@ -68,5 +66,3 @@ Provider.propTypes = {
6866 context : PropTypes . object ,
6967 children : PropTypes . any
7068}
71-
72- export default Provider
0 commit comments