javascriptreactjsecmascript-6react-hooksreact-custom-hooks

Custom hook taking parameters from another function


I am having a progress bar which on click starts its progress from 60 to all the way upto 10 for each of the file of files array. I am a using the ref to dynamically increment the progress by 10 and when its 100, I clear it and bring the message as Upload Complete. Code works just fine.

I have moved the mechanism of initiating progress onto a custom hook, that basically takes setter, the interval and a file name for which it has to update upload progress.

I am initializing the hook inside the parent, but each of the file name I will only get when I click each of the file names button. how can I pass the name of the file?

Sandbox: https://codesandbox.io/s/progress-bar-r0zcjn?file=/src/App.js

Component:

import React, { useState, useRef } from "react";
import "./styles.css";
import ProgressBar from "./ProgressBar";
import useProgress from "./useProgress";

const appStyles = {
  height: 20,
  width: 500,
  margin: 50
};

export default function App() {
  const [files, setFiles] = useState([
    { name: "file1", status: 0 },
    { name: "file2", status: 0 },
    { name: "file3", status: 0 }
  ]);

  const initiateProgress = useProgress(setFiles, 60);

  const initiate = (name) => {
    console.log(name);
  };

  return (
    <div className="App" style={appStyles}>
      {files.map((file) => {
        return (
          <div key={file.name}>
            <button type="button" onClick={() => initiate(file.name)}>
              {file.name}
            </button>
            <ProgressBar bgColor={"#DF8100"} progress={file.status} />
            {file.status === 100 && <span>Upload complete</span>}
          </div>
        );
      })}
    </div>
  );
}

Hook

import { useRef } from "react";

const useProgress = (updater, timer, name) => {
  const mockRef = useRef(timer);

  const handleProgress = () => {
    const intervalID = setInterval(() => {
      if (mockRef.current >= 100) {
        clearInterval(intervalID);
        updater((prevState) =>
          prevState.map((item, itemIndex) =>
            item.name === name ? { ...item, status: 100 } : item
          )
        );
      } else {
        mockRef.current = mockRef.current + 10;
        updater((prevState) =>
          prevState.map((item, itemIndex) =>
            item.name === name ? { ...item, status: mockRef.current } : item
          )
        );
      }
    }, 200);
  };

  return handleProgress;
};

export default useProgress;

Solution

  • You need to define another component combining the useProgress hook logic and ProgressBar component. Try like this:

    const FileProgress = ({ file, updater }) => {
      const initiateProgress = useProgress(updater, 60, file.name);
    
      return (
        <div key={file.name}>
          <button type="button" onClick={initiateProgress}>
            {file.name}
          </button>
          <ProgressBar bgColor={"#DF8100"} progress={file.status} />
          {file.status === 100 && <span>Upload complete</span>}
        </div>
      );
    };
    
    export default function App() {
      const [files, setFiles] = useState([
        { name: "file1", status: 0 },
        { name: "file2", status: 0 },
        { name: "file3", status: 0 }
      ]);
    
      return (
        <div className="App" style={appStyles}>
          {files.map((file) => {
            return <FileProgress file={file} updater={setFiles} />;
          })}
        </div>
      );
    }
    

    Code sandbox demo:

    Edit progress bar (forked)