简体   繁体   中英

Is it possible to turns this HoC pattern into a React Hook syntax?

I'm trying to create a reusable component, which alters it's behaviour - essentially rendering to either SVG or Canvas. I'm currently trying to do this by wrapping it up in a HoC (however I'm trying to use React hooks) so this is all falling a bit flat on it's face.


Edit with extra info

With the current approach (HoC returning a function) I get the following error:

Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.

If I remove the function call within the HoC's:

React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object


I've seen an example of converting HoC's to React hooks, but I'm struggling to work out how I might convert this - if it's even possible and wondering if anyone can give me a pointer? I might have tried to architect this the wrong way, as it feels like quite a complex use case.

So I've these 2 HoC's at the moment which I feel like I need to refactor somehow to use hooks called withSVG and withCanvas . They setup different DOM to be represented and importantly one has a function called renderLoop which needs to be called from the Scatter component below.

Slightly quirky, is because the Scatter is just business logic now (leveraging dependencies in the useEffect), it doesn't actually need to return any DOM, because it's just manipulating the DOM of the parent HoC. I'm expecting many more components like Scatter in the future however so don't want to move this logic into the HoCs (if they're even the right way of doing this).

 const duration = 2000; const withSVG = ({ width, height, ...props }) => WrappedComponent => { const layer = useRef(null); return (props) => ( <g width={width} height={height} ref={layer}> <WrappedComponent {...props} layer={layer} /> </g> ); }; const withCanvas = ({ width, height, ...props }) => WrappedComponent => { const layer = useRef(null); const canvas = useRef(null); const renderLoop = getRenderLoop(canvas.current, width, height); return (props) => ( <React.Fragment> <custom ref={layer}/> <canvas width={width} height={height} ref={canvas}></canvas> <WrappedComponent {...props} layer={layer} renderLoop={renderLoop} /> </React.Fragment> ); }; const Scatter = ({ data, layer, renderLoop }) => { useEffect(() => { if (!layer.current) { return; } // D3 data join const join = d3 .select(layer.current) .selectAll("circle") .data(data, d => d.key); // Shrink the circles to 0 size const exit = join.exit() .transition("radius") .duration(duration) .attr("r", 0) .remove(); const enter = join.enter() .append("circle") .attr("cx", d => dx) .attr("cy", d => dy) .attr("r", 0) .style("fill", d => d.color); const update = enter .merge(join) .transition("radius") .duration(duration) .attr("cx", d => dx) .attr("cy", d => dy) .attr("r", 30) .style("fill", d => d.color); if (renderLoop) { renderLoop(exit, update); } }, [layer, data]); return null; }; const CanvasScatter = withCanvas(Scatter); const SVGScatter = withSVG(Scatter); // index.js const width = 400; const height = 400; const App = () => { const [data, setData] = useState([ { key: 1, x: 50, y: 50, color: "red" }, { key: 2, x: 150, y: 150, color: "blue" }, ]); setTimeout(() => { setData([ { key: 1, x: 100, y: 100, color: "orange" }, { key: 3, x: 150, y: 50, color: "green" }, ]); }, 3000); return ( <div> <svg width={width} height={height}> <SVGScatter width={width} height={height} data={data} /> </svg> <CanvasScatter width={width} height={height} data={data} /> </div> ); // return <div>Hello React,Webpack 4 & Babel 7!</div>; }; ReactDOM.render(<App />, document.querySelector("#root"));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <div id="root"></div>

Issue

I think you have malformed your Higher Order Components. It appears you've defined them to consume some props, then a component to wrap.

const withSVG = ({ width, height, ...props }) => WrappedComponent => {...}

but you invoke them as expected

const SVGScatter = withSVG(Scatter);

Here the Scatter component is consumed first and the HOC tries to destructure values from it and returns a function to consume a component, which is undefined . I think this causes the error you are seeing.

Solution

Based on how you use the decorated components

<SVGScatter width={width} height={height} data={data} />
<CanvasScatter width={width} height={height} data={data} />

I think you meant to destructure width and height from the inner component, the one that is returned by the HOC.

const withSVG = WrappedComponent => ({ width, height, ...props }) => {
  const layer = useRef(null);

  return (
    <g width={width} height={height} ref={layer}>
      <WrappedComponent
        layer={layer}
        {...props} // <-- data passed here
      />
    </g>
  );
};

const withCanvas = WrappedComponent => ({ width, height, ...props }) => {
  const layer = useRef(null);
  const canvas = useRef(null);
  const renderLoop = getRenderLoop(canvas.current, width, height);

  return (
    <React.Fragment>
      <custom ref={layer}/>
      <canvas width={width} height={height} ref={canvas}></canvas>
      <WrappedComponent
        layer={layer}
        renderLoop={renderLoop}
        {...props} // <-- data passed here
      />
    </React.Fragment>
  );
};

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