Component Component
NOTE: This component was developed in the days before React Hooks. In most cases, you should probably build your function components using hooks and use a class component in the rare cases you need them. We may deprecate this component in the future.
A dynamic, functional version of React.Component
, a component component if you will. Useful for inline lifecycles and state.
<Component initialState={{ hue: 0 }}> {({ setState, state }) => ( <div style={{ textAlign: "center" }}> <button onClick={() => setState({ hue: Math.random() * 360 })}> Generate Triad Colorscheme </button> <br /> {[1, 2, 3].map((n) => ( <div key={n} style={{ display: "inline-block", margin: 10, width: "2em", height: "2em", borderRadius: "50%", background: `hsl(${state.hue + n * 120}, 50%, 50%)`, transition: "background-color 200ms ease", }} /> ))} </div> )}</Component>
Installation
From the command line in your project directory, run npm install @reach/component-component
or yarn add @reach/component-component
. Then import the component:
npm install @reach/component-component# oryarn add @reach/component-component
import Component from "@reach/component-component";
Component API
Props
Prop | Type |
---|---|
initialState | object |
getInitialState | func |
refs | object |
getRefs | func |
didMount | func |
didUpdate | func |
willUnmount | func |
getSnapshotBeforeUpdate | func |
shouldUpdate | func |
children | func |
render | func |
initialState
initialState?: object
An object of initial state.
function Example() { return ( <Component initialState={{ count: 10 }}> {({ state }) => <div>Count is {state.count}</div>} </Component> );}
getInitialState
getInitialState?: () => object
A function to return intitial state. Use this when initial state is computed.
In the following example, Date.now()
will not be called every time a parent component causes this component to re-render.
<Component getInitialState={() => ({ now: Date.now() })}> {({ state }) => <div>Now is: {state.now}</div>}</Component>
However, in the next example, Date.now()
would be called with every re-render, which is not what we want.
// 😭<Component initialState={{ now: Date.now() }} />
refs
refs?: object
Put any refs you need to keep track of here, stuff like DOM nodes, timers, and subcriptions.
function Example() { return ( <Component refs={{ input: null }}> {({ refs }) => ( <form onSubmit={(event) => { event.preventDefault(); alert(refs.input.value); }} > <input ref={(node) => (refs.input = node)} type="text" />{" "} <button type="submit">Go</button> </form> )} </Component> );}
getRefs
getRefs?: () => object
Use this when any of your refs are computed.
<Component getRefs={() => { return { input: React.createRef(), popupContainer: document.createElement("div"), }; }}/>
didMount
didMount?: (args: { state: object, props: object, refs: object, setState: Function, forceUpdate: Function }) => void
Called when the component mounts.
Perhaps you want some async data but don't want to make an entirely new component just for the lifecycles to get it:
<Component initialState={{ gists: null }} didMount={({ setState }) => { fetch("https://api.github.com/gists?per_page=5") .then((res) => res.json()) .then((gists) => setState({ gists })); }}> {({ state }) => state.gists ? ( <ul> {state.gists.map((gist) => ( <li key={gist.id}> <a href={gist.html_url}>{gist.description || gist.id}</a> </li> ))} </ul> ) : ( <div>Loading...</div> ) }</Component>
See also React Docs.
didUpdate
didMount?: (args: { state: object, props: object, refs: object, setState: Function, forceUpdate: Function, prevProps?: object, prevState?: object }) => void
Called when the component updates. See React Docs.
willUnmount
willUnmount?: (args: { state: object, props: object, refs: object }) => void
Called when the component will be removed from the page. See React Docs.
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate?: (args: { state: object, props: object, refs: object, prevProps: object, prevState: object }) => any
See React Docs.
shouldUpdate
shouldUpdate?: (args: { state: object, props: object, nextProps: object, nextState: object }) => boolean
Return true
to signify the component needs an update, false
if it does not. Useful for wrapping up expensive parts of your app without having to refactor to new components.
For example: often you find just one part of your component is expensive to render, maybe because of a large SVG with a dynamic style or two. Rather than pulling the elements out of your component and putting them in a new PureComponent
, you can inline a shoudlUpdate
check:
function Example() { return ( <Component initialState={{ hue: 0 }}> {({ setState, state }) => ( <div> <button onClick={() => { setState({ hue: Math.random() * 360 }); }} > Change Color </button> <Component hue={state.hue} shouldUpdate={({ nextProps, props }) => { return nextProps.hue !== props.hue; }} > <div> <svg width="100" height="100"> <path d="M20,30 Q40,5 50,30 T90,30" fill="none" stroke={`hsl(${state.hue}, 50%, 50%)`} strokeWidth="5" /> </svg> </div> </Component> </div> )} </Component> );}
See also React Docs.
children
children?: React.ReactNode | ((args: { state: object, props: object, refs: object, setState: Function, forceUpdate: Function }) => React.ReactNode)
Usual React children prop or render prop callback to provide the stateful parts of your component at render time..
function Example() { return ( <Component> <div>Hey, I am the child</div> </Component> );}
function Example() { return ( <Component initialState={{ hue: 0 }}> {({ setState, state }) => ( <div> <button onClick={() => setState({ hue: Math.random() * 360 })}> Generate Color </button> <br /> <svg width="100" height="100"> <path d="M20,30 Q40,5 50,30 T90,30" fill="none" stroke={`hsl(${state.hue}, 50%, 50%)`} strokeWidth="5" /> </svg> </div> )} </Component> );}
render
render?: (args: { state: object, props: object, refs: object, setState: Function, forceUpdate: Function }) => React.ReactNode
function Example() { return ( <Component initialState={{ hue: 0 }} render={({ setState, state }) => ( <div> <button onClick={() => setState({ hue: Math.random() * 360 })}> Generate Color </button> <br /> <svg width="100" height="100"> <path d="M20,30 Q40,5 50,30 T90,30" fill="none" stroke={`hsl(${state.hue}, 50%, 50%)`} strokeWidth="5" /> </svg> </div> )} /> );}
Todo App Example
Here is a pretty involved example showing just how composable this component is:
- "App state" containing all todos
- "Todo state" containing state for a specific todo
- Updates the document title to the number of todos in the list
- Optimized todo rendering, avoiding updates if the color has not changed
- Tracked refs
function Example() { return ( <Component getRefs={() => ({ input: React.createRef(), })} getInitialState={() => { return { todos: ["This is kinda weird"], }; }} > {({ state, setState, refs }) => ( <> <Component didUpdate={() => (document.title = state.todos.length + " Todos")} /> <div style={{ fontFamily: "sans-serif" }}> <h4>Todo List</h4> <form onSubmit={(event) => { event.preventDefault(); let node = refs.input.current; setState({ todos: state.todos.concat([node.value]) }); node.value = ""; }} > <input ref={refs.input} /> </form> <div> {state.todos.map((todo, index) => ( <Component key={index} getInitialState={() => ({ hue: Math.random() * 360 })} todo={todo} shouldUpdate={({ nextProps, nextState, props, state }) => { return ( nextProps.todo !== props.todo || nextState.hue !== state.hue ); }} > {({ setState, state }) => ( <div style={{ color: `hsl(${state.hue}, 50%, 50%)` }}> <button onClick={() => { setState({ hue: Math.random() * 360 }); }} > Change Color </button>{" "} {todo} </div> )} </Component> ))} </div> <p> <button onClick={() => setState({ todos: [] })}>Clear all</button> </p> </div> </> )} </Component> );}