javascriptvue.jsthis-pointer

VueJS Error in v-on handler variable this. is undefined


Working in VueJS with MongoDB/Express backend api, attempting to add post a new record to the Bookmarks model with a single field url, but getting an error that the url field I am attempting to pass is undefined. I have used this pattern for api calls before without trouble, so I am not sure what is happening this time.

from the console:

Error in v-on handler (Promise/async): "Error: Request failed with             status code 500"

in the network tab:

{"errors":{"message":"Cannot read property 'url' of undefined","error":{}}}

here is the component:

<template>
  <div class="bookmarks">
    <h1>Add a Bookmark</h1>
    <div class="form">
      <div>
        <input type="text" placeholder="Enter a URL" v-model="url">
      </div>
      <div>
        <button class="app_post_btn" @click="addBookmark">Save</button>
      </div>
    </div>
  </div>
</template>

<script>
import BookmarksService from '@/services/BookmarksService'
export default {
  name: 'NewBookmark',

  data () {
    return {
      url: ''
    }
  },
  methods: {
    async addBookmark () {
      await BookmarksService.addBookmark({
        url: this.url
      })
      this.$router.push({ name: 'Bookmarks' })
    }
  }
}
</script>

<style type='text/css'>
.form input,
.form textarea {
  width: 500px;
  padding: 10px;
  border: 1px solid #e0dede;
  outline: none;
  font-size: 12px;
}
.form div {
  margin: 20px;
}

.app_post_btn {
  background: #4d7ef7;
  color: #fff;
  padding: 10px 80px;
  text-transform: uppercase;
  font-size: 12px;
  font-weight: bold;
  width: 520px;
  border: none;
  cursor: pointer;
}
</style>

The api call to the backend comes via the services folder so in BookmarksService.js:

import Api from '@/services/Api'

export default {
  fetchBookmarks () {
    return Api().get('bookmarks')
  },

  addBookmark (params) {
    return Api().post('bookmarks', params)
  }
...

Api.js:

import axios from 'axios'
const token = localStorage.getItem('token')

export default () => {
  return axios.create({
    baseURL: 'http://localhost:8000/api',
    headers: {
      'Content-Type': 'application/json',
      token: token,

    }
  })
}

In response to comments below, here is the request info:

Request URL: http://localhost:8000/api/bookmarks
Request Method: POST
Status Code: 500 Internal Server Error
Remote Address: [::1]:8000
Referrer Policy: no-referrer-when-downgrade
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 75
Content-Type: application/json; charset=utf-8
Date: Fri, 05 Apr 2019 16:47:01 GMT
ETag: W/"4b-DnvHolHvXjMbxmLeqrleWSWRA0Q"
X-Powered-By: Express
Provisional headers are shown
Accept: application/json, text/plain, */*
authorization: Token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVzZXJvbmVAZ21haWwuY29tIiwiaWQiOiI1YzlmOTcxY2E3YzIyNTc1ZTFjYzkwMDUiLCJleHAiOjE1NTk2NjY3ODcsImlhdCI6MTU1NDQ4Mjc4N30.zlrqFfj2r7K_1iAVm7m2w_kUeqFH3ZvsDJkIK7UOUu8
Content-Type: application/json
Origin: http://localhost:8080
Referer: http://localhost:8080/
token: Token undefined
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
{url: "http://axios.com"}
url: "http://axios.com"

on the server side:

here is the post route:

const mongoose = require('mongoose');
const passport = require('passport');
const router = require('express').Router();
const auth = require('../auth');
const Bookmarks = mongoose.model('Bookmarks');

//POST new user route (optional, everyone has access)
router.post('/', auth.required, (req, res, next) => {
  const userId = req.user.id;
  const bookmark = req.body.bookmark;

  if(!bookmark.url) {
    return res.status(422).json({
      errors: {
        url: 'is required',
      },
    });
  }

  bookmark.userId = userId;
  const finalBookmark = new Bookmarks(bookmark);

  return finalBookmark.save()
    .then(() => res.json({ bookmark: finalBookmark }));
});

...

the app.js file from the express app:

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const session = require('express-session');
const cors = require('cors');
const mongoose = require('mongoose');
const errorHandler = require('errorhandler');

//Configure mongoose's promise to global promise
mongoose.promise = global.Promise;

//Configure isProduction variable
const isProduction = process.env.NODE_ENV === 'production';

//Initiate our app
const app = express();

//Configure our app
app.use(cors());
app.use(require('morgan')('dev'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({ secret: 'bookmarks-darius', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false }));

if(!isProduction) {
  app.use(errorHandler());
}

//Configure Mongoose
mongoose.connect('mongodb://localhost/bookmarks', { useNewUrlParser: true });
mongoose.set('debug', true);

//Models & routes
require('./models/Users');
require('./models/Bookmarks');
require('./config/passport');
app.use(require('./routes'));

// express doesn't consider not found 404 as an error so we need to handle 404 explicitly handle 404 error
app.use(function(req, res, next) {
  if (err) {
    next(err);
  } else { // no error thrown by another route, so we must not have found a route, therefore return a 404
    let err = new Error('Not Found');
    err.status = 404;
    next(err);
  }
});

//Error handlers & middlewares
if(!isProduction) {
  app.use(function (err, req, res, next) {
    res.status(err.status || 500);

    res.json({
      errors: {
        message: err.message,
        error: err,
      },
    });
  });
}

app.use(function (err, req, res, next) {
  res.status(err.status || 500);

  res.json({
    errors: {
      message: err.message,
      error: {},
    },
  });
});

app.listen(8000, () => console.log('Server running on http://localhost:8000/'));

Solution

  • The body of the request that comes from your frontend is not what you expect.
    The main problem here is that in your addBookmark fuction in the vue.js component you are passing an object which has the only property 'url', while in your backend service you are expecting a 'bookmark' object which contains the property 'url'. You can see it also in the body of the request: {url: "http://axios.com"} Changing your component code to

    async addBookmark () {
      await BookmarksService.addBookmark({
        bookmark: {
                   url: this.url
               }
      })
      this.$router.push({ name: 'Bookmarks' })
    }
    

    should work. You should obtain something like this { bookmark: {url: "http://axios.com"} }. Anyway verify any property that comes from the request body as Phil said is always a good practice that you should follow.