GitHub package.json versionTypeScriptNPM
GitHub package.json versionTypeScriptNPM

Getting Started

As of v7.0.0
React Observable Context is now an independent state manager, which once created, can be deployed at any location in all parts of the application without the need for a Provider component.
npm install --save @webkrafters/react-observable-context

Creating the context store

To obtain a fresh context store, just call the createEagleEye(...) function.
context.js
1 2 3 4 5 6 import { createEagleEye } from '@webkrafters/react-observable-context'; const MyContext = createEagleEye({ a: { b: { c: null, x: { y: { z: [ 2022 ] } } } } }); export const useMyStream = MyContext.useStream; export default MyContext;

Providing the context store

Simply accessing the store property of the React Observable Context from anywhere on an application makes available the internal store. The Provider component is no longer required for this.
Further readings on the React Observable Context Provider could be found here.
container.js
1 2 3 4 5 6 7 8 9 10 11 import React, { useEffect, useState } from 'react'; import MyContext from './context'; import Ui from './ui'; const Container = ({ ageInMinutes: c = 0 }) => { useEffect(() => MyContext.store.setState({ c: ageInMinutes }), [ ageInMinutes ]); return ( <Ui /> ); }; Container.displayName = 'Container'; export default Container;

Joining the context change stream

Context change stream is a reactive store whose data are automatically changing to reflect most recent changes affecting them.
There are two ways of joining the React Observable Context change stream: The HOC method and the React Hook method.
Let's tackle the HOC method first. This method uses the context's connect(...) property holding an HOC function to wire up the context change stream 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). The 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 import React, { useCallback, useEffect } from 'react'; import MyContext 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 = MyContext.connect({ year: 'a.b.x.y.z[0]' }); const Client1 = withConnector( YearText ); const Client2 = withConnector( YearInput ); const Ui = () => ( <div> <Client1 /> <Client2 /> </div> ); export default Ui;

Joining the context change stream (React Hook method)

The following shows how to join the React Observable Context stream using the hook method.
This method uses the context's useStream(...) property holding the hook function to expose the context change stream to your 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 import React, { memo, useCallback, useEffect } from 'react'; import { useMyStream } 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 } = useMyStream( selectorMap ); return ( <div>Year: { data.year }</div> ); }); const Client2 = memo(() => { // memoize to prevent 'no-change' renders from the parent. const { data, setState, resetState } = useMyStream( 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;
The React Observable Context runs decoupled from its embodying application, simply providing an active place for the application to accumulate, access, update and delete its various states as needed in ways that maintains immutabiliity and integrity of state data. 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 Container from './container'; 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> <Container ageInMinutes={ age } /> </div> ); } export default App;