reactjscomponentsupdatingrerender

Reactjs Child Component not updating


I'm having trouble updating the state of the child component. If i upload an image it doesn't appear in the ReactChromakeyedImage component. (which is a package i use)

import './App.css';
import ReactChromakeyedImage from 'react-chromakeyed-image';
import React, { useState, useEffect } from 'react';

function App() {
  const [file, setFile] = useState();
   function handleChange(e) {
       console.log(e.target.files);
       setFile(URL.createObjectURL(e.target.files[0]));

   }


  return (
    <div className="App">
    <h2>Add Image:</h2>
    <input type="file" onChange={handleChange} />
    <h3>Original</h3>
    <img style={{width: "400px"}}  src={file} />

    <h3>Chromakeyed</h3>

    <ReactChromakeyedImage src={file} style={{width: "400px"}} findColor="#3CC156" replaceColor="#FFFFFF" tolerance={70}/>
    </div>
  );
}

export default App;

Solution

  • I figured it out. It's a bug in the lib. Internally, it checks the img tag it uses inside to see if the complete property has been set on the image HTML element. This goes to true when the browser has painted the image. However, it only does this on every rerender, and it it also does not wait until it becomes true (it's asynchronous). That's why it's flaky.

    Really the lib needs to be forked to fix, but there is a pretty horrible workaround you could do (this is really not nice at all, but it might be your only option unless you fork).

    In this solution we attach a load listener using the DOM API (bad!) and then force a rerender, which triggers the library to check the completed property again.

    Try this https://codesandbox.io/s/bold-voice-bhxw36?file=/src/App.js:0-1085.

    import ReactChromakeyedImage from "react-chromakeyed-image";
    import React, { useState, useRef, useLayoutEffect } from "react";
    
    function App() {
      const [file, setFile] = useState();
      const containerRef = useRef();
      const [, forceReload] = useState(0);
      function handleChange(e) {
        setFile(URL.createObjectURL(e.target.files[0]));
      }
    
      useLayoutEffect(() => {
        if (!containerRef.current) return;
        containerRef.current.querySelector("img").addEventListener("load", () => {
          forceReload((x) => x + 1);
        });
      }, [file]);
    
      return (
        <div className="App">
          <h2>Add Image:</h2>
          <input type="file" onChange={handleChange} />
          <h3>Original</h3>
           <img style={{width: "400px"}}  src={file} />
    
          <h3>Chromakeyed</h3>
    
          {file && (
            <div ref={containerRef}>
              <ReactChromakeyedImage
                src={file}
                findColor="#3CC156"
                replaceColor="#FFFFFF"
                tolerance={70}
                width="400"
                height="400"
              />
            </div>
          )}
        </div>
      );
    }
    
    export default App;