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

[WIP] Use Hooks #967

Merged
merged 34 commits into from
Nov 5, 2019
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4c74a12
Tests are broken :( but it works
emmatown Oct 26, 2018
2560905
Add displayName
emmatown Oct 26, 2018
b0a2363
Use ref
emmatown Oct 26, 2018
1608f42
Remove stuff
emmatown Oct 26, 2018
306886f
Update react-test-renderer
emmatown Oct 26, 2018
6b1bbfc
Use useContext in more places
emmatown Oct 26, 2018
3b84076
Fix a Global insertion order bug
emmatown Oct 26, 2018
44f6c7e
Add a test
emmatown Oct 26, 2018
6c5fa1a
Fix typo
emmatown Oct 26, 2018
587b561
Merge branch 'master' into hooks
emmatown Oct 26, 2018
dd42c9c
Merge branch 'master' into hooks
emmatown Oct 27, 2018
897fede
Remove passing the theme to the css prop, Global and ClassNames
emmatown Oct 28, 2018
1aa4655
Merge branch 'master' into hooks
emmatown Oct 31, 2018
2bf9383
Merge branch 'master' into hooks
emmatown Nov 25, 2018
9d8cc37
stuff
emmatown Nov 25, 2018
e725feb
stuff
emmatown Nov 25, 2018
fae9f0a
changes
emmatown Nov 25, 2018
33df35a
Merge branch 'master' into hooks
emmatown Nov 25, 2018
d1e3293
Add a test back
emmatown Nov 25, 2018
b899c8b
Fix a thing
emmatown Nov 25, 2018
cb0feb2
Update stuff
emmatown Nov 25, 2018
70118b7
Merge branch 'master' into hooks
emmatown May 22, 2019
7e9b819
Update things
emmatown May 22, 2019
4890527
Merge branch 'next' into hooks
Andarist Nov 5, 2019
95b1398
Drop support for innerRef entirely
Andarist Nov 5, 2019
8b6bee6
Set correct peerDeps on react + upgrade react-related devDeps
Andarist Nov 5, 2019
407a062
Merge branch 'next' into hooks
Andarist Nov 5, 2019
6e98780
Fix tests
Andarist Nov 5, 2019
a6439f2
Fix linting error
Andarist Nov 5, 2019
bd7222e
Remove custom useContext flow types
Andarist Nov 5, 2019
65d1ccc
Fix flow error
Andarist Nov 5, 2019
e45467d
Fix layour effect input array to include serialized.name instead of w…
Andarist Nov 5, 2019
1103861
Add a changeset
emmatown Nov 5, 2019
6af574b
Add another changeset
emmatown Nov 5, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"version-packages": "lerna version --preid=beta"
},
"resolutions": {
"**/react": "16.5.2",
"**/react-dom": "16.5.2",
"**/react": "16.7.0-alpha.0",
"**/react-dom": "16.7.0-alpha.0",
"**/browserslist": "^3.2.8",
"**/react-primitives/react-native-web": "0.9.3"
},
Expand Down Expand Up @@ -103,7 +103,7 @@
"react-native": "^0.57.0",
"react-primitives": "^0.6.1",
"react-router-dom": "^4.2.2",
"react-test-renderer": "^16.3.2",
"react-test-renderer": "16.7.0-alpha.0",
"react-testing-library": "^5.2.0"
},
"author": "Kye Hohenberger",
Expand Down
33 changes: 33 additions & 0 deletions packages/core/__tests__/global-insertion-after-others.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/** @jsx jsx */
import { jsx, Global } from '@emotion/core'
import { render } from 'react-testing-library'

let getDataAttributes = () =>
Array.from(document.querySelectorAll('style[data-emotion]'), x =>
x.getAttribute('data-emotion')
)

test('Global style element insertion after insertion of other styles', () => {
let Comp = ({ second }) => (
<div>
<div
css={{
color: 'green'
}}
/>
{second && (
<Global
styles={{
html: {
backgroundColor: 'hotpink'
}
}}
/>
)}
</div>
)
let { rerender } = render(<Comp />)
expect(getDataAttributes()).toEqual(['css'])
rerender(<Comp second />)
expect(getDataAttributes()).toEqual(['css-global', 'css'])
})
80 changes: 35 additions & 45 deletions packages/core/src/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,55 @@ import createCache from '@emotion/cache'

let EmotionCacheContext = React.createContext(isBrowser ? createCache() : null)

export let useContext: <Value>(
context: React$Context<Value>
) => Value = (React: any).useContext

export let useState: <State>(
initialState: (() => State) | State
) => [State, (State) => void] = (React: any).useState

export let ThemeContext = React.createContext<Object>({})
export let CacheProvider: React.ComponentType<{ value: EmotionCache }> =
// $FlowFixMe
EmotionCacheContext.Provider

let forwardRef: <Props>(
render: (props: Props, ref: any) => React.Node
) => React.StatelessFunctionalComponent<Props> = (React: any).forwardRef

let withEmotionCache = function withEmotionCache<Props, Ref: React.Ref<*>>(
func: (props: Props, cache: EmotionCache, ref: Ref) => React.Node
): React.StatelessFunctionalComponent<Props> {
let render = (props: Props, ref: Ref) => {
return (
<EmotionCacheContext.Consumer>
{(
// $FlowFixMe we know it won't be null
cache: EmotionCache
) => {
return func(props, cache, ref)
}}
</EmotionCacheContext.Consumer>
)
}
// $FlowFixMe
return React.forwardRef(render)
return forwardRef((props: Props, ref: Ref) => {
let cache = useContext(EmotionCacheContext)

return func(props, cache, ref)
})
}

if (!isBrowser) {
class BasicProvider extends React.Component<
{ children: EmotionCache => React.Node },
{ value: EmotionCache }
> {
state = { value: createCache() }
render() {
return (
<EmotionCacheContext.Provider {...this.state}>
{this.props.children(this.state.value)}
</EmotionCacheContext.Provider>
)
}
}

withEmotionCache = function withEmotionCache<Props>(
func: (props: Props, cache: EmotionCache) => React.Node
): React.StatelessFunctionalComponent<Props> {
return (props: Props) => (
<EmotionCacheContext.Consumer>
{context => {
if (context === null) {
return (
<BasicProvider>
{newContext => {
return func(props, newContext)
}}
</BasicProvider>
)
} else {
return func(props, context)
}
}}
</EmotionCacheContext.Consumer>
)
return (props: Props) => {
let cache = useContext(EmotionCacheContext)
if (cache === null) {
// yes, we're potentially creating this on every render
// it doesn't actually matter though since it's only on the server
// so there will only every be a single render
// that could in the future because of suspense and etc. but for now,
// this works and i don't want to optimise for a future thing that we aren't sure about
cache = createCache()
return (
<EmotionCacheContext.Provider value={cache}>
{func(props, cache)}
</EmotionCacheContext.Provider>
)
} else {
return func(props, cache)
}
}
}
}

Expand Down
186 changes: 84 additions & 102 deletions packages/core/src/global.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
// @flow
import * as React from 'react'
import { withEmotionCache, ThemeContext } from './context'
import {
isBrowser,
type EmotionCache,
type SerializedStyles,
insertStyles
} from '@emotion/utils'
import { withEmotionCache, ThemeContext, useContext } from './context'
import { isBrowser, insertStyles } from '@emotion/utils'
import { StyleSheet } from '@emotion/sheet'
import { serializeStyles } from '@emotion/serialize'

Expand All @@ -16,8 +11,17 @@ type GlobalProps = {
+styles: Styles | (Object => Styles)
}

let useRef: <Val>(Val) => { current: Val } = (React: any).useRef

let useMutationEffect: (() => mixed, mem?: Array<any>) => void = (React: any)
.useMutationEffect

let warnedAboutCssPropForGlobal = false

// maintain place over rerenders.
// initial render from browser, insertBefore context.sheet.tags[0] or if a style hasn't been inserted there yet, appendChild
// initial client-side render from SSR, use place of hydrating tag

export let Global: React.StatelessFunctionalComponent<
GlobalProps
> = /* #__PURE__ */ withEmotionCache((props: GlobalProps, cache) => {
Expand All @@ -37,105 +41,83 @@ export let Global: React.StatelessFunctionalComponent<
}
let styles = props.styles

if (typeof styles === 'function') {
return (
<ThemeContext.Consumer>
{theme => {
let serialized = serializeStyles(cache.registered, [styles(theme)])

return <InnerGlobal serialized={serialized} cache={cache} />
}}
</ThemeContext.Consumer>
)
}
let serialized = serializeStyles(cache.registered, [styles])

return <InnerGlobal serialized={serialized} cache={cache} />
})

type InnerGlobalProps = {
serialized: SerializedStyles,
cache: EmotionCache
}
let serialized = serializeStyles(cache.registered, [
typeof styles === 'function' ? styles(useContext(ThemeContext)) : styles
])

// maintain place over rerenders.
// initial render from browser, insertBefore context.sheet.tags[0] or if a style hasn't been inserted there yet, appendChild
// initial client-side render from SSR, use place of hydrating tag
if (isBrowser) {
let sheetRef = useRef(null)
useMutationEffect(
() => {
let sheet = new StyleSheet({
key: `${cache.key}-global`,
nonce: cache.sheet.nonce,
container: cache.sheet.container
})
// $FlowFixMe
let node: HTMLStyleElement | null = document.querySelector(
`style[data-emotion-${cache.key}="${serialized.name}"]`
)

class InnerGlobal extends React.Component<InnerGlobalProps> {
sheet: StyleSheet
componentDidMount() {
this.sheet = new StyleSheet({
key: `${this.props.cache.key}-global`,
nonce: this.props.cache.sheet.nonce,
container: this.props.cache.sheet.container
})
// $FlowFixMe
let node: HTMLStyleElement | null = document.querySelector(
`style[data-emotion-${this.props.cache.key}="${
this.props.serialized.name
}"]`
if (node !== null) {
sheet.tags.push(node)
}
if (cache.sheet.tags.length) {
sheet.before = cache.sheet.tags[0]
}
sheetRef.current = sheet
},
[cache]
)

if (node !== null) {
this.sheet.tags.push(node)
}
if (this.props.cache.sheet.tags.length) {
this.sheet.before = this.props.cache.sheet.tags[0]
}
this.insertStyles()
}
componentDidUpdate(prevProps) {
if (prevProps.serialized.name !== this.props.serialized.name) {
this.insertStyles()
}
}
insertStyles() {
if (this.props.serialized.next !== undefined) {
// insert keyframes
insertStyles(this.props.cache, this.props.serialized.next, true)
}
if (this.sheet.tags.length) {
// if this doesn't exist then it will be null so the style element will be appended
this.sheet.before = this.sheet.tags[0].nextElementSibling
this.sheet.flush()
useMutationEffect(
() => {
let sheet: StyleSheet = (sheetRef.current: any)
if (serialized.next !== undefined) {
// insert keyframes
insertStyles(cache, serialized.next, true)
}
cache.insert(``, serialized, sheet, false)
return () => {
// if this doesn't exist then it will be null so the style element will be appended
sheet.before = sheet.tags[0].nextElementSibling
sheet.flush()
}
},
[
serialized,
// we don't use the cache in this but we use the sheet
// which is determined by the cache
// and we don't have a reference to the sheet in render so
// we use the cache instead
cache
]
)
return null
} else {
let serializedNames = serialized.name
let serializedStyles = serialized.styles
let next = serialized.next
while (next !== undefined) {
serializedNames += ' ' + next.name
serializedStyles += next.styles
next = next.next
}
this.props.cache.insert(``, this.props.serialized, this.sheet, false)
}

componentWillUnmount() {
this.sheet.flush()
}
render() {
if (!isBrowser) {
let { serialized } = this.props

let serializedNames = serialized.name
let serializedStyles = serialized.styles
let next = serialized.next
while (next !== undefined) {
serializedNames += ' ' + next.name
serializedStyles += next.styles
next = next.next
}

let rules = this.props.cache.insert(
``,
{ name: serializedNames, styles: serializedStyles },
this.sheet,
false
)
let rules = cache.insert(
``,
{ name: serializedNames, styles: serializedStyles },
cache.sheet,
false
)

return (
<style
{...{
[`data-emotion-${this.props.cache.key}`]: serializedNames,
dangerouslySetInnerHTML: { __html: rules },
nonce: this.props.cache.sheet.nonce
}}
/>
)
}
return null
return (
<style
{...{
[`data-emotion-${cache.key}`]: serializedNames,
dangerouslySetInnerHTML: { __html: rules },
nonce: cache.sheet.nonce
}}
/>
)
}
}
})
Loading