0

I am trying to add a key-value pair to an array of files. I know that this question is already covered in detail in other articles, e.g. here. However, the approaches used do not work in my context. If I missed something or there is another obvious flaw, please apologize!

I have one or more pictures in an array and would like to add the information to each of these files, whether the picture is a title image or not. I.e. I want to achieve something like this:

  [0:  {File: {name: "Test", 
             lastModified: "125344", 
             etc: "something"}, 
       isTitlePicture: null}
   1:  {File: {name: "Test",
              lastModified: "125344",
              etc: "something"}, 
       isTitlePicture: null}]

With my current code I get the array as as displayed below. This means that the information about the title image is inside the file information. This is what I try to avoid.

  [0: File: {name: "Test", 
             lastModified: "125344", 
             etc: "someting",
             isTitlePicture: {isTitlePicture: null}}
   1:  File: {name: "Test", 
             lastModified: "125344", 
             etc: "someting",
             isTitlePicture: {isTitlePicture: null}]
import React, { Component } from "react";
import { MDBRow, MDBCol, MDBCardBody, MDBCard, MDBCardImage } from "mdbreact";


export class ImageUploadView extends Component {
  constructor(props) {
    super(props);
    this.state = {
      files: [],
      images: []
    };
  }


  onChangeImage(e) {
    const files = Array.from(e.target.files);

    var obj = { isTitlePicture: null };
      
    files.forEach(item => item.isTitlePicture = {...obj}); 

    const images = files.map((file) => URL.createObjectURL(file));
    this.setState({
      files: [...this.state.files, ...files],
      images: [...this.state.images, ...images]
    });
  }

  render() {
    
    return (
      <React.Fragment>
        <div className="card card-body mt-4 mb-4">
          <h1>Add Image</h1>
          <div>
            <input
              type="file"
              onChange={(e) => this.onChangeImage(e)}
              multiple
              //accept=".jpg,.png"
            />
          </div>
          <div className="Images">
            {this.state.images.map((image, i) => (
              <MDBRow
                key={image}
                style={{ margin: "10px", display: "inline-block" }}
              >
                <MDBCol>
                  <MDBCard style={{ width: "5rem" }}>
                    <MDBCardImage
                      className="img-fluid"
                      src={image}
                      waves
                    />
                    <MDBCardBody>       
                    </MDBCardBody>
                  </MDBCard>
                </MDBCol>
              </MDBRow>
            ))}
          </div>
        </div>
      </React.Fragment>
    );
  }
}
  }

Basically I want to have isTitlePicture on the same "level" as File and not within File.

dan_boy
  • 1,735
  • 4
  • 19
  • 50

2 Answers2

1

You can simplify your code a little bit like this:

  onChangeImage(e) {
    // Use spread operator like you do everywhere else.
    const files = [...e.target.files].map(file => ({
      isTitlePicture: null,
      file // same as file: file,
    }));
  
    const images = files.map(
      ({file}) => URL.createObjectURL(file)
    );
    
    // When dealing with previous state, the best practice is to pass a
    // function to setState that'll give you access to your mmm... prev state
    // Read below...
    this.setState(prevState => ({
      files: [...prevState.files, ...files],
      images: [...prevState.images, ...images],
    }));
  }

The code above will make your state look like:

  "files": [
    {
      "isTitlePicture": null,
      "file": {File Object}
    },
    {
      "isTitlePicture": null,
      "file": {File Object}
    }
  ],

  "images": [
    "blob:https://...",
    "blob:https://..."
  ]
}

setState is async, that's why it is better to pass a function when dealing with old state: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

Also, make sure you call URL.revokeObjectURL(file) once you're done with the files. The browser does not release the reference to your files until you manually do it and can cause a memory leak in a single page app.

I don't have enough context, but looks like you could include the File URL as part of your files array:

    const files = [...e.target.files].map(file => ({
      isTitlePicture: null,
      file,
      url: URL.createObjectURL(file),
    }));

Good luck!

Victor

victmo
  • 1,206
  • 11
  • 17
0

I think your object structure is wrong.

onChangeImage(e) {
    const files = Array.from(e.target.files);

    var result = [];
    files.forEach(item => result.push({file:item,isTitlePicture: null}));

    const images = result.map((file) => URL.createObjectURL(file));
    this.setState({
      files: [...this.state.files, ...result],
      images: [...this.state.images, ...images]
    });
  }
Great Coder
  • 397
  • 3
  • 14
  • Whenever you create code that does a `forEach` that just `push`es data into another array, think about using `map` to do it in one statement.... Also note that `images` is now an array of `ObjectURL`s created from objects that are not `File`s, and therefore will be unlikely to display correctly in an `img src` attribute. – Heretic Monkey Nov 10 '20 at 17:39