I'm using multer to upload images from my react client. All the images are being stored they just aren't showing up. The images are being stored, but aren't showing up in react. The are set in state just not displaying. I can see them in the images folder on the backend so I know multer is working.

This is my react component

class AddNewDish extends Component {
    constructor(props){
        super(props)
        this.state = {
            name: '',
            imageUrl: '',
            description: ''

        }
    }



    createDishHandler = (event) => {
        event.preventDefault()
        const fd = new FormData();
        fd.append('name', this.state.name)
        fd.append('imageUrl', this.state.imageUrl, this.state.imageUrl.name)
        fd.append('description', this.state.description)
        axios.post('http://localhost:8080/add-new-dish', fd,)
            .then(res => {
                console.log(res, 'res')
            })
        this.props.history.push('/')
    }


    onChange = (e) => {
        switch(e.target.name) {

            case 'imageUrl':
                this.setState({imageUrl: e.target.files[0]});
                break;
            default:
                this.setState({[e.target.name]: e.target.value})
        }
        console.log(e)
    }



    render () {
        console.log(this.state.imageUrl, 'in imgUrl')
        return (
            <div>
             <form onSubmit={this.createDishHandler}> 
                <input
                    label='Name'
                    onChange={this.onChange}
                    name='name'
                    type='text'
                    placeholder='Enter Dish Name'
                    value={this.state.name}
                />

                < input
                    label='Dish Image'
                    onChange={this.onChange}
                    name='imageUrl'
                    type='file'
                    placeholder='Enter Dish Image'
                    // value={this.state.imageUrl}
                />

                 <input
                    label='Description'
                    onChange={this.onChange}
                    name='description'
                    placeholder='Enter Dish Description'
                    type='text'
                    value={this.state.description}
                />
                <img src={this.state.imageUrl}/>
                 <button>Submit Dish</button> 
             </form> 
            </div>
        )
    }
};

export default AddNewDish

One the backend


const multer = require('multer');

const fileStorage = multer.diskStorage({
    destination: (req, file, cb) => {
        cb(null, 'public')
    },
    filename: (req, file, cb) => {
        cb(null, new Date().toISOString() + '-' + path.extname(file.originalname))
    }
});

const fileFilter = (req, file, cb) => {
    if(
        file.mimetype === 'image/png' ||
        file.mimetype === 'image/jpg' || 
        file.mimetype === 'image/jpeg'
        ) {
            cb(null, true)
    } else {
        cd(null, false)
    }
}
exports.upload = multer({

        storage: fileStorage, 
        fileFilter: fileFilter
    })
        .single('imageUrl')

This is how I call my route

exports.postADish = async  ( req, res,) => {
    console.log(req.file, 'in req.file')

    try {
        const { name,  description } = req.body;
        const imageUrl = req.file.path

            const newDish = await dish.postNewDish({name, description, imageUrl})
            res.status(201).json(`new dish added`)

    } catch (err) {
        res.status(500).json(`Error posting dish`)
        console.log(err)
    }
};

How I use upload middleware

router.post('/add-new-dish', dishController.upload, dishController.postADish);

making folder public

app.use('/public', express.static('public'))

This is how I'm trying to display the images in my index route on the frontend

class App extends Component {
  constructor () {
    super()
    this.state = {
      data: []

    }

  }

    componentDidMount()  {
      axios
        .get(`http://localhost:8080/`)
        .then(res => {
          console.log(res, 'response')
          this.setState({
            data: res.data.dishData
          })
        })
        .catch(err => {
          console.log(err)
        })
  }


  render() {
   console.log(this.state.data)
    return (
      <div className="App">
        <Nav

         <Route
            exact path ='/'
            render={props =>
              <Home 
                {...props}
                dishes={this.state.data}

              />
            }
          />

My state looks like this

{id: 7, name: "test dish", imageUrl: "public/2019-04-20T22:52:09.900Z-20190411_112505.jpg", description: "test description"}

I'm getting back the imageUrl and using it in the home component like this

const Home = (props) => {
    return (
        <DishWrapper>
            <DishContent>
           {props.dishes.map(dish => {
               console.log(dish, 'in dish')
               return (

                   <Dish key={dish.id}>
                        <h3>{dish.name}</h3>
                        <img src={ `public/${dish.imageUrl}`} alt={dish.name}/>
                        <h5>{dish.description}</h5>
                   </Dish>

               )
           })}
            </DishContent>
        </DishWrapper>
    )
};

export default Home

In the backend my get looks like this

exports.getDishes = async (req, res) => {
    try {
        const dishData = await dish.getDishes()
        if(!dishData) {
            res.status(404).json(`No dishes available at this time`)
        } else {
            res.status(200).json({
                dishData, 

            })
        }
    } catch (err) {
        res.status(500)
        console.log(err)
    }
};

1 Answers

0
Steve K On

So to view the image before the the upload you need to first use FileReader which is a javascript object that lets web applications asynchronously read the contents of files. This will give you a base64 version of your image that you can add to your image src. So you can do this in your onChange function and it would look something like the following:

onChange = (e) => {
    switch(e.target.name) {

        case 'imageUrl':
            const file = e.target.files[0];
            const reader = new FileReader();
            reader.onload = () => {
                this.setState({
                    imageUrl: file,
                    imgBase64: reader.result
                })
            };
            reader.readAsDataURL(file);
            break;
        default:
            this.setState({[e.target.name]: e.target.value})
    }
}

in this example i'm adding to the state the imgBase64 key and adding the base64 value to it but you can use whatever name you like just be sure to add it to your state object.

Then to view it you can use this value as the image src like so:

<img src={this.state.imgBase64}/>

After that when you submit your image to multer you need to return the image's filename so you can access it after you upload it so in your route since it seems like your getting the correct filename back when you log it you can return it to your axios call and use it after that. So in your route just send back a json object and use it. So instead of res.status(201).json(new dish added) send something like the following:

res.status(201).json({
  msg: 'new dish added',
  imageUrl: req.file.filename
})

and then your axios call will receive this json object and you can access in your frontend afterwards like so:

createDishHandler = (event) => {
    event.preventDefault()
    const fd = new FormData();
    fd.append('name', this.state.name)
    fd.append('imageUrl', this.state.imageUrl, this.state.imageUrl.name)
    fd.append('description', this.state.description)
    axios.post('http://localhost:8080/add-new-dish', fd,)
        .then(res => {
            //you can set your res.data.imageUrl to your state here to use it
            console.log('Success Message: ' + res.data.msg + ' - Image File Name: ' + res.data.imageUrl)
        })
    this.props.history.push('/')
}

But in the above function I see that you push do a different page when uploading. So if you push to a different page then you obviously aren't going to be using this state anymore so you will need probably need to just retrieve it from your database at that point from the new page.

Anyway I hope this helps and if you have any questions let me know.

P.S. I just wanted to let you know you may want to use Date.now() + path.extname(file.originalname) instead of new Date().toISOString() + '-' + path.extname(file.originalname) in you multer upload it looks a little cleaner without all of the colons and dashes but it's not necessary.

Edit:

So if you are going serve up a static folder with express then like I said in previous comments below you are going to have to use absolute urls to access your content. React cannot access anything with relative paths outside of the public folder in your client. So if in your backends root you are going to have a folder named images then in multer you would set the destination to 'images' with express you would serve up the static folder app.use(express.static('images')) and to access this with your image you would need to use an absolute url

<img src={ `http://localhost:8080/${imageUrl}`} alt={dish.name}/>