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:
- Setting up a global events system https://gist.github.com/yitsushi/e3b7823f7d4bf34faa4f
- Using Flux - but even a proverbial todo list app tutorial winds up more than 10 screens long :-/ Ain't nobody got time for that!
- Some packages like https://www.npmjs.com/package/react-global-events
- Using react context https://reactjs.org/docs/context.html
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