Skip to content

Instantly share code, notes, and snippets.

@dfoverdx

dfoverdx/_app.tsx

Last active Jan 17, 2019
Embed
What would you like to do?
React Context Factory for Next.js
import App, { Container } from 'next/app';
import React, { Component } from 'react';
import { ExampleContextProvider, ExampleContextType } from './example-context';
interface Props {
pageProps: {};
Component: Component;
exampleContext?: ExampleContextType;
}
export default class MyApp extends App<Props> {
static async getInitialProps({ Component, ctx }) {
let pageProps: { [key: string]: any } = {},
req = ctx.req;
if (Component.getInitialProps) {
Object.assign(pageProps, await Component.getInitialProps(ctx));
}
if (!req) {
// client-side transition
return { pageProps };
}
let exampleContext: ExampleContextType = {
value1: req.value1,
value2: req.value2
};
return { pageProps, exampleContext };
}
render() {
const { Component, pageProps, exampleContext } = this.props;
return (
<Container>
<ExampleContextProvider value={exampleContext}>
<Component {...pageProps} />
</ExampleContextProvider>
</Container>
);
}
}
import React, { Component } from 'react';
/**
* A simple wrapper of a context's value.
*/
interface Props<T> {
value?: T;
}
/**
* Function that generates a bitmap of changed values between two sets of properties.
*
* @see https://hph.is/coding/bitmasks-react-context
*/
type CalculateChangedBits<T> = (prevValue?: T, nextValue?: T) => number;
type ContextFactoryResult<T> = {
/**
* The `React.Context` created.
*/
Context: React.Context<T>;
/**
* The provider component that wraps the context.
*/
ContextProvider: new(...args: any[]) => Component<Props<T>, T>;
}
/**
* Creates a client-side page-transition-resilient context.
*
* @param defaultValue Default value passed to `React.createContext()`
* @param displayName The `displayName` of the returned context
* @param calculateChangedBits Optional function that returns a bitmask representing which value properties were
* changed.
*/
export default function createContext<T>(
defaultValue: T,
displayName: string,
calculateChangedBits?: CalculateChangedBits<T>
): ContextFactoryResult<T> {
const Context = React.createContext(defaultValue, calculateChangedBits);
Context.displayName = displayName;
class ContextProvider extends Component<Props<T>, T> {
constructor(props: Props<T>) {
super(props);
this.state = props && props.value || defaultValue;
}
/**
* When props are updated, we set the state to the new props value if it isn't undefined. When updating the
* state, we don't want to rerender, since the new rendering will be identical to the previous one.
*/
private updatingState: boolean = false;
componentDidUpdate() {
if (this.props.value) {
this.updatingState = true;
this.setState(this.props.value);
}
}
shouldComponentUpdate() {
if (this.updatingState) {
this.updatingState = false;
return false;
}
return true;
}
render() {
return (
<Context.Provider value={this.props.value || this.state}>
{this.props.children}
</Context.Provider>
);
}
}
return {
Context,
ContextProvider
};
}
import createContext from './context-factory';
export interface ExampleContextType {
value1: string,
value2: number
}
const defaultValue: ExampleContextType = {
value1: '',
value2: 0
};
const {
Context: ExampleContext,
ContextProvider
} = createContext<ExampleContextType>(defaultValue, 'ExampleContext');
export default ExampleContext;
export const ExampleContextProvider = ContextProvider;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment