ReactJS table diff span highlight toggle

Viewed 344

I am trying to make a ReactJS application that will showcase a table - I want to have a toggle switch to show/highlight the differences -- in table rows. I've tried adding ref's to the table rows to try and use them as inputs.

current sandbox https://codesandbox.io/s/hopeful-haibt-q489l?file=/src/Home.js

Some demo with JavaScript http://jsfiddle.net/8c4nt2e1/1/

enter image description here

    import React, { Component } from "react";
    import { withRouter } from "react-router-dom";
    import { connect } from "react-redux";
    import { bindActionCreators } from "redux";
    
    import { Diff } from "diff";
    
    import ReactDOM from "react-dom";
    
    class Home extends Component {
      componentDidMount() {
        console.log("show differences");
      }
    
      onShowDiff() {
        var name = ReactDOM.findDOMNode(this.refs.row1_0); //.value;
        console.log("name", name);
    
        /*
        const diff = Diff.diffChars(
            document.getElementById('row1_0').value, 
            document.getElementById('row1_1').value,
            document.getElementById('row1_2').value,
            document.getElementById('row1_3').value
        );*/
    
        //
    
        /*
        const diff = Diff.diffChars(document.getElementById('input-1').value, document.getElementById('input-2').value);
        const output = diff.reduce((result, change) => {
          if (change.added) return result + '<span data-diff="added">' + change.value + '</span>';
          if (change.removed) return result + '<span data-diff="removed">' + change.value + '</span>'
          return result + change.value;
        }, '');
        */
        //document.getElementById('output').innerHTML = output;
        //console.log(output);
      }
    
      onHideDiff() {
        //const output = document.getElementById('output').innerHTML;
        //document.getElementById('output').innerHTML = output.replace(/<span data-diff=["'](?:added|removed)["']>(.+)<\/span>/gi, '$1');
      }
    
      render() {
        return (
          <div className="Page">
            <button id="show-diff" onClick={this.onShowDiff()}>
              Show diff
            </button>
            <button id="hide-diff" onClick={this.onHideDiff()}>
              Hide diff
            </button>
    
            <div className="table-spec">
              <table>
                <tr>
                  <th>spec1</th>
                  <th>spec2</th>
                  <th>spec3</th>
                  <th>spec4</th>
                </tr>
                <tr>
                  <td ref="row1_0">480GB Adata SU630 SATA SSD</td>
                  <td ref="row1_1">512GB Adata XPG SX8200 Pro NVMe SSD</td>
                  <td ref="row1_2">512GB Adata XPG SX8200 Pro NVMe SSD</td>
                  <td ref="row1_3">2TB NVMe SSD</td>
                </tr>
                <tr>
                  <td ref="row2_0">GeForce RTX 3080 10GB</td>
                  <td ref="row2_1">GeForce GTX 1660 SUPER 6GB</td>
                  <td ref="row2_2">Radeon RX 5500 XT 8GB</td>
                  <td ref="row2_3">GeForce RTX 3090 24GB</td>
                </tr>
                <tr>
                  <td ref="row3_0">Intel Core i5 9400F 2.9GHz</td>
                  <td ref="row3_1">AMD Athlon 3000G 3.5GHz</td>
                  <td ref="row3_2">AMD Ryzen 7 3700X 3.6GHz</td>
                  <td ref="row3_3">Intel Core i9 10900K 3.7GHz</td>
                </tr>
              </table>
            </div>
          </div>
        );
      }
    }
    
    function mapStateToProps(state) {
      return {};
    }
    
    function mapDispatchToProps(dispatch) {
      return bindActionCreators({}, dispatch);
    }
    
    export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Home));

idea comes from here https://www.gsmarena.com/compare.php3?idPhone1=9847&idPhone2=9536&idPhone3=9256#diff-*,*,*

2 Answers

The main ideas here are to use state to control how the HTML is structured (as per (what I think is) standard React) and to have a component for each row so that each row can figure out the diffs and the elements each row should have.

The 'DiffRow's could be the result of a map over some set of row data.

I've not looked into how to use Diff to achieve exactly what you want, this just takes the example use of Diff you gave and them finds out the diff for each cell after the first, to the first and adds spans with classes for the things Diff has mentioned.

For some reason importing Diff didn't seem to work, so I required it instead.

https://codesandbox.io/s/compassionate-yalow-th57n?file=/src/Home.js

import React, { Component } from "react";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";

const Diff = require("diff");

const diff_part_class_name = (part) =>
  part.added ? "added" : part.removed ? "removed" : "default";

const diff_cells = (cells) => {
  const [first_cell, ...other_cells] = cells;

  return [first_cell].concat(
    other_cells
      .map((content) => Diff.diffChars(first_cell, content))
      .map((diffed) => (
        <>
          {diffed.map((part, index) => (
            <span key={index} className={diff_part_class_name(part)}>
              {part.value}
            </span>
          ))}
        </>
      ))
  );
};

const DiffRow = ({ data, show_diff }) => {
  const cell_data = show_diff ? diff_cells(data) : data;
  return (
    <tr>
      {cell_data.map((content, index) => (
        <td key={index}>{content}</td>
      ))}
    </tr>
  );
};

class Home extends Component {
  constructor(props) {
    super(props);
    this.state = { show_diff: false };
  }

  componentDidMount() {
    console.log("show differences");
  }

  onShowDiff() {
    this.setState({ show_diff: true });
  }

  onHideDiff() {
    this.setState({ show_diff: false });
  }

  render() {
    return (
      <div className="Page">
        <button id="show-diff" onClick={this.onShowDiff.bind(this)}>
          Show diff
        </button>
        <button id="hide-diff" onClick={this.onHideDiff.bind(this)}>
          Hide diff
        </button>

        <div className="table-spec">
          <table>
            <thead>
              <tr>
                <th>spec1</th>
                <th>spec2</th>
                <th>spec3</th>
                <th>spec4</th>
              </tr>
            </thead>
            <tbody>
              <DiffRow
                show_diff={this.state.show_diff}
                data={[
                  "480GB Adata SU630 SATA SSD",
                  "512GB Adata XPG SX8200 Pro NVMe SSD",
                  "512GB Adata XPG SX8200 Pro NVMe SSD",
                  "2TB NVMe SSD"
                ]}
              />
              <DiffRow
                show_diff={this.state.show_diff}
                data={[
                  "GeForce RTX 3080 10GB",
                  "GeForce GTX 1660 SUPER 6GB",
                  "Radeon RX 5500 XT 8GB",
                  "GeForce RTX 3090 24GB"
                ]}
              />
              <DiffRow
                show_diff={this.state.show_diff}
                data={[
                  "Intel Core i5 9400F 2.9GHz",
                  "AMD Athlon 3000G 3.5GHz",
                  "AMD Ryzen 7 3700X 3.6GHz",
                  "Intel Core i9 10900K 3.7GHz"
                ]}
              />
            </tbody>
          </table>
        </div>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {};
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({}, dispatch);
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Home));

I think you shouldn't use diff js library in this scenario.

I saw compare site example you mentioned. its easier to compare items with specific pattern

and not character by character.

in gsmaena there is several specifications in a column which seperated with comma.

if you have several specification too, you can do like this:

const getWords = (item, array, show_diff) => {
  const seperatedWords = useRef(item.split(","));
  return seperatedWords.current.map((word, i) => (
    <span
      style={
        show_diff && !array[0].split(",").includes(word) ? { opacity: 0.3 } : {}
      }
    >
      {word}
      {i !== seperatedWords.current.length - 1 && ","}
    </span>
  ));
};

const Row = ({ data, show_diff }) => {
  return (
    <tr>
      {data.map((content, index) => (
        <td key={index}>{getWords(content, data, show_diff)}</td>
      ))}
    </tr>
  );
};

if not its easier too. you can write:

const Row = ({ data, show_diff }) => {
  const hasDifference = useRef(!data.every((val, i, arr) => val === arr[0]));
  return (
    <tr>
      {data.map((content, index) => (
        <td
          style={show_diff && hasDifference.current ? { opacity: .3 } : {}}
          key={index}
        >
          {content}
        </td>
      ))}
    </tr>
  );
};

you can see full code in this sandbox

Related