randal
randal

Reputation: 1372

how to update array object within the componentDidUpdate method

I'm trying to fetch the new updated state object within componentDidUpdate method. but its returning the the old values in the array.

A user enters a image title, and uploads an image, and react should console.log a new array object with the new values. Without having to refresh.

For example the following array should have two keys along with values within the array considering i just added an image without refreshing.

enter image description here

and on refresh, the array is updated

enter image description here

dashboard.js 66

this is console.log(prevState.images)

[
  {
    "id": 139,
    "image_title": "owl",
    "img_url": "http://res.cloudinary.com/dq281hpqd/image/upload/v1559966278/uploads/wph0kuhzg52lxkhxmxsi.png",
    "created_at": "2019-06-08T03:57:58.351Z",
    "updated_at": "2019-06-08T03:57:58.351Z",
    "user_id": null
  },
  {
    "id": 138,
    "image_title": "uuuuuu",
    "img_url": "http://res.cloudinary.com/dq281hpqd/image/upload/v1559965905/uploads/m1oxtyae2d6pyvteidyq.png",
    "created_at": "2019-06-08T03:51:46.815Z",
    "updated_at": "2019-06-08T03:51:46.815Z",
    "user_id": null
  }
]

Ideally a new object array with the new values should append the array without having to refresh the page.

Dashboard.js

import React, { Component } from "react";
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import Paper from '@material-ui/core/Paper';
import ImageUploader from 'react-images-upload';
import Axios from '../Axios';
import Image from './Image';
class Dashboard extends Component{
    constructor(props){
        super(props);
        this.state = {
            image_url: 'http://www.conservewildlifenj.org/images/artmax_1001.jpg', 
            images: [], 
            description:'',
            upload:false,
        }
    }
    handleUpload =  file =>  {
        const data = new FormData()
        const image = file[0]
        // console.log(this.state.description)
        // data.append('ourImage', this.state.description)
        data.append('ourImage',image, this.state.description )
        Axios.post('/images/upload', data).then((response) => {
            this.setState({
                image_url:response.data.img_url,
                description:response.data.image_title
                // images: [...this.state.images, this.state.image_url ]
            })
        });
        this.props.history.push("/dashboard");
    }
    handleChange = (e) => {
        // e.preventDefault();
        this.setState({
            [e.target.name]: e.target.value
        })
        // console.log(this.state.description)
    }
    fileOnchange = (file) => {
        this.setState({
            [file[0].target.name]: file[0].target.value
        })
    }
    componentWillMount(){
        Axios.get('/images/uploads').then( (response) => {
            // let img;
            // let imgTitle;
            Object.keys(response.data).forEach( (key) => {
                // console.log(response.data);
                // img = response.data[key].img_url
                // imgTitle = response.data[key].image_title
                // pulling in the data from the backend
                console.log(response.data[key]);
                this.setState({
                    images:[ ...this.state.images, response.data[key]]
                })
                console.log(this.state.images);
            });
        })
    }
    componentDidUpdate(prevProps, prevState) {
        if (this.state.image_url !== prevState.image_url) {
            // trying to fetch the new upated array. but not sure how. 
            console.log(prevState.images);
            this.setState({
                images: [this.state.image_url, this.state.description, ...this.state.images]
            });
        }
      }
    onUploadClick = (e) => {
        e.preventDefault();
        this.setState({
            upload: !this.state.upload
        })
    }
    render(){
        const uploader = ( 
            <ImageUploader
                withIcon={true}
                withPreview={true}
                onChange={this.handleUpload}
                singleImage={true}
                buttonText='Upload an image'
                imgExtension={['.jpg', '.gif', '.png', '.gif']}
                maxFileSize={5242880}
            />
        )
        return(
            <div>
            <Grid container justify="center" spacing={16}>
                <Grid item sm={8} md={6} style={{ margin: '40px 0px', padding: '0px 30px'}}>
                    <Typography align="center" variant="h6">
                        Welcome to the Dashboard
                    </Typography>
                        <Button onClick={this.onUploadClick} variant="outlined" component="span" color="primary">
                            {/* toggle between Upload or Close
                                Will be upload by default, else if upload is clicked, close will show.
                            */}
                            {!this.state.upload ? "Upload": "Close"}
                        </Button>
                        {this.state.upload ? (
                            <div>
                             <TextField
                                 id="outlined-name"
                                 label="Image Title"
                                 name="description"
                                 type="text"
                                 fullWidth
                                 style={{ borderRadius: '0px'}}
                                 className=""
                                 value={this.state.description}
                                 onChange={this.handleChange}
                                 margin="normal"
                               />
                                <br></br>
                                <br></br>
                              {uploader}
                            </div>
                        ):(
                            null
                        )}
                    {this.state.images.length > 0 ? (
                        this.state.images.map( (img, i) => (     
                            <Grid item sm={12} md={12} key={i} style={{ margin: '30px 0px'}}>
                                 <Paper>
                                        {/* // empty image_title */}
                                     <Typography variant="h6" align="center">{img.image_title}</Typography> 
                                    <Image image_url={img.img_url} />    
                                </Paper>                              
                            </Grid>
                        ))
                    ) : (
                        <div>
                            <Grid item md={8}>
                                <Typography>No Images yet</Typography>
                            </Grid>
                        </div>
                    )}
                </Grid>
                {/* Images  */}
              </Grid>
            </div>
        )
    }
}
export default Dashboard;

componentDidUpdate

console.log(this.state.images);

The values are passed, but not the way i would like

enter image description here

The last two values are not within the nested array. as the rest as the others.

[
  {
    "id": 142,
    "image_title": "cheese",
    "img_url": "http://res.cloudinary.com/dq281hpqd/image/upload/v1559973164/uploads/bvfotlfivr4yqsqhkvrw.png",
    "created_at": "2019-06-08T05:52:45.203Z",
    "updated_at": "2019-06-08T05:52:45.203Z",
    "user_id": null
  },
  {
    "id": 141,
    "image_title": "owl",
    "img_url": "http://res.cloudinary.com/dq281hpqd/image/upload/v1559972965/uploads/w9aup9r76q6hiktdjvyp.png",
    "created_at": "2019-06-08T05:49:26.911Z",
    "updated_at": "2019-06-08T05:49:26.911Z",
    "user_id": null
  },
  {
    "id": 140,
    "image_title": "tea",
    "img_url": "http://res.cloudinary.com/dq281hpqd/image/upload/v1559966806/uploads/y9q4upguo3hy6vhkk0va.png",
    "created_at": "2019-06-08T04:06:47.217Z",
    "updated_at": "2019-06-08T04:06:47.217Z",
    "user_id": null
  },
  {
    "id": 139,
    "image_title": "owl",
    "img_url": "http://res.cloudinary.com/dq281hpqd/image/upload/v1559966278/uploads/wph0kuhzg52lxkhxmxsi.png",
    "created_at": "2019-06-08T03:57:58.351Z",
    "updated_at": "2019-06-08T03:57:58.351Z",
    "user_id": null
  },
  {
    "id": 138,
    "image_title": "uuuuuu",
    "img_url": "http://res.cloudinary.com/dq281hpqd/image/upload/v1559965905/uploads/m1oxtyae2d6pyvteidyq.png",
    "created_at": "2019-06-08T03:51:46.815Z",
    "updated_at": "2019-06-08T03:51:46.815Z",
    "user_id": null
  },
  "http://www.conservewildlifenj.org/images/artmax_1001.jpg",
  "cheese2"
]

Upvotes: 0

Views: 1923

Answers (1)

Cat_Enthusiast
Cat_Enthusiast

Reputation: 15698

When you execute handleUpload() it looks like you just are not passing the new image object correctly to your component-state, I'll break this down into parts:

This is fine:

handleUpload =  file =>  {
    const data = new FormData()
    const image = file[0]
    data.append('ourImage',image, this.state.description )
    Axios.post('/images/upload', data).then((response) => {

Create a shallow copy of the new image:

This is good practice so you do not accidentally mutate the original object. Although to be honest we probably do not need to do this because the item is stored in your database and any mutations here would not affect it

    const newImage = {...response.data}

We want a new object that contains all key-value pairs of the old object. Since the image object only has one layer of data { key: string } not { key: {} } using the spread operator ... is sufficient.

We do this because we never want to run into a scenario like this:

const newImage = response.data // { url: "https://boygeniusreport.files.wordpress.com/2015/06/funny-cat.jpg?quality=98&strip=all&w=782"}

newImage.url = "https://boygeniusreport.files.wordpress.com/2016/05/scared-surprised-cat-face.jpg?quality=98&strip=all&w=782"

Our expectation is that our code will only change the url in newImage, but we will find that response.data also been changed:

console.log(newImage.url) //"https://boygeniusreport.files.wordpress.com/2016/05/scared-surprised-cat-face.jpg?quality=98&strip=all&w=782"
console.log(response.data.url)  //"https://boygeniusreport.files.wordpress.com/2016/05/scared-surprised-cat-face.jpg?quality=98&strip=all&w=782"

In JavaScript, when you make a copy of an object, const newImage = oldImage you do not just simply copy its value. You copy its entire reference in memory. Making a shallow-copy with ... works around that by making a completely new reference. It's good practice to make a shallow-copy if you ever want to define new values inside an object you initially copied.

Update component-state

        this.setState({
            image_url: newImage.img_url,
            description: newImage.image_title,
            images: [
               ...this.state.images,
               {
                  id: newImage.id,
                  image_title: newImage.image_title,
                  img_url: newImage.img_url,
                  created_at: newImage.created_at,
                  updated_at: newImage.updated_at,
                  user_id: null
               }
            ]
        })

    });
}

This code-block here says we will make a shallow-copy of this.state.images, Then we will create a new object, using the values inside our newImage obj. As you remember, newImage was made by creating a shallow-copy of response.data. So newImage has all the key-value pairs we need.

images: [
   ...this.state.images,
   {
      id: newImage.id,
      image_title: newImage.image_title,
      img_url: newImage.img_url,
      created_at: newImage.created_at,
      updated_at: newImage.updated_at,
      user_id: null
   }
]

Also, when you call componentDidUpdate() you are adding only description and image_url as individual values to the images array. This seems like a mistake, you already added the image in your handleClick(). But, if really want tom you should pass in an object if anything like so.

componentDidUpdate(prevProps, prevState){
    if(this.state.images.length !== prevState.images.length){
       //unnecessary, since we already added the image, but test it out anyway.
       this.setState({
           images: [...this.state.images, { image_url: this.state.image_url, description: this.state.description}]
       })
    }
} 

Note: If you decide to use componentDidUpdate() you'll notice that you will not have fields like "created_at", "updated_at" and others available because those are fields added to the new image object from your backend API.

Upvotes: 2

Related Questions