【发布时间】:2021-08-02 08:26:41
【问题描述】:
很抱歉,我不得不重新表述这个问题,因为我从来不知道这个问题来自我的 rails-api 后端。
当我尝试从我的 react-redux 前端创建 appointments 列表时,我收到此错误:
appointmentsSlice.js:16 POST http://localhost:3000/api/v1/appointments 422 (Unprocessable Entity)
最初,我能够在错误出现之前创建一个约会列表。而且我没有对后端/前端进行任何重大更改。
这些是来自我的 rails 后端的日志:
Started POST "/api/v1/appointments" for ::1 at 2021-08-04 20:53:35 +0100
Processing by AppointmentsController#create as JSON
Parameters: {"appointment_date"=>"2021-08-04", "doctor_id"=>"", "user_id"=>20, "appointment"=>{"appointment_date"=>"2021-08-04", "doctor_id"=>"", "user_id"=>20}}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 20], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:28:in `current_user'
CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 20], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:28:in `current_user'
Unpermitted parameter: :appointment
Completed 422 Unprocessable Entity in 414ms (Views: 373.9ms | ActiveRecord: 1.1ms | Allocations: 343604)
Started GET "/api/v1/appointments" for ::1 at 2021-08-04 20:53:36 +0100
Processing by AppointmentsController#index as */*
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 20], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:28:in `current_user'
CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 20], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:28:in `current_user'
Appointment Load (0.3ms) SELECT "appointments".* FROM "appointments" WHERE "appointments"."user_id" = ? [["user_id", 20]]
↳ app/controllers/appointments_controller.rb:8:in `index'
Completed 200 OK in 8ms (Views: 3.5ms | ActiveRecord: 0.5ms | Allocations: 2300)
class ApplicationController < ActionController::API
include Response
private
def secret
Rails.application.secret_key_base
end
def encode_token(payload)
JWT.encode(payload, secret)
end
def decoded_token
auth = request.headers['Authorization']
if auth
token = auth.split(' ')[1]
JWT.decode(token, secret, true, algorithm: 'HS256')
end
rescue StandardError
nil
end
def current_user
return unless decoded_token
user_id = decoded_token[0]['user_id']
User.find_by(id: user_id)
end
def authorize
render json: { message: 'Please log in.' }, status: :unauthorized if current_user.nil?
end
end
class AppointmentsController < ApplicationController
before_action :authorize
before_action :find_appointment, only: %i[show update destroy]
def index
@appointments = current_user.appointments
render json: @appointments
end
def show
render json: @appointment
end
def create
@appointment = current_user.appointments.build(appointment_params)
if @appointment.save
render json: @appointment, status: :created
else
render json: @appointment.errors, status: :unprocessable_entity
end
end
def update
if @appointment.update(appointment_params)
render json: @appointment
else
render json: @appointment.errors, status: :unprocessable_entity
end
end
def destroy
@appointment.destroy
render json: { message: 'Appointment deleted' }, status: :ok
end
private
def find_appointment
@appointment = Appointment.find(params[:id])
end
def appointment_params
params.permit(:appointment_date, :doctor_id, :user_id)
end
end
来自我的 react-redux 前端的 src/redux/appointmentsSlice
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import API from '../api/api';
export const postAppointments = createAsyncThunk(
'appointments/postAppointments',
async (
{
user_id, appointment_date, doctor_id,
},
) => {
const token = localStorage.getItem('token');
const response = await fetch(`${API}/appointments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
appointment_date,
doctor_id,
user_id,
}),
});
const data = await response.json();
if (!response.ok) throw new Error(data.failure);
return data;
},
);
export const getAppointments = createAsyncThunk(
'appointments/getAppointments',
async () => {
const token = localStorage.getItem('token');
const response = await fetch(`${API}/appointments`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) throw new Error(response.statusText);
const data = await response.json();
return data;
},
);
export const appointmentsSlice = createSlice({
name: 'appointments',
initialState: {
loading: false,
error: null,
data: [],
},
extraReducers: {
[postAppointments.pending]: (state) => {
state.loading = true;
},
[postAppointments.rejected]: (state, action) => {
state.loading = false;
state.error = action.error.message;
},
[postAppointments.fulfilled]: (state, action) => {
state.loading = false;
state.data.push(action.payload);
},
[getAppointments.pending]: (state) => {
state.loading = true;
},
[getAppointments.rejected]: (state, action) => {
state.loading = false;
state.error = action.error.message;
},
[getAppointments.fulfilled]: (state, action) => {
state.loading = false;
state.data = action.payload;
},
},
});
export default appointmentsSlice.reducer;
src/components/约会
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, Redirect } from 'react-router-dom';
import { getAppointments } from '../redux/appointmentsSlice';
const Appointments = () => {
const dispatch = useDispatch();
const { data: user } = useSelector((state) => state.user);
const appointments = useSelector((state) => state.appointments);
console.log('appointments', appointments);
const { data, loading } = appointments;
useEffect(() => {
if (user) {
dispatch(getAppointments());
}
}, []);
if (!user) {
return <Redirect to="/Login" />;
}
return (
<div className="container text-center">
<h3>Appointments</h3>
{loading && <span className="spinner-border spinner-border-lg" />}
<div className="d-flex flex-wrap">
{(!loading && data.length === 0)
&& (
<h4>
You do not have any appointment. Create one
<Link to="/appointments/new">
here
</Link>
</h4>
)}
</div>
{
data && data.map((appointment) => {
const d = new Date(appointment.appointment_date);
const date = d.toUTCString();
return (
<Link to={`/appointments/${appointment.id}`} key={appointment.id}>
<div className="card m-4">
<div className="card-body">
<p>
On
{date}
</p>
</div>
</div>
</Link>
);
})
}
</div>
);
};
export default Appointments;
src/components/NewAppointment
/* eslint-disable camelcase */
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { postAppointments } from '../redux/appointmentsSlice';
import { getDoctors } from '../redux/doctorsSlice';
const NewAppointment = () => {
const [appointmentDate, setAppointmentDate] = useState('');
const [doctorId, setDoctorId] = useState('');
const [successful, setSuccessful] = useState(false);
const [loading, setLoading] = useState(false);
const { data: userData } = useSelector((state) => state.user);
const { user_id } = userData;
const dispatch = useDispatch();
const { data, error } = useSelector((state) => state.doctors);
useEffect(() => {
if (data === null && userData) {
dispatch(getDoctors())
.then(() => {
loading(false);
})
.catch(() => {
});
}
}, [data, dispatch]);
const onChangeDoctorId = (e) => {
const doctorId = e.target.value;
setDoctorId(doctorId);
};
const onChangeAppointmentDate = (e) => {
const appointmentDate = e.target.value;
setAppointmentDate(appointmentDate);
};
const doctor_id = doctorId;
const appointment_date = appointmentDate;
const handleBooking = (e) => {
e.preventDefault();
setSuccessful(false);
// eslint-disable-next-line no-underscore-dangle
dispatch(postAppointments({
user_id, doctor_id, appointment_date,
}))
.then(() => {
setSuccessful(true);
alert.show('Appointment created', {
type: 'success',
timeout: 2000,
});
setLoading(false);
})
.catch((error) => {
console.log(error.message);
setSuccessful(false);
});
};
const options = data && (
data.map((doctor) => (
<option
key={doctor.id}
value={doctor.id}
>
{doctor.name}
</option>
))
);
if (!userData) {
return <Redirect to="/Login" />;
}
if (successful) {
return <Redirect to="/appointments" />;
}
return (
<div className="col-md-12">
<div className="card card-container">
<form onSubmit={handleBooking}>
{ !successful && (
<div>
<div className="form-group create">
<label htmlFor="appointmentDate" className="control-label">
Appointment Date
<input
type="date"
className="form-control"
name="appointmentDate"
id="appointmentDate"
required
value={appointmentDate}
onChange={onChangeAppointmentDate}
/>
</label>
</div>
<div className="form-group create">
<label htmlFor="doctorId">
Select from list:
<select className="form-control" id="doctorId" onChange={onChangeDoctorId} value={doctorId}>
{loading ? <option>Loading..</option> : options }
</select>
</label>
</div>
<div className="form-group create">
<button className="btn btn-primary btn-block" disabled={loading} type="submit">
{loading && (
<span className="spinner-border spinner-border-sm" />
)}
<span>Book</span>
</button>
</div>
</div>
)}
{error && (
<div className="form-group">
<div className={successful ? 'alert alert-success' : 'alert alert-danger'} role="alert">
{error}
</div>
</div>
)}
</form>
</div>
</div>
);
};
export default NewAppointment;
src/redux/存储
import { configureStore } from '@reduxjs/toolkit';
import doctorsReducer from './doctorsSlice';
import appointmentsReducer from './appointmentsSlice';
import userReducer from './userSlice';
import typeReducer from './typeSlice';
import doctorReducer from './doctorSlice';
export default configureStore({
reducer: {
doctors: doctorsReducer,
appointments: appointmentsReducer,
user: userReducer,
type: typeReducer,
doctor: doctorReducer,
},
});
我知道问题出在我的 rails-api 上。我所有的几个更改都无法解决这个问题。我已经在这个平台内外实现了类似问题的解决方案,但没有一个能解决我的问题。 我真的不知道我错过了什么。
【问题讨论】:
-
检查${id}的值,看起来是空的或者没有设置。
-
@Grumpy,是的,它把它标记为
undefined。但为什么?。我想我已经以正确的方式添加了它。 ${id} 现在是一个变量,它将自动从商店获取数据的 id 值。这是我的思路。如果我错了,请纠正我。 -
你在哪里派送
postAppointments? -
@ksav 到 redux 商店。
-
这意味着我们只能更新
user_id、doctor_id和appointment_date这三个参数。任何其他参数都会使其错误或引发错误。因此,日志中的这个:"appointment"=>{"appointment_date"=>"2021-08-04", "doctor_id"=>"", "user_id"=>20}不应该是参数的一部分。我不知道从 react-redux 端生成它的原因。
标签: react-redux rails-api redux-toolkit