Skip to content

Instantly share code, notes, and snippets.

@karlbright
Created November 29, 2018 06:22
Show Gist options
  • Save karlbright/26a86f61226a15f6130703dc25487f9a to your computer and use it in GitHub Desktop.
Save karlbright/26a86f61226a15f6130703dc25487f9a to your computer and use it in GitHub Desktop.
import React, {Component, Fragment} from 'react'
import {storiesOf} from '@storybook/react'
import sample from 'lodash/sample'
const stories = storiesOf('⚛ Context', module)
// # New React Context API
// New React Context API requires both
// React >= 16.3.0 along with ReactDOM >= 16.3.0
//
// Context provides a way to pass data through the component tree
// without having to pass props down manually at every level.
// Creating a context is simple with the new React Context API,
// and gives us access to both a consumer and provider.
const ctx = React.createContext('https://www.youtube.com/results?search_query=')
// We use React.createContext and provide a default value. The default value is
// used when a matching context provider is not found above in the tree.
stories.add('Using context consumer without a provider', () => (
<ctx.Consumer>{value => <a href={value + 'taylor swift'}>View Taylor Swift on YouTube</a>}</ctx.Consumer>
))
// We can use a context provider to provide a different value. The default value
// is no longer used if a provider is found. In this exmaple we can see that a provider
// can be found, but no value is provided by the provider, therefore the the value
// is null.
stories.add('Using context consumer and provider without a value', () => (
<ctx.Provider>
<ctx.Consumer>{spoon => spoon ? 'Be the spoon 🥄' : 'There is no spoon'}</ctx.Consumer>
</ctx.Provider>
))
// We can provide a value by passing a `value` prop to the context provider, which
// will then be accessible via the consumers below it in the tree. The consumer
// gets the value from closest matching provider.
stories.add('Using context consumer and provider', () => (
<ctx.Provider value='https://www.google.com/search?q='>
<ctx.Consumer>{value => <a href={value + 'taylor swift'}>View Taylor Swift on Google</a>}</ctx.Consumer><br />
<ctx.Provider value='https://duckduckgo.com/?q='>
<ctx.Consumer>{value => <a href={value + 'taylor swift'}>View Taylor Swift on DuckDuckGo</a>}</ctx.Consumer>
</ctx.Provider>
</ctx.Provider>
))
// Context consumers can be cumbersome to use by way of the context consumer directly.
// While our short examples work well enough, we might want to do something a little more
// crazy. This is where we can use `contextTypes`, and encapsulate the context consumer
// functionality within a component.
class BasicSearcher extends Component {
static contextType = ctx
state = { term: 'taylor swift' }
handleInputChange = (event) => this.setState({ term: event.target.value })
render () {
return (
<div>
<input value={this.state.term} onChange={this.handleInputChange} /><br />
<a href={this.context + this.state.term}>{this.context + this.state.term}</a>
</div>
)
}
}
stories.add('Using contextType instead of context.Consumer', () => (
<BasicSearcher />
))
// It's important to remember that context provides a way to pass data through
// the component tree without having to pass the props manually. So a heavily
// nested component tree may work like this.
const T = ({children}) => <div className='s'>{children}</div>
const A = ({children}) => <div className='w'>{children}</div>
const Y = ({children}) => <div className='i'>{children}</div>
const L = ({children}) => <div className='f'>{children}</div>
const O = ({children}) => <div className='t'>{children}</div>
const R = ({children}) => children
stories.add('Avoid prop drilling', () => (
<ctx.Provider value='https://duckduckgo.com/?q='>
<T><A><Y><L><O><R><BasicSearcher /></R></O></L></Y></A></T>
</ctx.Provider>
))
// The value you provide in you context is not limited to a string of course.
// This means you can provide many values within context, such as a search prefix
// and service name.
class AdvancedSearcher extends Component {
static contextType = ctx
state = { term: 'taylor swift' }
handleInputChange = (event) => this.setState({ term: event.target.value })
render () {
const {service, prefix} = this.context
return (
<div>
<input value={this.state.term} onChange={this.handleInputChange} /><br />
<a href={prefix + this.state.term}>Search for {this.state.term} on {service}</a>
</div>
)
}
}
stories.add('Object as a context value', () => (
<ctx.Provider value={{ prefix: 'https://www.youtube.com/results?search_query=', service: 'YouTube' }}>
<AdvancedSearcher />
</ctx.Provider>
))
// It's common practice to create your own provider that renders your context provider. This is our final
// example, which gets closer to a real world example of context.
const SERVICES = [
{ service: 'DuckDuckGo', prefix: 'https://www.duckduckgo.com/?q=' },
{ service: 'Google', prefix: 'https://www.google.com/search?q=' },
{ service: 'YouTube', prefix: 'https://www.youtube.com/results?search_query=' }
]
class SearchEngineProvider extends Component {
state = SERVICES[0]
handleChange = (event) => this.setState(SERVICES[event.target.value])
render () {
return (
<Fragment>
<select onChange={this.handleChange}>
{SERVICES.map(({service},i) => <option key={service} value={i}>{service}</option>)}
</select>
<ctx.Provider value={this.state}>{this.props.children}</ctx.Provider>
</Fragment>
)
}
}
stories.add('Our very own provider', () => (
<SearchEngineProvider>
<T><A><Y><L><O><R><AdvancedSearcher /></R></O></L></Y></A></T>
</SearchEngineProvider>
))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment