当状态更改时,react不会获取api属性,而在重新渲染时会获取?

时间:2019-06-14 06:15:03

标签: reactjs

我收到此错误

  

img.user.username未定义。

<Typography variant="h6" align="center">{img.user.username}</Typography> 

上传图片时

  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) => {
            const newImage = {...response.data}


            //update component-state
            this.setState({
                image_url: newImage.img_url,
                description: '',
                images: [
                   {
                      id: newImage.id,
                    //   user: newImage.user.username,
                      image_title: newImage.image_title,
                      img_url: newImage.img_url,
                      created_at: new Date().toLocaleString().replace(',', ''),
                      updated_at: new Date().toLocaleString().replace(',', '')
                   },
                    ...this.state.images,

                ],

            })

        });
    }

{...response.data}不包含属性user,所以可以理解此错误,但是当我刷新页面时

img.user.username 显示用户名。,并且没有错误。

这会从后端获取帖子,我如何才能使其同时工作,例如何时上传图片以及何时刷新页面。

如果他们是编辑问题标题的更好方法,请做出更好的问题标题。

componentWillMount(){
    Axios.get('/images/uploads').then( (response) => {
        // let img;
        // let imgTitle;
        Object.keys(response.data).forEach( (key) => {
            console.log(response.data[key]);
            this.setState({
                images:[ ...this.state.images, response.data[key]]
            })
            console.log(this.state.images);
        });
    })
}

完整代码

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 Divider from '@material-ui/core/Divider';
import Axios from '../Axios';
import Image from './Image';
import moment from 'moment';
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) => {
            const newImage = {...response.data}


            //update component-state
            this.setState({
                image_url: newImage.img_url,
                description: '',
                images: [
                   {
                      id: newImage.id,
                    //   user: newImage.user.username,
                      image_title: newImage.image_title,
                      img_url: newImage.img_url,
                      created_at: new Date().toLocaleString().replace(',', ''),
                      updated_at: new Date().toLocaleString().replace(',', '')
                   },
                    ...this.state.images,

                ],

            })

        });
    }
    handleChange = (e) => {
        // e.preventDefault();
        this.setState({
            [e.target.name]: e.target.value
        })
        // console.log(this.state.description)
    }
    componentWillMount(){
        Axios.get('/images/uploads').then( (response) => {
            // let img;
            // let imgTitle;
            Object.keys(response.data).forEach( (key) => {
                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.images.length !== prevState.images.length) {
            console.log(this.state.images);
        }
        // debugger;
    }
    onUploadClick = (e) => {
        e.preventDefault();
        this.setState({
            upload: !this.state.upload
        })
    }
    deleteImg = (id) => {
        Axios.post(`/images/delete/${id}`).then( () => {
           this.setState({
               images: [ ...this.state.images.filter(img => img.id !== id)]
           })
        })
    }
    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>
                        <br></br>
                        <br></br>
                        {this.state.upload ? (
                            <div>
                             <TextField
                                 id="outlined-name"
                                 label="Image Title"
                                 name="description"
                                 type="text"
                                 required={true}
                                 fullWidth
                                 style={{ borderRadius: '0px'}}
                                 className=""
                                 value={this.state.description}
                                 onChange={this.handleChange}
                                 margin="normal"
                               />
                                <br></br>
                                <br></br>
                                {/* so here what we are saying, if this text field is FILLED show the uploader component 
                                else hide it.
                                */}
                                {this.state.description ? uploader : null}

                            </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 style={{padding:'20px 20px'}}>
                                        {/* // empty image_title */}
                                        <Typography style={{ padding: '30px 5px', letterSpacing:'8px', textTransform:'uppercase'}} variant="h4" align="center">{img.image_title}</Typography> 
                                        <Divider style={{ width: '150px', margin:'10px auto', backgroundColor:'#000000'}} variant="middle" />
                                    <Image image_url={img.img_url} />   
                                    <Typography variant="h6" align="center">{img.user.username}</Typography> 
                                    <Typography variant="h6" align="center">{moment(img.created_at).calendar()}</Typography> 
                                    <Button onClick={() => this.deleteImg(img.id)} variant="outlined" component="span" color="primary">
                                        Delete
                                    </Button>
                                </Paper>                              
                            </Grid>
                        ))
                    ) : (
                        <div>
                            <Grid item md={8}>
                                <Typography>No Images yet</Typography>
                            </Grid>
                        </div>
                    )}
                </Grid>
                {/* Images  */}
                </Grid>
            </div>
        )
    }
}
export default Dashboard;

后端

router.get('/uploads', async (req, res) =>  {
    await Image.query( (image) => {
        image.orderBy('img_url', 'DESC')
        image.limit(10)
        // if you want to include the user with the image, you would use the withRelated 
    }).fetchAll({withRelated: ['user']}).then( (images) => {
        // console.log(images.toJSON());
        return res.status(200).json(images.toJSON());
    })
})

router.post('/upload',  multipartMiddleware,  upload.single('ourImage'), (req, res) => {
     if(!req.files){
         return res.status(500).send("Please upload a file");
     }
    //  console.log(req.files)
    cloud.uploader.upload(req.files.ourImage.path, {crop: "fill", folder: '/uploads'} ,    (err, result) => {
        if(err){
            return res.status(500).send(err);
        }
        // console.log(req.user)
        const img = new Image({
            img_url:result.url,
            image_title:req.files.ourImage.name,
            user_id: req.user.id

        });
        // console.log(img);

        img.save().then( img => {
            return res.status(200).json(img);
        });

    });
});

3 个答案:

答案 0 :(得分:1)

您的实际问题是您的后端方法GET /images/uploads和POST /images/upload的每个项目都不具有相同的属性,即,没有提供user来响应POST {{1 }},从而导致前端数据不一致。

最佳解决方案:在POST /images/update的响应中添加user,确保两者的响应数据相同

如果无法这样做:

  • 一种解决方案是在上传图片后刷新整个列表,即再次调用images/upload。这是昂贵的,但是要确保数据中的所有项目都具有渲染所需的属性。
  • 另一种解决方案是跳过显示上传图像的用户名:
Axios.get('/images/uploads')

但是,这不会保持列表的完整性。

  • 另一个便宜的解决方案是,如果您将新存储的图像存储在React应用程序中的某个位置,则为新上载的图像将当前用户名分配为<Typography variant="h6" align="center">{!!img.user ? img.user.username : ""}</Typography> ,因为上传者将始终是当前用户。
user

答案 1 :(得分:1)

首先,确保后端POST响应以与GET响应相同的格式返回数据。

const img = new Image({
  img_url:result.url,
  image_title:req.files.ourImage.name,
  user_id: req.user.id
});

Image.save().then(img => {
  /* Do something like this, but for a single image...
  await Image.query(image => {
    image.orderBy("img_url", "DESC");
    image.limit(10);
  })
    .fetchAll({ withRelated: ["user"] })
    .then(images => {
      return res.status(200).json(images);
    });
  */
});

此外,当您在handleUpload方法中调用setState时,您没有在要添加到images数组的新对象上设置user.username属性。也许尝试更换

this.setState({
  images: [
    {
      id: newImage.id,
      //   user: newImage.user.username,
      image_title: newImage.image_title,
      img_url: newImage.img_url,
      created_at: new Date().toLocaleString().replace(',', ''),
      updated_at: new Date().toLocaleString().replace(',', '')
    },
    ...this.state.images
  ]
})

使用

this.setState({
  images: [
    {
      id: newImage.id,
      user: {
        username: newImage.user.username
      },
      image_title: newImage.image_title,
      img_url: newImage.img_url,
      created_at: new Date().toLocaleString().replace(',', ''),
      updated_at: new Date().toLocaleString().replace(',', '')
    },
    ...this.state.images
  ]
})

或进一步简化

this.setState(prevState => ({
  images: [
    {
      ...newImage,
      created_at: new Date().toLocaleString().replace(',', ''),
      updated_at: new Date().toLocaleString().replace(',', '')
    },
    ...prevState.images
  ]
}))

答案 2 :(得分:0)

因此,在从@blaz和@Gabor Szekely获得反馈后,我重构了代码,现在它可以工作了。

我意识到在提出帖子请求时需要使用[0],然后在上传帖子时显示该帖子。像这样

this.setState({
    description:'',   // resets title after upload
    images: [
        {
        id: newImage[0].id,
        user:{
            username: newImage[0].user.username
        },

        image_title: newImage[0].image_title,
        img_url: newImage[0].img_url,
        created_at: new Date().toLocaleString().replace(',', ''),
        updated_at: new Date().toLocaleString().replace(',', '')
        },
        ...this.state.images
    ]
})

以及在POST方法中查询图像。我的印象是我无法在发帖请求中获取图像,但是您当然可以。以及将您从router.postrouter.all的路线。

但是,我想将其保留为帖子,因为它无论哪种方式都可以工作。

router.post('/upload',  multipartMiddleware,  upload.single('ourImage'), (req, res) => {
     if(!req.files){
         return res.status(500).send("Please upload a file");
     }
    //  console.log(req.files)
    cloud.uploader.upload(req.files.ourImage.path, {crop: "fill", folder: '/uploads'} ,    (err, result) => {
        if(err){
            return res.status(500).send(err);
        }
        // console.log(req.user)
        const img = new Image({
            img_url:result.url,
            image_title:req.files.ourImage.name,
            user_id: req.user.id

        });
        // console.log(img);
        // fetches image with user when a upload is made
        img.save().then( async () => {
            await Image.query(image => {
                image.orderBy("img_url", "DESC");
                image.limit(10);
              })
                .fetchAll({ withRelated: ["user"] })
                .then(images => {
                  return res.status(200).json(images);
                });

        });

    });
});