1. Create notifier class using observer pattern
class ChangeNotifier {
subscribers = [];
subscribe(callback) {
this.subscribers.push(callback);
}
unsubscribe(callback) {
const index = this.subscribers.indexOf(callback);
if (index > -1) {
this.subscribers.splice(index, 1);
}
}
notifyAll(data) {
this.subscribers.forEach(callback => callback(data));
}
}
2. ComponentA receives notifier as a prop and used to notify all subscribers
const ComponentA = ({ notifier }) => {
const triggerNotifier = () => {
notifier.notifyAll('Some data that will subscribers receive');
}
return <div>{/** Some content */}</div>
}
3. ComponentB receives notifier and subscribes to it to receive data sent by from ComponentB
const ComponentB = ({ notifier }) => {
useEffect(() => {
const callbackFn = data => {/** Do whatever you want with received data */ }
notifier.subscribe(callbackFn);
return () => notifier.unsubscribe(callbackFn);
}, [])
}
4. App holds both component. Create instance of notifier there and pass as a props
const App = () => {
const dataNotifier = new ChangeNotifier();
return <div>
<ComponentA notifier={dataNotifier} />
<ComponentB notifier={dataNotifier} />
</div>
}
If you have components on different levels deeply nested and it is hard to pass notifier as a prop, please read about React Context which is very helpful when you want to avoid property drilling
React Context
Here's implementation with context
class ChangeNotifier {
subscribers = [];
subscribe(callback) {
this.subscribers.push(callback);
return this.unsubscribe.bind(this, callback);
}
unsubscribe(callback) {
const index = this.subscribers.indexOf(callback);
if (index > -1) {
this.subscribers.splice(index, 1);
}
}
notifyAll(data) {
this.subscribers.forEach(callback => callback(data));
}
}
const NotifierContext = React.createContext();
const ComponentA = () => {
const { notifier } = useContext(NotifierContext);
const triggerNotifier = () => {
notifier.notifyAll('Some data that will subscribers receive');
}
return <div><button onClick={triggerNotifier}>Notify</button></div>
}
const ComponentB = () => {
const { notifier } = useContext(NotifierContext);
useEffect(() => {
const callbackFn = data => { console.log(data) }
notifier.subscribe(callbackFn);
return () => notifier.unsubscribe(callbackFn);
}, [notifier])
}
Now all components wrapped in NotifierContext.Provider (no matter how deep they are nested inside other components) will be able to use useContext hook to receive context value passed as value prop to NotifierContext.Provider
const App = () => {
const dataNotifier = useMemo(() => new ChangeNotifier(), []);
return <NotifierContext.Provider value={{ notifier: dataNotifier }}>
<ComponentA />
<ComponentB />
</NotifierContext.Provider>
}
export default App;
Last but not least, I guess you can avoid context or properties drilling and just create instance of ChangeNotifier in some utility file and export it to use globally...