Nsevens Nsevens - 2 months ago 13
React JSX Question

Call function every time any component updates (including route change)

Is it possible to call a function every time anything in React updates?

A little more context:
I want to use the full capabilities of EQCSS in React. I've tried 2 NPM packages, but they didn't seem to do what I expected exactly.

What I would like to do, is every time any component updates, I want to call

EQCSS.apply()
.

I've tried calling in
componentDidUpdate
but this doesn't seem to work when changing pages (using react-router) for example.

import React from "react";
import ReactDOM from "react-dom";

import { Router, Route, browserHistory } from "react-router";

import { MainLayout } from "./layouts";
import {
HomePage,
RankingsPage
} from "./routes";

class App extends React.Component {
render() {
return (
<Router history={browserHistory}>
<Route component={MainLayout}>
<Route path="/" component={HomePage} />
<Route path="/rankings" component={RankingsPage} />
</Route>
</Router>
);
}

componentDidUpdate() {
console.log("COMPONENT UPDATED");
// This does not get called when changing pages.
}
}

// Render the main component into the dom
ReactDOM.render(<App />, document.getElementById("app"));

Answer

Here are three different ways you could achieve this:


  1. React Context


  1. MutationObserver

Wrap your main parent component in the MutationObserver. More info here:

MutationObserver will track any changes to the actual DOM tree.


  1. Callback functions passed vie props

I've demonstrated another approach here: https://codepen.io/PiotrBerebecki/pen/wzqQkx. Please check the console which logs messages every time something has happened on the page, including Route changes, or re-renders of deeply nested children components. This relies however on a callback passed down through the child hierarchy via props.

Ad 1. Context demo code:

class App extends React.Component {
  static childContextTypes = {
    data: React.PropTypes.bool
  };

  getChildContext() {
    return {data: true};
  }
  render() {
    return (
      <div>
        <h1>Parent</h1>
        <Child />
      </div>
    )
  }
}


class Child extends React.Component {       
  static contextTypes = {
    data: React.PropTypes.bool
  };

  render() {
    console.log('in Child', this.context.data)
    return (
      <div>
        <h3>Child</h3>
        <Grandchild />
      </div>
    );
  }
}


class Grandchild extends React.Component {       
  static contextTypes = {
    data: React.PropTypes.bool
  };

  render() {
    console.log('in Grandchild', this.context.data)
    return (
      <div>
        <h6>Grandchild</h6>
      </div>
    );
  }
}


ReactDOM.render(
  <App />,
  document.getElementById('app')
);

Ad 3. Callback demo code:

var MainLayout = React.createClass({
  doSomething: function() {
    console.log('something happened');
  },

  render: function() {
    this.doSomething();
    const childrenWithProps = React.Children.map(this.props.children,
     (child) => React.cloneElement(child, {
       doSomething: this.doSomething
     })
    );

    return (
      <div className="app">
        <header className="primary-header"></header>
        <aside className="primary-aside">
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/users">Users</Link></li>
            <li><Link to="/widgets">Widgets</Link></li>
          </ul>
        </aside>
        <main>
          {childrenWithProps}
        </main>
      </div>
      )
  }
})

var Home = React.createClass({
  getInitialState: function() {
    return {
      counter: 1
    };
  },

  componentDidUpdate: function() {
    this.props.doSomething()
  },

  changeCounter: function() {
    this.setState({
      counter: this.state.counter + 1
    });
  },

  render: function() {
    return (
      <div>
        <button onClick={this.changeCounter}>Click me</button>
        <br />
        {this.state.counter}
      </div>
    );
  }
})

var SearchLayout = React.createClass({
  render: function() {
    const childrenWithProps = React.Children.map(this.props.children,
      (child) => React.cloneElement(child, {
       doSomething: this.props.doSomething
     })
    );

    return (
      <div className="search">
        <header className="search-header"></header>
        <div className="results">
          {childrenWithProps}
        </div>
        <div className="search-footer pagination"></div>
      </div>
      )
  }
})

var UserList = React.createClass({
  getInitialState: function() {
    return {
      counter: 1
    };
  },

  componentDidUpdate: function() {
    this.props.doSomething()
  },

  changeCounter: function() {
    this.setState({
      counter: this.state.counter + 1
    });
  },

  render: function() {
    return (
      <div>
        User List
        <br />
        <button onClick={this.changeCounter}>Click me</button>
        <br />
        {this.state.counter}
      </div>
    );
  }
});

var WidgetList = React.createClass({
  getInitialState: function() {
    return {
      counter: 1
    };
  },

  componentDidUpdate: function() {
    this.props.doSomething()
  },

  changeCounter: function() {
    this.setState({
      counter: this.state.counter + 1
    });
  },

  render: function() {
    return (
      <div>
        Widgets
        <br />
        <button onClick={this.changeCounter}>Click me</button>
        <br />
        {this.state.counter}
      </div>
    );
  }
})



ReactDOM.render((
  <Router>
    <Route path="/" component={MainLayout}>
      <IndexRoute component={Home} />
      <Route component={SearchLayout}>
        <Route path="users" component={UserList} />
        <Route path="widgets" component={WidgetList} />
      </Route> 
    </Route>
  </Router>
), document.getElementById('root'))