Simple Ways to Listen to Events Across React.js Components

without hooks, flux, thunk, redux and react-redux

There comes a point in your life, when you JUST want to click a button in one component and JUST make something happen in another component of your application

class Emitter extends React.Component {
    _handleButtonClick() {
        console.log('clicked')
    }
    render() {
        return <button onClick={this._handleButtonClick}>
            Click me
        </button>;
    }
}

class Listener extends React.Component {
    state = {
        text: 'Initial text, lol',
    };

    render() {
        return <div>{ this.state.text }</div>;
    }
}

function App() {
    return (
        <div className="App">
            <Emitter />
            <Listener/>
        </div>
    );
}

export default App;

Maybe you don’t have much time and you don’t want to set up redux, with reducers, actions, actionTypes, constants and then connect() and mapStateToProps and mapDispatchToProps. And just maybe you don’t “worry about scalability” (I hate this). Maybe it’s supposed to stay that way: just a button, you click on it, and something happens in another component.

It occurred to me that I don’t know an easy way to do that in React, so I tried to research ones so you don’t have to. Also I’ll list a few difficult but “proper” ways.

Listen to events in another component by passing props

I think this method requires least knowledge of react-specific syntax.

class Emitter extends React.Component {
    render() {
        return <button onClick={this.props._handleButtonClickParent}>
            Click me 
        </button>;
    }
}

class Listener extends React.Component {
   render() {
        return <div>{ this.props.textChanged ? 
                        "You clicked a button" : "Initial text, lol" }</div>;
    }
}

class App extends React.Component  {
    state = {
        textChanged: false,
    };

    _handleButtonClickParent = () => {
        this.setState({textChanged: true})
    };

    render() {
        return <div className="App">
            <Emitter _handleButtonClickParent={this._handleButtonClickParent}/>
            <Listener textChanged={this.state.textChanged}/>
        </div>
    }
}

export default App;

So essentially now all the important things are happening in the parent component and we’re just passing props to children. When you click a button, a _handleButtonClickParent is triggered, it changes state of a parent component and passes a flag textChanged to Listener. Listener can do everything with it, but for now, it just changes the text.

Listen to events in another component using refs

So we’re creating a ref in a parent component from <Listener>component, that way we can call its methods in a parent component. I like this way a bit better since this way you at least can put some meaningful logic in you child component. Except just reacting to flags

import React from 'react';
import './App.css';

class Emitter extends React.Component {
    render() {
        return <button onClick={this.props._handleButtonClickParent}>
            Click me
        </button>;
    }
}

class Listener extends React.Component {
    state = {
        text: "Initial text, lol"
    };
    _handleButtonClicked = () => {
        this.setState({text: "You clicked a button"});
    };
    render() {
        return <div>{ this.state.text }</div>;
    }
}

class App extends React.Component  {
    state = {
        textChanged: false,
    };

    _handleButtonClickParent = () => {
        this.listener._handleButtonClicked();
    };

    render() {
        return <div className="App">
            <Emitter _handleButtonClickParent={this._handleButtonClickParent}/>
            <Listener  ref={ref => { this.listener = ref; }} />
        </div>
    }
}

export default App;

A bit weird way to listen for events: through window

I guess it’s the easiest, but it looks really wrong

import React from 'react';
import './App.css';

class Emitter extends React.Component {
    _handleButtonClick = () => {
        const event = new CustomEvent('button-clicked');
        window.dispatchEvent(event);
    };

    render() {
        return <button onClick={this._handleButtonClick}>
            Click me
        </button>;
    }
}

class Listener extends React.Component {
    state = {
        text: "Initial text, lol"
    };
    componentDidMount() {
        window.addEventListener('button-clicked', () => {
            this.setState({text: "You clicked a button"})
        })
    }

    render() {
        return <div>{ this.state.text }</div>;
    }
}

class App extends React.Component  {
    render() {
        return <div className="App">
            <Emitter/>
            <Listener/>
        </div>
    }
}

export default App;

But it works nonetheless. No props, refs, hooks etc. You just emit event in one component and listen to it in another one. But it looks so “non-react” that you better use it only in projects where you’re the only one developer, or everyone will laugh at you.

Correct but even harder ways:

P.S. How I would listen to events in vue.js

After writing all this it occurs to me that except the wrong way with window, everything else is not so simple. So how would I do it in vue.js?

import Vue from 'vue'

Vue.component('emitter', {template:
      '<button @click="$root.$emit(`button-clicked`)">Click me</button>'
});

Vue.component('listener', {
  template: "<div>{{text}}</div>",
  data: function () { return { 
    text: 'Initial text'
  }},
  mounted() {
    this.$root.$on('button-clicked', () => this.text = 'Button clicked')
  }
});

const App = Vue.component('App', {template:
      "<div id='app'><emitter/><listener/></div>"
});
new Vue({render: h => h(App)}).$mount('#app');

Yeah, it’s that easy.

Code is in the repos:

https://github.com/nomadicsoft/example-simple-vue-events-listening

https://github.com/nomadicsoft/example-simple-react-events-listening

Simple Ways to Listen to Events Across React.js Components

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top