I am working on an application consisting of a Laravel 8 API and a Vue 3 front-end.
I have a registration form whose validation fails.
In the users table migration file I have:
class CreateUsersTable extends Migration {
public function up() {
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('first_name');
$table->string('last_name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->unsignedInteger('country_id')->nullable();
$table->foreign('country_id')->references('id')->on('countries');
$table->rememberToken();
$table->timestamps();
});
}
// More code here
}
In the routes file (routes\api.php):
Route::get('/register', [AuthController::class, 'countries']);
Route::post('/register', [AuthController::class, 'register']);
As can be seen above, the id in the countries table is a foreign key in the users table.
I have this piece of code in the AuthController to register a new user:
class AuthController extends Controller {
public function countries()
{
return country::all('id', 'name', 'code');
}
public function register(Request $request) {
$rules = [
'first_name' => 'required|string,',
'last_name' => 'required|string',
'email' => 'required|email|unique:users,email',
'password' => 'required|string|confirmed',
'country_id' => 'required|exists:countries',
'accept' => 'accepted',
];
$customMessages = [
'first_name.required' => 'First name is required.',
'last_name.required' => 'Last name is required.',
'email.required' => 'A valid email is required.',
'email.email' => 'The email address you provided is not valid.',
'password.required' => 'A password is required.',
'password.confirmed' => 'The passwords do NOT match.',
'country_id.required' => 'Please choose a country.',
'accept.accepted' => 'You must accept the terms and conditions.'
];
$fields = $request->validate($rules, $customMessages);
$user = User::create([
'first_name' => $fields['first_name'],
'last_name' => $fields['last_name'],
'email' => $fields['email'],
'password' => bcrypt($fields['password']),
'country_id' => $fields['country_id']
]);
$token = $user->createToken('secret-token')->plainTextToken;
$response = [
'countries' => $this->countries(),
'user' => $user,
'token' => $token
];
return response($response, 201);
}
}
On the front-end, I have:
const registrationForm = {
data() {
return {
apiUrl: 'http://myapp.test/api',
formSubmitted: false,
countries: [],
fields: {
first_name: '',
last_name: '',
email: '',
password: '',
country_id: 0,
accepted: '',
},
errors: {},
};
},
methods: {
// Select country
changeCountry(e) {
if(e.target.options.selectedIndex > -1) {
this.country_id = parseInt(e.target.options[e.target.options.selectedIndex].value);
}
},
// get Countries
getCountries(){
axios.get(`${this.apiUrl}/register`).then((response) =>{
// Populate countries array
this.countries = response.data;
}).catch((error) => {
this.errors = error.response.data.errors;
});
},
registerUser(){
// Do Registrarion
axios.post(`${this.apiUrl}/register`, this.fields).then(() => {
// Show success message
this.formSubmitted = true;
// Clear the fields
this.fields = {}
}).catch((error) => {
this.errors = error.response.data.errors;
});
}
},
created() {
this.getCountries();
}
};
Vue.createApp(registrationForm).mount("#myForm");
In the Vue template:
<form id="myForm">
<div v-if="formSubmitted" >
<button type="button" data-dismiss="alert">×</button>
Your account was created :)
</div>
<div :>
<input type="text" placeholder="First name" v-model="fields.first_name">
<span v-if="errors.first_name" >{{ errors.first_name[0] }}</span>
</div>
<div :>
<input type="text" placeholder="Last name" v-model="fields.last_name">
<span v-if="errors.last_name" >{{ errors.last_name[0] }}</span>
</div>
<div :>
<input type="email" placeholder="Enter email" v-model="fields.email">
<span v-if="errors.email" >{{ errors.email[0] }}</span>
</div>
<div :>
<input type="password" placeholder="Enter password" v-model="fields.password">
<span v-if="errors.password" >{{ errors.password[0] }}</span>
</div>
<div :>
<input type="password" placeholder="Confirm password" v-model="fields.password_confirmation">
<span v-if="errors.password_confirmation" >{{ errors.password_confirmation[0] }}</span>
</div>
<div >
<select @change="changeCountry">
<option value="0" selected>Select your country</option>
<option v-for="country in countries" :value="country.id">{{ country.name }}</option>
</select>
</div>
<div :>
<input type="checkbox" name="accept" v-model="fields.accept">
<span >By creating an account I accept <a href="#" >Terms & Privacy Policy</a></span>
<span v-if="errors && errors.accept" >{{ errors.accept[0] }}</span>
</div>
<div >
<button @click.prevent="registerUser" type="submit" >Register</button>
</div>
</form>
The problem
I fill the form, pick a country but, when I submit, it fails with a 422 status and the network tab shows:
{"message":"The given data was invalid.","errors":{"country_id":["The selected country id is invalid."]}}
Question
What am I doing wrong?
CodePudding user response:
You have mistake here this.country_id (registrationForm component) but property country_id is child of this.fields and you send fields to the server. Correct will be:
this.fields.country_id = parseInt(e.target.options[e.target.options.selectedIndex].value);
