简体   繁体   English

如何使用来自 React 的 useReducer 挂钩从两个不同的处理程序更新 state

[英]How to update state from two different handlers using useReducer hooks from React

I'm using { useReducer } to manage my form's state. but I have two separate handlers我正在使用 { useReducer } 来管理我的表单的 state。但我有两个单独的处理程序

  1. handleChange() = for inputs changes (this one works as expected) handleChange() = 用于输入更改(这个按预期工作)

 const handleChange = async (e) => { dispatch({field: e.target.name, value: e.target.value}); }

  1. UploadFile() = for uploading images (this one doesn't update the images/state) UploadFile() = 用于上传图像(这个不更新图像/状态)

 const uploadFile = async (e) => { console.log('Uploading file...'); const files = e.target.files; const data = new FormData(); data.append('file', files[0]); data.append('upload_preset', 'artemis'); const res = await fetch(`https://api.cloudinary.com/v1_1/${cludinaryAccount}/image/upload`, { method: 'POST', body: data }); const file = await res.json(); console.log(file); dispatch({ image: file.secure_url, largeImage: file.eager[0].secure_url, }) }

I couldn't update the state of images from with UploadFile(), not sure what I'm doing wrong.我无法使用 UploadFile() 更新图像的 state,不确定我做错了什么。 Below is the entire complete code.下面是完整的完整代码。

 import React, { useState, useReducer } from 'react'; import { Mutation } from 'react-apollo'; import gql from 'graphql-tag'; import Router from 'next/router'; import Form from './styles/Form'; import formatMoney from '../lib/formatMoney'; import Error from './ErrorMessage'; const CREATE_LISTING_MUTATION = gql` mutation CREATE_LISTING_MUTATION( $title: String, $description: String, $address: String, $availableFor: String, $spaceType: String, $height: String, $accessType: String, $security: String, $features: String, $nearbyStablishments: String, $rules: String, $image: String, $largeImage: String, $price: Int, $bond: Int, $minBookingStay: Int, $size: String, ) { createListing( title: $title description: $description address: $address availableFor: $availableFor spaceType: $spaceType height: $height accessType: $accessType security: $security features: $features nearbyStablishments: $nearbyStablishments rules: $rules image: $image largeImage: $largeImage price: $price bond: $bond minBookingStay: $minBookingStay size: $size ) { id } } `; const initialState = { title: '', description: '', address: '', availableFor: '', spaceType: '', height: '', accessType: '', security: '', features: '', nearbyStablishments: '', rules: '', image: '', largeImage: '', price: 0, bond: 0, minBookingStay: 0, size: '' }; function reducer(state, {field, value}) { return {...state, [field]: value } } export const CreateListing = () => { const [state, dispatch] = useReducer(reducer,initialState); const handleChange = async (e) => { dispatch({field: e.target.name, value: e.target.value}); } const { title, description, address, availableFor, spaceType, height, accessType, security, features, nearbyStablishments, rules, image, largeImage, price, bond, minBookingStay, size } = state; const uploadFile = async (e) => { console.log('Uploading file...'); const files = e.target.files; const data = new FormData(); data.append('file', files[0]); data.append('upload_preset', 'artemis'); const res = await fetch(`https://api.cloudinary.com/v1_1/${cludinaryAccount}/image/upload`, { method: 'POST', body: data }); const file = await res.json(); console.log(file); dispatch({ image: file.secure_url, largeImage: file.eager[0].secure_url, }) } return ( <Mutation mutation={CREATE_LISTING_MUTATION} variables={state}> { /* 1. Expose createListing function 2. Expose the error and loading state */ } {(createListing, {error, loading, called}) => { // Possible params: error, loading, called, data, info return ( <Form data-test="form" onSubmit={ async (e) => { // Stop the form from submitting e.preventDefault(); // Call the mutation const res = await createListing(); // Change them to the single Listing page console.log(res); Router.push({ pathname: '/listing', query: { id: res.data.createListing.id } }); }}> <h2>Lease my Space</h2> <Error error={error}/> {/* area-busy attribute is needed for our loading animation*/ } <fieldset disabled={loading} aria-busy={loading}> <label htmlFor="file"> Image <input type="file" id = "file" name = "file" placeholder = "Upload an image" required onChange={uploadFile} /> {image && <img src={image} alt="Image Preview"/> } </label> <label htmlFor="title"> Title <input type="text" id = "title" name = "title" placeholder = "title" required value = {title} onChange={handleChange} /> </label> <label htmlFor="description"> Description <input type="text" id = "description" name = "description" placeholder = "Description" required value = {description} onChange={handleChange} /> </label> <label htmlFor="address"> Address <input type="text" id = "address" name = "address" placeholder = "address" required value = {address} onChange={handleChange} /> </label> <label htmlFor="availableFor"> Available For <input type="text" id = "availableFor" name = "availableFor" placeholder = "Available For" required value = {availableFor} onChange={handleChange} /> </label> <label htmlFor="spaceType"> Space Type <input type="text" id = "spaceType" name = "spaceType" placeholder = "Space Type" required value = {spaceType} onChange={handleChange} /> </label> <label htmlFor="height"> Height <input type="text" id = "height" name = "height" placeholder = "Height" required value = {height} onChange={handleChange} /> </label> <label htmlFor="accessType"> Access Type <input type="text" id = "accessType" name = "accessType" placeholder = "Access Type" required value = {accessType} onChange={handleChange} /> </label> <label htmlFor="security"> Security <input type="text" id = "security" name = "security" placeholder = "Security" required value = {security} onChange={handleChange} /> </label> <label htmlFor="features"> Features <input type="text" id = "features" name = "features" placeholder = "Features" required value = {features} onChange={handleChange} /> </label> <label htmlFor="nearbyStablishments"> Nearby Stablishments <input type="text" id = "nearbyStablishments" name = "nearbyStablishments" placeholder = "Nearby Stablishments" required value = {nearbyStablishments} onChange={handleChange} /> </label> <label htmlFor="rules"> Rules <input type="text" id = "rules" name = "rules" placeholder = "Rules" required value = {rules} onChange={handleChange} /> </label> <label htmlFor="image"> Image <input type="text" id = "image" name = "image" placeholder = "Image" required value = {image} onChange={handleChange} /> </label> <label htmlFor="largeImage"> Large Image <input type="text" id = "largeImage" name = "largeImage" placeholder = "Large Image" required value = {largeImage} onChange={handleChange} /> </label> <label htmlFor="price"> Price <input type="number" id = "price" name = "price" placeholder = "Price" required value = {price} onChange={handleChange} /> </label> <label htmlFor="bond"> Bond <input type="number" id = "bond" name = "bond" placeholder = "Bond" required value = {bond} onChange={handleChange} /> </label> <label htmlFor="minBookingStay"> Min Booking stay <input type="number" id = "minBookingStay" name = "minBookingStay" placeholder = "size" required value = {minBookingStay} onChange={handleChange} /> </label> <label htmlFor="size"> size <input type="text" id = "size" name = "size" placeholder = "size" required value = {size} onChange={handleChange} /> </label> <button type="submit"> Submit</button> </fieldset> </Form> ) }} </Mutation> ) } export default CreateListing; export {CREATE_LISTING_MUTATION};

You are not using the useReducer API correctly.您没有正确使用useReducer API。 You use reducers with actions to determine how the state should be updated based off of the dispatched action.您将 reducer 与操作一起使用,以确定应如何根据分派的操作更新 state。 A basic example can be seen in the useReducer docs .可以在useReducer文档中看到一个基本示例。

The reason your reducer isn't working is because it will only every work with an object that has a field and a value property:您的 reducer 不工作的原因是因为它只能与具有fieldvalue属性的 object 一起工作:

function reducer(state, {field, value}) {
  //                    ^^^^^^^^^^^^^^ needs to be object with field and value props
  return {
    ...state,
    [field]: value
  }
}

Your handleChange function dispatches an object with those properties.您的handleChange function 使用这些属性调度 object。 uploadFile dispatches an object without these two properties. uploadFile在没有这两个属性的情况下调度 object。 In fact, your current reducer will only ever be able to update one key/value pair at a time.事实上,您当前的 reducer 一次只能更新一个键/值对。 A quick fix would be to change the dispatch in uploadFile to be:一个快速的解决方法是将uploadFile中的调度更改为:

dispatch({ field: 'image', value: file.secure_url });
dispatch({ field: 'largeImage', value: file.eager[0].secure_url });

That will tell your reducer to update those fields with those values.这将告诉您的减速器使用这些值更新这些字段。 But this is not the proper usage of useReducer .但这不是useReducer的正确用法。

It looks like what you are doing can just be converted to use useState , since as I understand from your reducer function, you are just trying to merge two objects into a new state object. The React docs recommend splitting state out into seperate pieces that often change together, but for simplicity's sake, we'll just stick with one large object.看起来您正在做的事情可以转换为使用useState ,因为据我从您的减速器 function 了解到,您只是试图将两个对象合并到一个新的 state object 中。React 文档建议将 state 拆分成单独的部分,这通常一起改变,但为了简单起见,我们将坚持使用一个大的 object。

In CreateListing we want to use useState :CreateListing中,我们要使用useState

const [state, setState] = useState(initialState);

Then in handleChange :然后在handleChange中:

const handleChange = (e) => {
  setState((oldState) => ({ ...oldState, [e.target.name]: e.target.value }));
};

And likewise in uploadFile :同样在uploadFile中:

setState((oldState) => ({
  ...oldState,
  image: file.secure_url,
  largeImage: file.eager[0].secure_url,
}));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM