1

A have two files, with two functional components A and B, in the first component A, i have a specialFunction that gets called with onClick, what i want to do is raise an event in specialFunction when it's called, and then in component B add a Listener for the event in specialFunction.

Component A:

function specialFunction(){
     //raise the event and send some data
}

Component B:

//contains a listener that does some work when specialFunction is called, example:
(data) => {console.log("am called:",data)};
Marwane
  • 333
  • 3
  • 13
  • 1
    Does this answer your question? [react: dispatch and listen to custom events](https://stackoverflow.com/questions/60927150/react-dispatch-and-listen-to-custom-events) – pilchard Jun 22 '22 at 21:33
  • The duplicate is from 2020 so possibly things have changed, but for simplicities sake you should look into [React Context](https://reactjs.org/docs/context.html) (or related libraries...) – pilchard Jun 22 '22 at 21:35
  • Are you familiar with Context? If yes, you can use Observer patterns to create notifier and pass it through context. – Andyally Jun 22 '22 at 21:45
  • see: [Absolute simplest example of an Functional Observable pattern in React](https://stackoverflow.com/questions/63584365/absolute-simplest-example-of-an-functional-observable-pattern-in-react) – pilchard Jun 22 '22 at 21:51
  • Use a shared service provided at root (singleton) and inject it in both components.... oh wait, you're using React :D – WSD Jun 22 '22 at 22:28
  • 1
    @pilchard The dispatch and listen did it for me :) – Marwane Jun 25 '22 at 16:45
  • @guzmanoj Maybe it's time to switch :D, are you talking about Angular ? – Marwane Jun 25 '22 at 16:46
  • @Marwane yessir! – WSD Jun 26 '22 at 07:05

2 Answers2

1

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...

Andyally
  • 863
  • 1
  • 9
  • 22
0

Andrius posted a really good answer, but my problem was that the two components, one of them is used as an API, and the other had a parent component, am a beginner so maybe there is a way to use them but i just didn't know how.

The solution that i used, (maybe not the best) but did the job was to dispatch a custom event in a Promise from the specialFunction:

function specialFunction(){
    new Promise((resolve) => {
      console.log("am the promise");
      document.dispatchEvent(event);
      resolve();
    });

And add a Listener in the other component using a useEffect hook:

useEffect(() => {
    let handlePreview = null;
    new Promise((resolve) => {
      document.addEventListener(
        "previewImg",
        (handlePreview = (event) => {
          event.stopImmediatePropagation();
          //Stuff...
        })
      );
      return () =>
        window.removeEventListener("previewImg", handlePreview, false);
    });
  }, []);

Thank you for your help.

Marwane
  • 333
  • 3
  • 13