1
1
import PropTypes from 'prop-types'
2
- import React , { createContext , Component } from 'react'
2
+ import React , { createContext , useLayoutEffect , useState } from 'react'
3
3
import { withTheme } from 'emotion-theming'
4
4
import { reduceObj } from '@exah/utils'
5
+ import { withDisplayName } from '../utils'
5
6
6
7
const listenForChanges = ( target , fn ) => {
7
8
fn ( )
@@ -14,67 +15,61 @@ const INITIAL_STATE = {
14
15
currentMediaKey : [ ]
15
16
}
16
17
17
- const { Provider, Consumer } = createContext ( INITIAL_STATE )
18
+ const Context = createContext ( INITIAL_STATE )
19
+ const { Provider, Consumer } = Context
18
20
19
- class CurrentMediaProvider extends Component {
20
- static defaultProps = {
21
- theme : { } ,
22
- media : null
21
+ const updateCurrentMedia = ( key , mql ) => ( currentMediaKey ) => {
22
+ if ( mql . matches ) {
23
+ return [ ...currentMediaKey , key ]
23
24
}
24
- static propTypes = {
25
- theme : PropTypes . object . isRequired ,
26
- media : PropTypes . object
27
- }
28
- state = INITIAL_STATE
29
- listeners = [ ]
30
- setCurrentMedia = ( key , mediaQueryList ) => {
31
- this . setState ( ( prevState ) => {
32
- const nextMediaKey = prevState . currentMediaKey . slice ( 0 )
33
-
34
- if ( mediaQueryList . matches ) {
35
- nextMediaKey . push ( key )
36
- } else {
37
- const index = nextMediaKey . indexOf ( key )
38
- if ( index === - 1 ) return
39
- nextMediaKey . splice ( index , 1 )
40
- }
41
-
42
- return {
43
- currentMediaKey : nextMediaKey
44
- }
45
- } )
46
- }
47
- componentDidMount ( ) {
48
- const media = this . props . media || this . props . theme . media || { }
49
25
50
- this . listeners = reduceObj ( ( acc , key , query ) => {
51
- const mediaQueryList = window . matchMedia ( query )
52
- const listener = ( ) => this . setCurrentMedia ( key , mediaQueryList )
26
+ return currentMediaKey . filter ( ( item ) => item !== key )
27
+ }
53
28
54
- return [ ...acc , listenForChanges ( mediaQueryList , listener ) ]
55
- } , [ ] , media )
56
- }
57
- componentWillUnmount ( ) {
58
- this . listeners . map ( ( fn ) => fn ( ) )
59
- }
60
- render ( ) {
61
- return (
62
- < Provider value = { this . state } >
63
- { this . props . children }
64
- </ Provider >
65
- )
66
- }
29
+ function CurrentMediaProvider ( {
30
+ theme = { } ,
31
+ media = theme . media || { } ,
32
+ children
33
+ } ) {
34
+ const [ currentMediaKey , setCurrentMedia ] = useState ( INITIAL_STATE . currentMediaKey )
35
+
36
+ useLayoutEffect ( ( ) => {
37
+ const listeners = reduceObj ( ( acc , key , query ) => {
38
+ const mql = window . matchMedia ( query )
39
+ const listener = ( ) => setCurrentMedia ( updateCurrentMedia ( key , mql ) )
40
+
41
+ return [ ...acc , listenForChanges ( mql , listener ) ]
42
+ } , [ ] ) ( media || { } )
43
+
44
+ return ( ) => listeners . map ( ( fn ) => fn ( ) )
45
+ } , [ media ] )
46
+
47
+ return (
48
+ < Provider value = { { currentMediaKey } } >
49
+ { children }
50
+ </ Provider >
51
+ )
52
+ }
53
+
54
+ CurrentMediaProvider . propTypes = {
55
+ theme : PropTypes . shape ( { media : PropTypes . object } ) ,
56
+ media : PropTypes . object
67
57
}
68
58
69
59
const CurrentMediaProviderWithTheme = withTheme ( CurrentMediaProvider )
70
60
71
- const withCurrentMedia = ( BaseComponent ) => ( props ) => (
72
- < Consumer >
73
- { ( state ) => < BaseComponent { ...state } { ...props } /> }
74
- </ Consumer >
75
- )
61
+ const withCurrentMedia = ( Comp ) => {
62
+ const HOC = ( props ) => (
63
+ < Consumer >
64
+ { ( state ) => < Comp { ...state } { ...props } /> }
65
+ </ Consumer >
66
+ )
67
+
68
+ return withDisplayName ( `withCurrentMedia(${ Comp . displayName || 'Component' } )` ) ( HOC )
69
+ }
76
70
77
71
export {
72
+ Context as CurrentMediaContext ,
78
73
CurrentMediaProviderWithTheme as CurrentMediaProvider ,
79
74
Consumer as CurrentMediaConsumer ,
80
75
withCurrentMedia
0 commit comments