GitHub package.json versionTypeScriptNPM
GitHub package.json versionTypeScriptNPM

Getting Started

Eagle Eye context and the React.Context API share a similar setup flow. Let us begin by installing the Eagle Eye context package.
npm install --save react-eagleeye

Creating the context store

To obtain a fresh context store, just call the parameterless createContext() function.
context.js
1 2 import { createContext } from '@webkrafters/react-observable-context'; export default createContext();

Providing the context store

Similar to the React.Context API, you can make the context store available to any section of the component tree by wrapping it in the context store's Provider.
Further readings on the Eagle Eye Context Provider could be found here.
provider-demo.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import React, { useEffect, useState } from 'react'; import ObservableContext from './context'; import Ui from './ui'; const createInitialState = c = ({ a: { b: { c, x: { y: { z: [ 2022 ] } } } } }); const ProviderDemo = ({ ageInMinutes: c = 0 }) => { const [ value, setValue ] = useState(() => createInitialState( c )); useEffect(() => { // similar to `store.setState`, use the following to update // only the changed slice of the context internal state. // Please use the `Set State` link in the TOC for more details. setValue({ a: { b: { c } } }); // OR // setValue({ a: { b: { c: { '@@REPLACE': c } } } }); // Do not do the following: it will override the context internal state. // setValue({ ...value, a: { ...value.a, b: { ...value.a.b, c } } }); }, [ c ]); return ( <ObservableContext.Provider value={ value }> <Ui /> </ObservableContext.Provider> ); }; ProviderDemo.displayName = 'ProviderDemo'; export default ProviderDemo;

Consuming the context store

There are two ways of consuming the Eagle Eye context store: The HOC method and the React Hook method.
Let's tackle the HOC method first. This method uses the connect(...) HOC function to wire up the context store to your consumer component.
It embodies the "set-it-and-forget-it" paradigm. Just set up a list of property paths to state slices to observe (see Selector Map). Context takes care of the rest.
The following is a sample of the HOC consumer method.
ui.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import React, { useCallback, useEffect } from 'react'; import { connect } from '@webkrafters/react-observable-context'; import ObservableContext from './context'; export const YearText = ({ data }) => ( <div>Year: { data.year }</div> ); export const YearInput = ({ data, resetState, setState }) => { const onChange = useCallback( e => setState({ a: { b: { x: { y: { z: { 0: e.target.value } } } } } }), [ setState ]); useEffect(() => { data.year > 2049 && resetState([ 'a.b.c' ]); }, [ data.year ]); return ( <div>Year: <input type="number" onChange={ onChange } /></div> ); }; const withConnector = connect( ObservablContext, { year: 'a.b.x.y.z[0]' } ); const Client1 = withConnector( YearText ); const Client2 = withConnector( YearInput ); const Ui = () => ( <div> <Client1 /> <Client2 /> </div> ); export default Ui;

Consuming the context store (React Hook method)

The following shows how to consume the Eagle Eye context store through the hook method.
This method uses the useContext(...) function to expose the store to the consumer component.

Note: In addition to setting up a map of property paths to state slices to observe (see Selector Map), the consumer compoent may have to be wrapped in a React.memo(...) HOC to shield it from cascading rerenders from parent/anscestor components.

ui.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import React, { memo, useCallback, useEffect } from 'react'; import { useContext } from '@webkrafters/react-observable-context'; import ObservableContext from './context'; const selectorMap = { year: 'a.b.x.y.z[0]' }; const Client1 = memo(() => { // memoize to prevent 'no-change' renders from the parent. const { data } = useContext( ObservableContext, selectorMap ); return ( <div>Year: { data.year }</div> ); }); const Client2 = memo(() => { // memoize to prevent 'no-change' renders from the parent. const { data, setState, resetState } = useContext( ObservableContext, selectorMap ); const onChange = useCallback( e => setState({ a: { b: { x: { y: { z: { 0: e.target.value } } } } } }), [ setState ]); useEffect(() => { data.year > 2049 && resetState([ 'a.b.c' ]); }, [ data.year ]); return ( <div>Year: <input type="number" onChange={ onChange } /></div> ); }); const Ui = () => ( <div> <Client1 /> <Client2 /> </div> ); export default Ui;
Wiring up your Eagle Eye context to the rest of the application is identical to the React.Context API. The following is a contrived snippet to demonstrate.
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import React, { useEffect, useState } from 'react'; import ProviderDemo from './provider-demo'; const MILLIS_PER_MINUTE = 6e4; let numCreated = 0; const App = () => { const [ age, updateAge ] = useState( 0 ); const [ testNumber ] = useState( () => ++numCreated ); useEffect(() => { const t = setTimeout( () => updateAge( age => age + 1 ), MILLIS_PER_MINUTE ); return () => clearTimeout( t ); }, [ age ]); return ( <div> <h2>App instance #: { testNumber }</H2> <ProviderDemo ageInMinutes={ age } /> </div> ); } export default App;