【问题标题】:Getting 422 (Unprocessable Entity) from my react-redux front-end从我的 react-redux 前端获取 422(无法处理的实体)
【发布时间】: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 &nbsp;
                  {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,

  },
});

redux store

我知道问题出在我的 rails-api 上。我所有的几个更改都无法解决这个问题。我已经在这个平台内外实现了类似问题的解决方案,但没有一个能解决我的问题。 我真的不知道我错过了什么。

【问题讨论】:

  • 检查${id}的值,看起来是空的或者没有设置。
  • @Grumpy,是的,它把它标记为undefined。但为什么?。我想我已经以正确的方式添加了它。 ${id} 现在是一个变量,它将自动从商店获取数据的 id 值。这是我的思路。如果我错了,请纠正我。
  • 你在哪里派送postAppointments
  • @ksav 到 redux 商店。
  • 这意味着我们只能更新user_iddoctor_idappointment_date这三个参数。任何其他参数都会使其错误或引发错误。因此,日志中的这个:"appointment"=&gt;{"appointment_date"=&gt;"2021-08-04", "doctor_id"=&gt;"", "user_id"=&gt;20} 不应该是参数的一部分。我不知道从 react-redux 端生成它的原因。

标签: react-redux rails-api redux-toolkit


【解决方案1】:

createAsyncThunk - Payload creator

payloadCreator 函数将使用两个参数调用:

  1. arg:单个值,包含第一个参数,该参数在分派时传递给 thunk 动作创建者。这对于传递可能需要作为请求的一部分的项目 ID 等值很有用。如果您需要传递多个值,请在分派 thunk 时将它们一起传递到一个对象中,例如 dispatch(fetchUsers({status: 'active', sortBy: 'name'}))
  2. thunkAPI:一个对象,包含通常传递给 Redux thunk 函数的所有参数,以及其他选项...

确保正确调用动作创建者。例如,getAppointments 接受单个参数,即具有 idjwt 键的对象。例如

useEffect(() => {
  if (user) {
    dispatch(getAppointments({ id: user.user_id, jwt: 'somevalue' }));
  }
}, []);

同样,postAppointments 接受单个参数,即带有 user_idappointment_datedoctor_idjwt 键的对象。例如

dispatch(postAppointments({ 
  user_id: 'somevalue',
  appointment_date: 'somevalue',
  doctor_id: 'somevalue',
  jwt: 'somevalue',
}));

我想如果请求中没有有效的 jwt,您的 api 将返回 401 (Unauthorized)

【讨论】:

  • 我实现了上述解决方案。但我让jwt 成为undefinedgetAppointments 动作创建者。但是,对于 postAppointments 动作创建者,我得到了正确的 jwt。这是我实现的:const { data: user, jwt } = useSelector((state) =&gt; state.user);useEffect(() =&gt; { if (user) { dispatch(getAppointments({ id: user.user_id, jwt })); console.log('id', user.user_id); console.log('jwt', jwt); } }, []); 我想我错过了一些东西
  • 这两行记录了什么? console.log("id", user.user_id) console.log("jwt", jwt)
猜你喜欢
  • 2021-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-01
相关资源
最近更新 更多