Home > Software engineering >  React parent component needs child function to return data
React parent component needs child function to return data

Time:10-21

I think I need a call back function, but do not understand the proper syntax given a parent component calling a child function.

Here is the stripped down parent component followed by the function FilesUpload. I need the File.Name from child returned and setState({fileName}) in parent component.

Hopefully painfully obvious to someone who knows how to do this. Thank you in advance for solution. Rob

...

//Stripped down ParentComponent.jsx
import React, { Component } from 'react'
import FilesUpload from "../Services/FilesUpload";

class ParentComponent extends Component {
    constructor(props) {
        super(props)
        this.state = {
            fileName: null
        }
        this.changefileNameHandler = this.changefileNameHandler.bind(this);
    }

    changefileNameHandler= (event) => {
        this.setState({fileName: event.target.value});
    }

    componentDidMount(){
    }
   
    render() {
        return (
            <div>
                <td>this.state.fileName </td>
                <FilesUpload onUpdate={this.changefileNameHandler}/>
            </div>
        )
    }
}
export default ParentComponent


//functional service FilesUpload.js
import React, { useState, useEffect, useRef } from "react";
import UploadService from "../Services/FileUploadService";
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';


const UploadFiles = () => {
  const [selectedFiles, setSelectedFiles] = useState(undefined);
  const [progressInfos, setProgressInfos] = useState({ val: [] });
  const [message, setMessage] = useState([]);
  const [fileInfos, setFileInfos] = useState([]);
  const progressInfosRef = useRef(null)

  useEffect(() => {
    UploadService.getFiles().then((response) => {
      setFileInfos(response.data);
    });
  }, []);

  const selectFiles = (event) => {
    setSelectedFiles(event.target.files);
    setProgressInfos({ val: [] });
  };

  const upload = (idx, file) => {
    let _progressInfos = [...progressInfosRef.current.val];
    return UploadService.upload(file, (event) => {
      _progressInfos[idx].percentage = Math.round(
        (100 * event.loaded) / event.total
      );
      setProgressInfos({ val: _progressInfos });
    })
      .then(() => {
        toast.info(file.name   " Uploaded")
        setMessage((prevMessage) => ([
          ...prevMessage,
          "Uploaded the file successfully: "   file.name,
        ]));
      })
      .catch(() => {
        _progressInfos[idx].percentage = 0;
        setProgressInfos({ val: _progressInfos });

        setMessage((prevMessage) => ([
          ...prevMessage,
          "Could not upload the file: "   file.name,
        ]));
      });
  };

  const uploadFiles = () => {
    const files = Array.from(selectedFiles);

    let _progressInfos = files.map(file => ({ percentage: 0, fileName: file.name }));

    progressInfosRef.current = {
      val: _progressInfos,
    }

    const uploadPromises = files.map((file, i) => upload(i, file));

    Promise.all(uploadPromises)
      .then(() => UploadService.getFiles())
      .then((files) => {
        setFileInfos(files.data);
      });

    setMessage([]);
  };

  return (
    <div>
      {progressInfos && progressInfos.val.length > 0 &&
        progressInfos.val.map((progressInfo, index) => (
          <div className="mb-2" key={index}>
            <span>{progressInfo.fileName}</span>
            <div className="progress">
              <div
                className="progress-bar progress-bar-info"
                role="progressbar"
                aria-valuenow={progressInfo.percentage}
                aria-valuemin="0"
                aria-valuemax="100"
                style={{ width: progressInfo.percentage   "%" }}
              >
                {progressInfo.percentage}%
              </div>
            </div>
          </div>
        ))}

      <div className="row my-3">
        <div className="col-8">
          <label className="btn btn-default p-0">
            <input type="file" multiple onChange={selectFiles} />
          </label>
        </div>

        <div className="col-4">
          <button
            className="btn btn-success btn-sm"
            disabled={!selectedFiles}
            onClick={uploadFiles}
          >
            Upload
          </button>
        </div>
      </div>
      {message.length > 0 && (
        <div className="alert alert-secondary" role="alert">
          <ul>
            {message.map((item, i) => {
              return <li key={i}>{item}</li>;
            })}
          </ul>
        </div>
      )}

      <div className="card">
        {/* <div className="card-header">List of Files</div> */}
        <ul className="list-group list-group-flush">
          {!fileInfos &&
            fileInfos.map((file, index) => (
              <li className="list-group-item" key={index}>
                {/* <a href={file.url}>{file.name}</a> */}
              </li>
            ))}
        </ul>
      </div>
      <ToastContainer position="top-center" autoClose={1000}/>
    </div>
  );
};

export default UploadFiles;

...

CodePudding user response:

I'm not quite sure I understand your question perfectly, but do you want to pass down the changefileNameHandler function as a prop to your FilesUpload functional component?

In this case you can just add props as a paremeter:

const UploadFiles = (props) => { ...

and call it wherever you need it:

props.onUpdate(event)

CodePudding user response:

@davidsz Your final change worked. When integrating the solution into my production ParentComponent, the field would get updated and then the screen would do some rendering and I would lose the value. I had to add code to write the filename to the MySQL backend before returning the user to the next UI page.
Thank you for your assistance and patience. So apperciatted. Rob

  • Related