1.1.1 • Published 5 years ago

react-connect-context-map v1.1.1

Weekly downloads
86
License
MIT
Repository
github
Last release
5 years ago

react-connect-context-map

This is a fork of https://github.com/contiamo/react-connect-context.

This module exports a function factory that produces an HOC to wrap a component and provide the value of the context as props to the component, much like the react-redux API. The benefits of this approach versus the traditional Consumer:

  • All values are passed as props and can be read in your component's lifecycle methods
  • You can trivially export a "pure" version of the component as well as a connected version
  • Unlike a Redux container, the connected component can be rendered without being nested in a Provider component
  • The factory produces an HOC which can be applied to multiple components
  • You can select slices of the context value and map them to props that fit the component API
  • You can reference the props passed into the connected component when mapping the context value to props
  • You can customize the merging behavior to translate all props fed to the wrapped component

Getting Started

  1. yarn add react-connect-context-map
  2. At the top of your file, import { connectContext } from "react-connect-context-map"
  3. Wrap your component in the function as so: connectContext(Context.Consumer)(MyComponent)

Examples

Introductory example

import React from "react"
import { render } from "react-dom"
import { connectContext } from "react-connect-context-map"

// The shape of the context value
interface ContextType {
  color: string
}

// The shape of the props passed to your component
interface MergedPropsType extends ContextType {
  children: React.ReactNode
  myProp: string
}

// Create a new React Context instance
const Context = React.createContext<ContextType>({ color: "red" })

// Create an HOC for connecting your component to context
const createContainer = connectContext<ContextType>(Context.Consumer)

// The connected component will receive the props passed in as well as values
// from the context object by default
const Content: React.SFC<MergedPropsType> = ({ children, color, myProp }) => (
  <div>
    <p>{children}</p>
    <div>
      I have looked into context and I've seen the color is:
      <span style={{ color }}>
        <strong>{color}</strong>
      </span>! I also get my own props like <strong>{myProp}</strong>!
    </div>
  </div>
)

// Call the HOC on the component to connect it to context
const ConnectedContent = createContainer<MergedPropsType>(Content)

const App: React.SFC = () => (
  <div className="demo">
    <ConnectedContent myProp="THIS IS MY PROP, HI!">
      Hello! I've written this component so that Magical Context-based text
      appears after children!
    </ConnectedContent>
  </div>
)

// Render the connected components in the provider
render(
  <Context.Provider value={{ color: "red" }}>
    <App />
  </Context.Provider>,
  document.querySelector("#root")
)

Mapping context properties to props

This example shows how a context value can be mapped to a different prop name or value.

import React from "react"
import { render } from "react-dom"
import { connectContext } from "react-connect-context-map"

interface ContextType {
  color: string
}

// The shape of the props that have been derived from context
interface ContextPropsType {
  isRed: boolean
}

// The shape of the props that are not derived from context and may be
// passed to the connected component
interface OwnPropsType {
  myProp: string
}

// The shape of the props passed to the connected component
interface MergedPropsType extends ContextPropsType, OwnPropsType {}

const Context = React.createContext<ContextType>({ color: "red" })

const Content: React.SFC<MergedPropsType> = ({ isRed, myProp }) => (
  <p>This pen {isRed ? "is" : "is not"} red. My prop is {myProp}.</p>
)

// ContextPropsType can be used when the result of mapContextToProps does
// not match the shape of ContextType
const createContainer = connectContext<ContextType, ContextPropsType>(
  Context.Consumer,
  context => ({
    isRed: context.color === "red"
  })
)

// OwnPropsType can be used to define the props on the wrapped component
// when additional props not inferred from context are provided
const ConnectedContent = createContainer<
  MergedPropsType,
  OwnPropsType
>(Content)

render(
  <Context.Provider value={{ color: "red" }}>
    <App />
  </Context.Provider>,
  document.querySelector("#root")
)

Customizing prop merging

This example shows how you can customize the merge behavior. Here we even map a non-context prop to another key.

import React from "react"
import { render } from "react-dom"
import { connectContext } from "react-connect-context-map"

interface ContextType {
  color: string
}

interface OwnPropsType {
  theProp: string
}

interface MergedPropsType extends ContextType, OwnPropsType {}

const Context = React.createContext<ContextType>({ color: "red" })

const Content: React.SFC<MergedPropsType> = ({ color, theProp }) => (
  <p>This pen {isRed ? "is" : "is not"} red. My prop is {theProp}.</p>
)

const createContainer = connectContext<ContextType>(
  Context.Consumer,
  null,
  // Merge context properties into props and translate myProp to theProp
  (context, props) => ({
    ...context,
    theProp: props.myProp
  })
)

const ConnectedContent = createContainer<
  MergedPropsType,
  OwnPropsType
>(Content)

render(
  <Context.Provider value={{ color: "red" }}>
    <App />
  </Context.Provider>,
  document.querySelector("#root")
)

Using non-object context values with mapContextToProps

This example shows how you can allow for non-object context values if you provide a custom mapContextToProps function.

import React from "react"
import { render } from "react-dom"
import { connectContext } from "react-connect-context-map"

// Context value is not an object
type ContextType = string

interface ContextPropsType {
  color: string
}

interface OwnPropsType {
  myProp: string
}

interface MergedPropsType extends ContextPropsType, OwnPropsType {}

const Context = React.createContext<ContextType>({ color: "red" })

const Content: React.SFC<MergedPropsType> = ({ color, theProp }) => (
  <p>This pen {isRed ? "is" : "is not"} red. My prop is {theProp}.</p>
)

const createContainer = connectContext<
  ContextType,
  ContextPropsType
>(
  Context.Consumer,
  // Context value is mapped to an object that conforms to
  // ContextPropsType
  context => ({ color: context })
)

const ConnectedContent = createContainer<
  MergedPropsType,
  OwnPropsType
>(Content)

render(
  <Context.Provider value="red">
    <App />
  </Context.Provider>,
  document.querySelector("#root")
)

API

connectContext

Factory function that creates a container component HOC to consume context Context and map values to match ContextProps.

connectContext<Context, ContextProps extends Object = Context>(
  // The React Consumer component.
  ContextConsumer: React.Consumer<Context>,

  // A function that maps the consumed context value to props to pass to
  // the component. The default function requires the context value to be
  // an object and maps its properties to component props.
  mapContextToProps: MapContextToProps<Context, ContextProps, OwnProps>
    = context => context,

  // A function that merges the props that have been mapped from context
  // values with the props passed to the connected component. The default
  // function merges context props with the passed props, with the latter
  // overwriting the former.
  mergeProps: MergeProps<ContextProps, OwnProps, MergedProps> =
    (contextProps, ownProps) => ({ ...contextProps, ...ownProps })
): createContainer // HOC to connect a component.

createContainer

An HOC that returns a connected component that accepts props OwnProps and derives ContextProps to merge into MergedProps. It returns a component that accepts OwnProps as props and renders the given component with MergedProps.

createContainer<MergedProps extends ContextProps, OwnProps = Partial<MergedProps>>(
  // The component to connect.
  Component: React.SFC<MergedProps>
): React.SFC<OwnProps> // The container component.

MapContextToProps

A function type that maps the consumed context value Context and props passed to the connected component OwnProps to a subset of the props that can be derived from context ContextProps, to pass to the component.

type MapContextToProps<Context, ContextProps, OwnProps> = (
  // The consumed context value.
  context: Context,

  // The props passed to the connected component.
  ownProps: OwnProps
) => ContextProps // The props derived from context.

MergeProps

A function type that merges the props that have been mapped from context ContextProps with the props passed to the connected component OwnProps to return all the props MergedProps, to pass to the wrapped component.

type MergeProps<ContextProps, OwnProps, MergedProps> = (
  // The result of `mapContextToProps`.
  contextProps: ContextProps,

  // The props passed to the connected component.
  ownProps: OwnProps
) => MergedProps // The props to pass to the given component.

Frequently Asked Questions

Can I pick state and only re-render when necessary?

Sure. Consider using PureComponent or shouldComponentUpdate to let your components know when or when not to update.

Additionally, unlike Redux, React 16.3 allows the creation of multiple, composable Contexts, so ideally, you'd be using a Context that is small enough to house just the information that you'd like to reuse in order to properly separate concerns and correctly use the principle of least privilege when passing context around.


Made with ❤️ at Contiamo in Berlin.