问题
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?
回答1:
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, i.e. in this case options.data.length > 0
, and (b) that we only call the external library the first time we have those params, i.e. !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.
来源:https://stackoverflow.com/questions/61579383/moving-external-library-from-class-based-to-functional-component-in-react