Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactJS access to Virtual DOM reconciliation result

I am updating a part of a page via a standard this.setState mechanism. I want to grab a hold of a elements that has been changed on a web page and provide a visual feedback to a user.

Let's say we have a component RichText that gets a data props. To render rich text it will delegate render to smaller components like Paragraph, Header, BulletPoints, Text, etc. The final result is a properly rendered rich text.

Later data props change (e.g. socket push). As a result of that Paragraphs can be added, or text changed, or things could move around. I want to provide a visual feedback to a user by simply highlighting HTML nodes that were changed.

In a nutshell I want to achieve what Chrome inspector is showing when you are looking at HTML tree. It blinks DOM changes.

ReactJS knows what was changed. Ideally I would like to get an access to that knowledge.

While smaller Components like Paragraph could be responsible for highlighting a difference within themselves, I don't think they have enough of a knowledge of the outside world to make it work as expected.

Format (simplified version)

{
  content: [{
    type: 'Document',
    content: [{
      type: 'Paragraph',
      content: [{
        type: 'Text', 
        text: 'text text'
      }, {
        type: 'Reference', 
        content: 'text text'
      },
    ]}, {
        type: 'BulletPoints', 
        content: [{
          type: 'ListEntry', content: [{
            type: 'Paragraph', content: [{
              type: 'Text', 
              text: 'text text'
            }, {
              type: 'Reference', 
              content: 'text text'
            }]
          }]
        }]
      }]

My current solution

I have a top level Component that knows how to render the entire Document by delegating job to other components. I have a live version HOC of it: LiveDocument that is responsible for a change visualization.

So I capture DOM before setState and after setState. Then I am using HtmlTreeWalker to spot a first difference (ignoring certain elements as I walk the tree).

like image 634
Mykola Golubyev Avatar asked Jan 28 '17 19:01

Mykola Golubyev


1 Answers

React already have an addon for these situations. ReactCSSTransitionGroup

ReactCSSTransitionGroup is a high-level API based on ReactTransitionGroup and is an easy way to perform CSS transitions and animations when a React component enters or leaves the DOM. It's inspired by the excellent ng-animate library.

You can easily animate items that are entering or leaving a specific parent.

var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;

const nextId = (() => {
  let lastId = 0;
  return () => ++lastId;
})();

class TodoList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {items: [
      {id: nextId(), text: 'hello'}, 
      {id: nextId(), text: 'world'}, 
      {id: nextId(), text: 'click'}, 
      {id: nextId(), text: 'me'}
    ]};
    this.handleAdd = this.handleAdd.bind(this);
  }

  handleAdd() {
    const newItems = this.state.items.concat([
      {id: nextId(), text: prompt('Enter some text')}
    ]);
    this.setState({items: newItems});
  }

  handleRemove(toRemove) {
    let newItems = this.state.items.filter(item => item.id !== toRemove.id);
    this.setState({items: newItems});
  }

  render() {
    const items = this.state.items.map((item) => (
      <div key={item.id} onClick={() => this.handleRemove(item)}>
        {item.text}
      </div>
    ));

    return (
      <div>
        <button className="add-todo" onClick={this.handleAdd}>Add Item</button>        
        <ReactCSSTransitionGroup
          transitionName="example"
          transitionEnterTimeout={500}
          transitionLeaveTimeout={300}>
          {items}
        </ReactCSSTransitionGroup>
      </div>
    );
  }
}

ReactDOM.render(<TodoList/>, document.getElementById("app"));
.example-enter {  
  background-color: #FFDCFF;
  color: white;
}

.example-enter.example-enter-active {
  background-color: #9E1E9E;  
  transition: background-color 0.5s ease;
}

.example-leave {
  background-color: #FFDCFF;
  color: white;
}

.example-leave.example-leave-active {
  background-color: #9E1E9E;  
  transition: background-color 0.3s ease;
}

.add-todo {
  margin-bottom: 10px;
}
<script src="https://unpkg.com/react@15/dist/react-with-addons.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.js"></script>

<div id="app"></div>
like image 100
Tiago Engel Avatar answered Sep 18 '22 18:09

Tiago Engel