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.
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.
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;
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.
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
Entrepreneur and full-stack web developer capable of multitasking and completing large-scale projects in a short period of time. Founder of moy-razmer.ru and nomadicsoft.io, large experience in e-commerce and various SaaS projects
Custom eCommerce development starts with your needs. No matter what size your business is, crafting killer websites or robust applications with Nomadic Soft is a winning strategy. Our development team will always use the latest tech tools to build your business interactive and engaging web interfaces and applications.