React on Rails提交表单报错ActionController::ParameterMissing(asset参数缺失)
React on Rails表单提交报错:ActionController::ParameterMissing
提交Asset组件表单时,Rails服务器返回以下错误:
ActionController::ParameterMissing (param is missing or the value is empty: asset): app/controllers/api/assets_controller.rb:48:in `asset_params' app/controllers/api/assets_controller.rb:14:in `create'
同时填写表单时state显示undefined,提交时网络负载异常。尝试过在模型和控制器添加:id、修改表单name属性为asset[img]等方法,均未解决。
相关代码
routes.rb
Rails.application.routes.draw do mount_devise_token_auth_for 'User', at: 'api/auth' # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Defines the root path route ("/") # root "articles#index" namespace :api do resources :areas do resources :assets end end end
Asset模型
class Asset < ApplicationRecord belongs_to :area validates :name, :description, :barcode, :price, :pdate, :status, :img, :category, presence: true end
Assets控制器
class Api::AssetsController < ApplicationController before_action :set_area before_action :set_asset, only: [:show, :update, :destroy] def index render json: @area.assets end def show render json: @asset end def create @asset = @area.assets.new(asset_params) if @asset.save render json: @asset else errKey = @asset.errors.messages.keys[0].to_s errValue = @asset.errors.messages.values[0][0] render json: {errors: "#{errKey} #{errValue}"}, status: :unprocessable_entity end end def update if @asset.update(asset_params) render json:@asset else render json: {errors: @asset.errors }, status: :unprocessable_entity end end def destroy @asset.destroy render json: {message: "Asset Deleted"} end private def set_area @area = Area.find(params[:area_id]) end def set_asset @asset = @area.assets.find(params[:id]) end def asset_params params.require(:asset).permit(:name, :description, :barcode, :price, :pdate, :status, :img, :category) # params.fetch(:asset).permit(:name :description, :barcode, :price, :pdate, :status, :img, :category) end end
注:使用params.fetch仍出现相同错误
AssetProvider.js
import React, {useState } from 'react'; import axios from 'axios'; import {useNavigate} from 'react-router-dom'; export const AssetContext = React.createContext(); export const AssetConsumer = AssetContext.Consumer; const AssetProvider = ({ children }) => { const [assets, setAssets] = useState([]) const [errors, setErrors] = useState([]) const getAllAssets = (areaId) => { axios.get(`/api/areas/${areaId}/assets`) .then (res => setAssets(res.data)) .catch(err => { setErrors({ variant: 'danger', msg: err.response.data.errors.full_messages[0] }) }) } const addAsset = (areaId, asset) => { axios.post(`/api/areas/${areaId}/assets`, { asset }) .then ( res => setAssets([...assets, res.data])) .catch(err => { setErrors({ variant: 'danger', msg: Object.keys(err.response.data.errors)[0] + " " + Object.values(err.response.data.errors)[0][0] }) }) } const updateAsset = (areaId, id, asset) => { axios.put (`/api/areas/${areaId}/assets/${id}`, {asset}) .then ( res => { const newUpdatedAssets = assets.map(a => { if (a.id === id) { return res.data } return a }) setAssets(newUpdatedAssets) }) .catch(err => { setErrors({ variant: 'danger', msg: Object.keys(err.response.data.errors)[0] + " " + Object.values(err.response.data.errors)[0][0] }) }) } const deleteAsset = (areaId, id) => { axios.delete(`/api/areas/${areaId}/assets/${id}`) .then (res => { setAssets(assets.filter(a => a.id !== id)) }) .catch(err => { setErrors({ variant: 'danger', msg: err.response.data.errors[0] }) }) } return ( <AssetContext.Provider value={{ assets, errors, setErrors, getAllAssets, addAsset, updateAsset, deleteAsset, }}> {children} </AssetContext.Provider> ) } export default AssetProvider;
AssetForm.js
import { useState} from 'react' import {Form, Button} from 'react-bootstrap' import {AssetConsumer} from '../../providers/AssetProvider' import { useParams} from 'react-router-dom' const AssetForm = ({ setAdd, addAsset}) => { const [asset, setAsset] = useState ({ name: '', img: '', barcode: '', description: '', category: '', price: '', pdate: '', status: ''}) const { areaId } = useParams(); const handleSubmit = (e) => { e.preventDefault() addAsset(areaId, asset) setAdd(false) setAsset({ name: '', img: '', barcode: '', description: '', category: '', price: '', pdate: '', status: ''}) } return ( <> <Form onSubmit={handleSubmit}> <Form.Group className="mb-3" > <Form.Label>Asset Image</Form.Label> <Form.Control name='img' value={asset.img} onChange={(e) => setAsset({...asset, img: e.targetvalue})} /> </Form.Group> <Form.Group className="mb-3" > <Form.Label>Name</Form.Label> <Form.Control name='name' value={asset.name} onChange={(e) => setAsset({...asset, name: e.targetvalue})} autoFocus required /> </Form.Group> <Form.Group className="mb-3" > <Form.Label>Barcode</Form.Label> <Form.Control name='barcode' value={asset.barcode} onChange={(e) => setAsset({...asset, barcode: e.targetvalue})} /> </Form.Group> <Form.Group className="mb-3" > <Form.Label>Description</Form.Label> <Form.Control name='description' value={asset.description} onChange={(e) => setAsset({...asset, description: e.targetvalue})} as="textarea" rows={3} /> </Form.Group> <Form.Group className="mb-3" > <Form.Label>Category</Form.Label> <Form.Select name='category' value={asset.category} onChange={(e) => setAsset({...asset, category: e.targetvalue})} > <option>Select from list</option> <option value="camera">Camera</option> <option value="light">Light</option> <option value="tripod">Tripod</option> </Form.Select> </Form.Group> <Form.Group className="mb-3" > <Form.Label>Price</Form.Label> <Form.Control name='price' value={asset.price} onChange={(e) => setAsset({...asset, price: e.targetvalue})} /> </Form.Group> <Form.Group className="mb-3" > <Form.Label>Purchase Date</Form.Label> <Form.Control name='pdate' value={asset.pdate} onChange={(e) => setAsset({...asset, pdate: e.targetvalue})} /> </Form.Group> <Form.Group className="mb-3" > <Form.Label>Status</Form.Label> <Form.Control name='status' value={asset.status} onChange={(e) => setAsset({...asset, status: e.targetvalue})} /> </Form.Group> <Button variant="primary" type="submit"> Submit </Button> </Form> </> ) } const ConnectedAssetForm = (props) => ( <AssetConsumer> { value => <AssetForm {...value} {...props} />} </AssetConsumer> ) export default ConnectedAssetForm;
Schema.rb
create_table "areas", force: :cascade do |t| t.string "name" t.string "address" t.string "city" t.string "country" t.integer "zip" t.string "mcontact" t.string "pic" t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "assets", force: :cascade do |t| t.string "name" t.string "description" t.string "barcode" t.decimal "price" t.datetime "pdate" t.string "status" t.string "img" t.bigint "area_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "category" t.index ["area_id"], name: "index_assets_on_area_id" end
解决建议
修复React表单的state更新错误
所有表单输入的onChange事件里,你写的是e.targetvalue,正确的应该是e.target.value(注意是.不是连写)。这个错误会导致表单输入时state无法正确更新,始终保持初始空值,最终提交的数据是空的,触发Rails的参数缺失错误。示例修改:
onChange={(e) => setAsset({...asset, img: e.target.value})}把所有输入框的
e.targetvalue替换成e.target.value。确认请求数据格式
修复上述错误后,提交的asset对象应该会包含正确的字段值。此时axios.post发送的{ asset }会被序列化为{ "asset": { "name": "...", ... } },符合Rails控制器asset_params要求的嵌套参数格式。可选:添加请求头确保JSON格式
可以在axios请求中明确设置Content-Type为application/json,避免数据格式解析问题:axios.post(`/api/areas/${areaId}/assets`, { asset }, { headers: { 'Content-Type': 'application/json' } })
内容的提问来源于stack exchange,提问作者Daniel Putnam
相关产品推荐
相关产品推荐

