HubertBlu
HubertBlu

Reputation: 829

How can I collapse an accordion from a child component in react

I am creating a page to update product details on an e-commerce site I am building using NextJS, and I have the image upload section nested inside an accordion on the individual item page. Once images have been uploaded, I would like to clear the upload form and close the accordion. It is closing the accordion I am having trouble with.

ImageUploadAccordion.js:

import React, {useRef} from 'react';
import {Accordion} from 'react-bootstrap'
import ImageUpload from './ImageUpload'

export default function ImageUploadAccordion({ item }) {
  
  const accordionRef = useRef(null);
  const toggleAccordion = () => {
    accordionRef.current.click();
  }
    return (
      <Accordion ref={accordionRef} defaultActiveKey="0">
      <Accordion.Item eventKey="1">
        <Accordion.Header>
          <span className="btn btn-outline-success">Upload Images</span>
        </Accordion.Header>
        <Accordion.Body>
          <ImageUpload 
            toggle={toggleAccordion}
            item={item}
          />
          
        </Accordion.Body>
      </Accordion.Item>
    </Accordion>
    )
}

ImageUpload.js:

import React, {useState} from 'react';
import { useRouter } from 'next/router'

export default function ImageUpload({ item, toggle }) {
    
    const router = useRouter()
    const [images, setImages] = useState([])
    const [imageURLS, setImageURLS] = useState([])
    const [tags, setTags] = useState([])
    const [theInputKey, setTheInputKey] = useState('')

    const uploadImageToClient = (event) => {
   
      if (event.target.files && event.target.files[0]) {
        setImages((imageList) => [...imageList, {"index": images.length, "data": event.target.files[0]}]);
        setImageURLS((urlList) => [
            ...urlList,
            URL.createObjectURL(event.target.files[0])
          ]);
      }
      let randomString = Math.random().toString(36);
      setTheInputKey(randomString)
    };
  
    const uploadTagToClient = (e) => {
      if (event.target.value) {
        const name = e.target.getAttribute("name")
        // const i = event.target.value;
        // document.getElementById("image-upload")
        setTags((tagList) => [...tagList, {"name": name, "tag": e.target.value}]);
      }
    };

    const removeImage = (name) => {
      // debug
      alert(`Trying to remove image index ${name}`)
      let newImages = []
      let newTags = []
      setImages(images.filter(image => image.data.name !== name));
      setTags(tags.filter(tag => tag.name !== name));
    }

    const uploadToServer = async (e) => {
      const body = new FormData()
      images.map((file, index) => {
        body.append(`file${index}`, file.data);
      });
      // Use the filenames as keys then we can retrieve server side once we have the images
      tags.map((tag, index) => {
        body.append(tag.name, tag.tag)
      })
      
      const response = await fetch("/api/file", {
        method: "POST",
        "Content-Type": "multipart/form-data",
        body
      })

      var message = await response.json();
      alert(message['message'])
      setImages([])
      setTags([])
      toggle()
    
   
    };
    
    const openImageUploadDialogue = () =>{
      document.getElementById("image-upload").click()
    }

    return (
  
        <div className="container">
          <input style={{display:'none'}} accept="image/*" id="image-upload" type="file" key={theInputKey || '' } className="btn btn-outline-success-inverse" onChange={uploadImageToClient} />
          <button className="btn btn-outline-success-inverse" onClick={openImageUploadDialogue} >
            Add Image 
          </button>
          <hr className = "text-pink"/>
          <div className="row">
            <div className="col d-flex flex-wrap">
              {images.map((file, index) => {
              return (
                <div className="div p-1" key={file.data.name}>
                    <p className="text-pink">{file.data.name}</p>
                    <p>Tag</p>
                    <input type="text" name={file.data.name} id={`${file.data.name}`} onChange={uploadTagToClient} />
                    <img src={imageURLS[index]} height="200" width="150" />
                    <div className="btn btn-outline-success-inverse" onClick={ () =>removeImage(file.data.name)}>Remove Image</div>
                </div>
                );
              })}
              </div>
              <button
                className="btn btn-outline-success-inverse"
                type="submit"
                onClick={uploadToServer}
              >
                Upload Images
              </button>
            </div>
        </div> 
    );
}

I tried by creating a reference to the accordion using useRef, and a function which uses this reference to activate the click event, which I passed to the ImageUpload component, according to another answer to a similar question, but it doesn't seem to work and I'm unsure as to why.

Any help always appreciated :-)

Upvotes: 2

Views: 1403

Answers (1)

Samuel Goldenbaum
Samuel Goldenbaum

Reputation: 18939

I believe you have the wrong target as the ref, update it to target the button that is automatically generated to wrap the header content.

<h2 class="accordion-header"><button type="button" aria-expanded="true" class="accordion-button"><span class="btn btn-outline-success">Upload Images</span></button></h2>

Rudimentary example:

export default function ImageUploadAccordion({ item }) {
   const accordionRef = useRef(null);
  
   const toggleAccordion = () => {
     accordionRef.current.querySelector('button').click();
   }

   return (
      <Accordion  defaultActiveKey="0">
      <Accordion.Item eventKey="1">
        <Accordion.Header ref={accordionRef}>
          <span className="btn btn-outline-success">Upload Images</span>
        </Accordion.Header>
        <Accordion.Body>
          <ImageUpload 
            toggle={toggleAccordion}
            item={item}
          />
          
        </Accordion.Body>
      </Accordion.Item>
    </Accordion>
    )
}

Upvotes: 1

Related Questions