React+Rails项目中PATCH请求触发路由错误,寻求解决方案
问题:React+Rails项目中PATCH请求更新预订数据出现路由错误
开发React前端搭配Rails后端的项目时,发送PATCH请求更新预订数据时触发ActionController::RoutingError,提示找不到匹配[PATCH] "/reservation/2"的路由。以下是相关代码及日志,附解决方法:
错误日志
Started PATCH "/reservation/2" for 127.0.0.1 at 2022-09-01 00:32:51 +0530 ActionController::RoutingError (No route matches [PATCH] "/reservation/2"): Started GET "/me" for 127.0.0.1 at 2022-09-01 00:32:51 +0530 Processing by UsersController#show as */* hello 4 User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 4], ["LIMIT", 1]] ↳ app/controllers/application_controller.rb:9:in `authorize' [active_model_serializers] Rendered UserSerializer with ActiveModelSerializers::Adapter::Attributes (0.17ms) Completed 200 OK in 2ms (Views: 0.4ms | ActiveRecord: 0.3ms | Allocations: 832) Started GET "/reservations" for 127.0.0.1 at 2022-09-01 00:32:51 +0530 Processing by ReservationsController#index as */* hello 4 User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [[ "id", 4], ["LIMIT", 1]] ↳ app/controllers/application_controller.rb:9:in `authorize' Reservation Load (0.2ms) SELECT "reservations".* FROM "reservations" WHERE "reservations"."user_id" = $1 [[ "user_id", 4]] ↳ app/controllers/reservations_controller.rb:4:in `index' [active_model_serializers] Restaurant Load (0.1ms) SELECT "restaurants".* FROM "restaurants" WHERE "restaurants"."id" = $1 LIMIT $2 [[ "id", 30], ["LIMIT", 1]] [active_model_serializers] ↳ app/controllers/reservations_controller.rb:4:in `index' [active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Attributes (1.53ms) Completed 200 OK in 4ms (Views: 2.3ms | ActiveRecord: 0.5ms | Allocations: 2873)
Rails路由配置(routes.rb)
Rails.application.routes.draw do resources :reservations,only: [:index,:show,:create,:update,:destroy] resources :reviews,only: [:index,:create,:destroy] resources :restaurants,only: [:index] post "/signup", to: "users#create" get "/me", to: "users#show" post "/login", to: "sessions#create" delete "/logout", to: "sessions#destroy" end
Reservations控制器代码
def update reservation = Reservation.find_by(id: params[:id]) reservation.update!(reservation_params) render json: reservation,status: :ok end def reservation_params params.permit(:name, :date, :time, :num, :contact, :occasion,:user_id,:restaurant_id) end
前端相关代码
App.js
function App() { const [user, setUser] = useState(null); const[reviews,setReviews]=useState([]); const[current,setCurrent]=useState({}); useEffect(() => { document.title = "Nyc"; }, []); useEffect(() => { // auto-login fetch("/me", { credentials: "same-origin" }).then((r) => { if (r.ok) { r.json().then((user) => setUser(user)); } }); }, [setUser]); function handleAddReviews(newReview) { setReviews([...reviews, newReview]); } if (!user) return <Loggin error={'please login'} onLogin={setUser} />; return ( <Cont.Provider value={ {current,setCurrent}}> <div className="App"> <Navbar user={user} setUser={setUser} /> <Routes> <Route exact path="/blogs" element={<Blogs />} /> <Route exact path="/myreservations" element={<MyReservations user={user} />} /> <Route exact path="/restaurants/:id" element= {<RestaurantInfo handleAddReviews={handleAddReviews} user={user} reviews={reviews} setReviews={setReviews}/>} /> <Route exact path="/restaurants" element={<Restaurants />} /> <Route exact path="/about" element={<About user={user} />} /> </Routes> </div> </Cont.Provider> ); }
MyReservations.js
import {useEffect, useState } from "react"; import ReservationCard from "./ReservationCard"; function MyReservations({user}){ // const[editMode,setEditForm]=useState(false); const[reservations,setReservations]=useState([]); useEffect(()=>{ fetch("/reservations") .then(res=>res.json()) .then(reservationData=>{ setReservations(reservationData) }) },[]) // console.log("reservations",reservations) function handleUpdateReservation(updatedReservation) { const updatedReservations = reservations.map((reservation) => { if (reservation.id === updatedReservation.id) { return updatedReservation; } else { return reservation; } }); setReservations(updatedReservations); } function handleCancel(reservationtodelete){ const newReservations=reservations.filter(r=>r.id !== reservationtodelete) setReservations(newReservations) } // const renderReservations=reservations.map((reservation)=>( // <ReservationCard key={reservation.id} reservation={reservation} handleCancel={handleCancel} /> // )) const renderReservations=reservations.map((reservation)=>( <ReservationCard key={reservation.id} reservation={reservation} handleCancel={handleCancel} onUpdateReservation={handleUpdateReservation} /> )) return( <> {renderReservations} </> ) } export default MyReservations;
ReservationCard.js
function ReservationCard({reservation,handleCancel,onUpdateReservation}){ const{ name, date, time, num, contact, occasion}=reservation; const [isEditing, setIsEditing] = useState(false); const handleReservationUpdate = (updatedReservation) => { setIsEditing(false); onUpdateReservation(updatedReservation); }; function handleDeleteClick() { fetch(`/reservations/${reservation.id}`, { method: "DELETE", }) handleCancel(reservation.id) } return( <> {isEditing ?( <EditReservationForm reservation={reservation} onUpdateReservation={handleReservationUpdate} />) :( <div> <h2>{name}</h2> <h2>{date}</h2> <h2>{time}</h2> <h2>{num}</h2> <h2>{contact}</h2> <h2>{occasion}</h2> <h3>For-{reservation.restaurant.name}</h3> <button onClick={() => setIsEditing((isEditing) => !isEditing)}> Edit </button> <button onClick={handleDeleteClick}>Cancel Booking</button> </div> )} </> ) } export default ReservationCard;
EditReservationForm.js
function EditReservationForm({reservation,onUpdateReservation}){ const{ name, date, time, num, contact, occasion}=reservation; const[updateName,setUpdatedName]=useState(name); const[updateDate,setUpdatedDate]=useState(date); const[updateTime,setUpdatedTime]=useState(time); const[updateNum,setUpdatedNum]=useState(num); const[updateContact,setUpdatedContact]=useState(contact); const[updateOccasion,setUpdatedOccasion]=useState(occasion); function handleEditClick() { fetch(`/reservation/${reservation.id}`, { method: "PATCH", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ name:updateName,date:updateDate, time:updateTime,num:updateNum,contact:updateContact,occasion:updateOccasion}), }) .then((r) => r.json()) .then((updatedReservation) => { onUpdateReservation(updatedReservation); }); } return( <> <h2>Modify Reservation</h2> <form onSubmit={handleEditClick} > <div > <label htmlFor="name" >Name</label> <input type="text" name="name" value={updateName} onChange={(e) => setUpdatedName(e.target.value)} placeholder="name" /> </div> <div > <label htmlFor="date" >Date</label> <input type="date" name="date" value={updateDate} onChange={(e) => setUpdatedDate(e.target.value)} placeholder="date" /> </div> <div > <label htmlFor="time" >Time</label> <input type="time" name="time" value={updateTime} onChange={(e) => setUpdatedTime(e.target.value)} placeholder="time" /> </div> <div > <label htmlFor="num" >Num</label> <input type="number" name="num" value={updateNum} onChange={(e) => setUpdatedNum(e.target.value)} placeholder="num" /> </div> <div > <label htmlFor="date" >Contact</label> <input type="tel" name="contact" value={updateContact} onChange={(e) => setUpdatedContact(e.target.value)} placeholder="contact" /> </div> <div > <label htmlFor="occasion" >Occasion</label> <input type="text" name="occasion" value={updateOccasion} onChange={(e) => setUpdatedOccasion(e.target.value)} placeholder="occasion" /> </div> <button type="submit">Update Reservation</button> </form> </> ) } export default EditReservationForm;
解决方案
问题根源是前端EditReservationForm组件中的PATCH请求路径错误:当前使用单数形式/reservation/${reservation.id},而Rails路由配置的是复数资源reservations,正确路径应为复数形式/reservations/${reservation.id}。修改该路径后,路由错误即可解决。
内容的提问来源于stack exchange,提问作者codertraveller
相关产品推荐
相关产品推荐

