Rendering <JSX /> with styles directly
"...refers to a simple technique for sharing code between React components using a prop whose value is a function."
// Smart Component
class DataProvider extends React.Component {
state = { message: 'world' };
render() {
return this.props.render(this.state);
}
}
// Dumb Component
function HelloWorld() {
return (
<DataProvider render={(data) => (
<h1>Hello {data.message}</h1>
)}/>
);
}
// Not transpiled
class DataProvider extends React.Component {
state = { message: 'world' };
render() {
return this.props.render(this.state);
}
}
// Transpiled
function HelloWorld() {
return React.createElement(
DataProvider,
{
render: function render(data) {
return React.createElement(
'h1',
null,
'Hello ',
data.message
);
}
}
);
}
class DataProvider extends React.Component {
state = { message: 'world' };
render() {
return this.props.children(this.state);
}
}
function HelloWorld() {
return (
<DataProvider>
{(data) => <h1>Hello {data.message}</h1>}
</DataProvider>
);
}
class FetchUser extends React.Component {
state = { user: null };
async componentDidMount() {
const result = await fetch('/user');
const user = await result.json();
this.setState({ user });
}
render() {
return this.props.children(this.state.user);
}
}
function Greeting() {
return (
<FetchUser>
{(user) => user ? <h1>Hello {user.firstName}</h1> : <div>Loading...</div>}
</FetchUser>
);
}
class Fetch extends React.Component {
state = { data: null };
async componentDidMount() {
const result = await fetch(this.props.url);
const data = await result.json();
this.setState({ data });
}
render() {
return this.props.children(this.state.data);
}
}
function Greeting() {
return (
<Fetch url="/user">
{(user) => user ? <h1>Hello {user.firstName}</h1> : <div>Loading...</div>}
</Fetch>
);
}
// non-render prop example
class SomeWrapper extends React.Component {
handleUpdate = (message) => this.setState({ message });
state = { message: 'world' };
render() {
return (
<div>
<ChildComponent
value={this.state.message}
onChange={this.handleUpdate}
/>
</div>
);
}
}
How do we do this with Render Props?
// What's the problem with this?
class DataProvider extends React.Component {
handleUpdate = (message) => this.setState({ message });
state = { message: 'world' };
render() {
return this.props.children({
message: this.state.message,
handleUpdate: this.handleUpdate,
});
}
}
What's wrong with this?
class DataProvider extends React.Component {
handleUpdate = (message) => this.setState({ message });
// note we assign the handler to the state object
state = {
message: 'world',
handleUpdate: this.handleUpdate
};
render() {
// pass the entire state object, not a NEW object
return this.props.children(this.state);
}
}
export default class ConsumerOfRenderProps extends React.Component {
componentDidMount() {
// ARG!!! No access to render props data.message
}
render() {
return(
<SomeRenderPropDataProvider>
{(data) => <h1>Hello {data.message}</h1>}
</SomeRenderPropDataProvider>
);
}
}
Cannot access output of the render prop component within any lifecycle hooks
class ConsumerOfRenderProps extends React.Component {
componentDidMount() {
console.log('Yay I can access:', this.props.message);
}
render() {
return (
<h1>Hello {this.props.message}</h1>
);
}
}
export default (props) => (
<SomeRenderPropDataProvider>
{(data) => <ConsumerOfRenderProps {...props} message={data.message} />}
</SomeRenderPropDataProvider>
);
Note: the component that is exported is already wrapped in the Render Props provider
export class DataProvider extends React.Component {
handleUpdate = (message) => this.setState({ message });
state = {
message: 'world',
handleUpdate: this.handleUpdate
};
render() {
return this.props.children(this.state);
}
}
export function withDataProvider(Component) {
function WithDataProvider(props) {
return (
<DataProvider>
(data) => <Component {...props} {...data} />
</DataProvider>
);
}
// Do all the other HoC best practices here :)
return WithDataProvider;
}
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
Think of context data as global to your React app
Examples: Sharing authenticated user data, a UI theme, or preferred language
class App extends React.Component {
state = {
user: null,
avatarSize: null
}
componentDidMount() {
fetch('/user').then(data => this.setState({
user: data.user,
avatarSize: data.avatarSize
}));
}
render() {
return (
<Page
user={this.state.user}
avatarSize={this.state.avatarSize}
/>
);
}
}
const Page = (props) => (
return (
<main class="page">
<PageLayout
user={props.user}
avatarSize={props.avatarSize}
/>
</main>
);
);
const PageLayout = (props) => (
return (
<div class="page-layout">
<NavigationBar
user={props.user} avatarSize={props.avatarSize}
/>
<SideBar />
<MainContent>{/* stuff */}</MainContent>
</div>
);
);
const NavigationBar = (props) => (
return (
<nav class="nav-bar">
<MainNav>{/* stuff */}</MainNav>
<AvatarLink user={props.user} size={props.avatarSize} />
</nav>
);
);
const AvatarLink = (props) => (
return (
<Link href={props.user.permalink}>
<Avatar user={props.user} size={props.avatarSize} />
</Link>
);
);
class App extends React.Component {
state = {
user: null,
avatarSize: null
}
componentDidMount() {
fetch('/user').then(data => this.setState({
user: data.user,
avatarSize: data.avatarSize
}));
}
render() {
return (
<UserContext.Provider value={this.state}>
<Page />
</UserContext.Provider>
);
}
}
const AvatarLink = (props) => (
<UserContext.Consumer>
{({ user, avatarSize }) => (
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
)}
</UserContext.Consumer>
);
const value = {
theme: themes.dark,
toggleTheme: () => { /* no-op */ },
};
export const ThemeContext = React.createContext(value);
// <ThemeContext.Provider />
// <ThemeContext.Consumer />
import ThemeContext from './ThemeContext';
import Page from './Page';
export class App extends React.Component {
handleToggleTheme = (theme) => this.setState({ theme });
state = {
theme: themes.dark,
toggleTheme: this.handleToggleTheme,
};
render() {
return (
<ThemeContext.Provider value={this.state}>
<Page />
</ThemeContext.Provider>
);
}
}
import ThemeContext from './ThemeContext';
export const ThemeToggler = () => (
<ThemeContext.Consumer>
{({ toggleTheme }) =>
<ButtonBar>
<Button onClick={() => toggleTheme(theme.dark)}>Dark</Button>
<Button onClick={() => toggleTheme(theme.light)}>Light</Button>
</ButtonBar>
}
<ThemeContext.Consumer>
);
import ThemeContext from './ThemeContext';
export const ThemeToggler = () => (
<ThemeContext.Consumer>
{({ toggleTheme }) =>
<ButtonBar>
<Button onClick={() => toggleTheme(theme.dark)}>Dark</Button>
<Button onClick={() => toggleTheme(theme.light)}>Light</Button>
</ButtonBar>
}
<ThemeContext.Consumer>
);