External Access
Normally, the context must be rendered within the component tree in order to make its store accessible - any attempt to the contrary leads to a
UsageError
.However, once rendered, its store becomes accessible both within its context component tree and externally.
How do I access the store externally?
This is done by obtaining a reference to the Provider component. Once obtained, its
current
property (i.e. which holds the referenced store object) can be passed around to other parts of the application.When the reference is no longer needed, be sure to unsubscribe all observers attached through this store reference during this phase.
For external access to the context, 4 store methods have been exposed. Namely:
store.getState()
: Provides a static snapshot of the current state.store.resetState()
: Please see descriptions in the store page.store.setState()
: Please see descriptions in the store page.store.subscribe(...)
: Provides an API for manual subscription to the state. E.g.store.subscribe( // returning an unsubsciber function ( changes: Changes<State> ) => void ) => VoidFunction
Provides an API for manual subscription to the state.
Let's see some code!
Making our Provider accessible to the parent component.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { forwardRef } from 'react';
import ObservableContext from './context'; // using example from the "Getting Started Page"
import Ui from './ui'; // using example from the "Getting Started Page"
const initState = {a: { b: { x: { y: { z: [ 2022 ] } } } }};
const ProviderDemo = forwardRef(( props, ref ) => {
// signify to parent the exit of the storeRef Provider on component unmount.
useEffect(() => () => setTimeout( () => props?.onDismount(), 0 ), []);
return (
<ObservableContext.Provider ref={ ref } value={ initState }>
<Ui />
</ObservableContext.Provider>
);
};
ProviderDemo.displayName = 'ProviderDemo';
export default ProviderDemo;
Grabbing a ref to the Provider component and sharing it with a class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { useCallback, useEffect, useRef, useState } from 'react';
import ProviderDemo from './provider-demo';
import StoreMonitor from './debug-monitor';
const App = () => {
const storeRef = useRef();
const [ refChangeCount, setRefChangeCount ] = useState( 0 );
const updateMonitor = useCallback(() => setRefChangeCount( c => c + 1 ), []);
const [ monitor ] = useState(() => new StoreMonitor( d => console.log( d ) ));
useEffect(() => {
monitor.source = storeRef.current;
return () => monitor.cleanup();
}, [ refChangeCount ]);
return (
<ProviderDemo
onDismount={ updateMonitor }
ref={ storeRef }
/>
);
}
export default App;
Using a simple class instance to montor and report changes in the store in realtime.
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
class Monitor {
#onEvent;
#store;
#unsub;
constructor( onEvent, store ) {
this.onEvent = onEvent;
this.source = store;
}
set onEvent( handler ) { this.#onEvent = handler }
get source() { return this.#store }
set source( store ) {
if( store === this.#store ) { return }
this.cleanup();
if( !store ) { return }
this.#store = store;
this.#onEvent( this.#store.getState() );
this.#unsub = store.subscribe(
() => this.#onEvent( this.#store.getState() )
);
}
cleanup() {
this.#unsub?.();
this.#store = null;
}
}
export default Monitor;
Store references are simply ReactJS references to the Eagle Eye Provider component. Therefore, after
unmounting
the Provider
component, the storeRef.current
becomes empty. So therefore:if the need exists to maintain the store beyond the life of the Provider, then keep a reference to the state snapshot returned by the last valid
storeRef.current.getState()
call.be sure to unsubscribe all external subscribers attached to the
Provider
's store reference, at or prior to the unmount
phase of the Provider
component.State references are always snapshots of the state at the time of access. In essence, the state returned by
storeRef.current.getState()
are not affected by subsequent updates to the store's state. Any updates to this acquired state never affects the context's state. So therefore, the 4 considerations:use only the
storeRef.current.setState(...)
to update the context internal store.storeRef.current.getState()
must be used to obtain the current state value.use your
storeRef.current.subscribe(...)
to manually subscribe to state changes and refresh your current state value in realtime.use the
unsubscriber
returned by your storeRef's subscribe(...)
to unsubscribe from the store when needed.