简体   繁体   中英

Moving external library from Class-based to Functional Component in React

I'm using the JExcel javascript library with React. The documentation outlines the approach with Components, but makes use of ReactDOM.findDOMNode() which I believe has been deprecated.

I've tried moving it to a functional component, but whilst it does ostensibly work, there is an issue, in that the React component using the class re-renders about 5 times... and each re-render causes the JExcel element to add another sheet!

Here's the original example code:

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

import "./styles.css";
import "../node_modules/jexcel-pro/dist/jexcel.css";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.options = props.options;
  }

  componentDidMount = function() {
    this.el = jexcel(ReactDOM.findDOMNode(this).children[0], this.options);
  };

  addRow = function() {
    this.el.insertRow();
  };

  render() {
    return (
      <div>
        <div />
        <br />
        <input
          type="button"
          value="Add new row"
          onClick={() => this.addRow()}
        />
      </div>
    );
  }
}

const options = {
    data: [
      [123, 412],
      [null, 32, 43],
      [null, null, 41, null, 54],
      [null, 123, null, 64, 23],
      [null, null, 41, 23, 123]
    ],
    minDimensions: [5, 5],
    freezeColumns: 2,
    allowComments: true,
    tableOverflow: true,
    tabs: false,
    allowCreateTabs: false,
    tableWidth: "100%"
  };

const rootElement = document.getElementById("root");
ReactDOM.render(<App options={options} />, rootElement);

Here's my updated version, as a <MyGrid /> component:

import React, { useEffect, useRef, useState } from "react";
import ReactDOM from "react-dom";
import jexcel from "jexcel-pro";

import "./styles.css";
import "../node_modules/jexcel-pro/dist/jexcel.css";

function App(props) {
  var options = {
    data: [
      [123, 412],
      [null, 32, 43],
      [null, null, 41, null, 54],
      [null, 123, null, 64, 23],
      [null, null, 41, 23, 123]
    ],
    minDimensions: [5, 5],
    freezeColumns: 2,
    allowComments: true,
    tableOverflow: true,
    tabs: false,
    allowCreateTabs: false,
    tableWidth: "100%"
  };
  return <MyGrid options={options} />;
}

function MyGrid(props) {
  const { options } = props;
  const sheetRef = useRef(null);
  const [mySheet, setMySheet] = useState(null);

  useEffect(() => {
    setMySheet(jexcel(sheetRef.current, options));
  }, [options]);

  function addRow() {
    mySheet.insertRow();
  }

  return (
    <div>
      <div ref={sheetRef} />
      <br />
      <input type="button" value="Add new row" onClick={() => addRow()} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

The problem is that the data is coming via a fetch in a parent component. I suspect it's down to that, or possible my poor use of useRef . Is there a better approach?

Okay, with help from Paul @ JExcel here's the trick to get it to work.

import React, { useRef, useState, useEffect } from 'react'
import jexcel from 'jexcel-pro'

export default function MyGrid(props) {
  const { options } = props
  const sheetRef = useRef(null)
  const [mySheet, setMySheet] = useState(null)

  useEffect(() => {
    // here's the magic...
    if (!sheetRef.current.innerHTML && options.data.length > 0) {
      setMySheet( jexcel(sheetRef.current, options))
    }
    // clean-up function
    return function removeSheet() {
      sheetRef.current.remove();
    }
  }, [options])

  function addRow() {
    mySheet.insertRow()
  }

  return (
    <div>
      <div ref={sheetRef} />
      <br />
      <input type="button" value="Add new row" onClick={() => addRow()} />
    </div>
  )
}

To prevent multiple calls to the external library (jexcel in this case) the trick is to check that (a) the params have been passed into the component, ie in this case options.data.length > 0 , and (b) that we only call the external library the first time we have those params, ie .sheetRef.current.innerHTML - if the sheetRef element has no innerHTML, then it hasn't be initiated, so it must be the first time around. Therefore, the call to setMySheet only happens once, and only when all the properties have been passed in.

Here's a Sandbox showing it in action.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM