Как обновить состояние из двух разных обработчиков с помощью хуков useReducer из React

Я использую {useReducer} для управления состоянием моей формы. но у меня два отдельных обработчика

  1. handleChange () = для изменений входных данных (работает как ожидалось)

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

  1. 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,
    })
  }

Я не мог обновить состояние изображений с помощью UploadFile (), не уверен, что делаю не так. Ниже представлен полный код.

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};


person GIlbert Lucas    schedule 17.05.2020    source источник


Ответы (1)


Вы неправильно используете useReducer API. Вы используете редукторы с действиями, чтобы определить, как следует обновлять состояние на основе отправленного действия. Базовый пример можно увидеть в useReducer docs.

Причина, по которой ваш редуктор не работает, заключается в том, что он будет работать только с объектом, имеющим свойство field и value:

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

Ваша handleChange функция отправляет объект с этими свойствами. uploadFile отправляет объект без этих двух свойств. Фактически, ваш текущий редуктор сможет обновлять только одну пару ключ / значение за раз. Быстрое решение - изменить отправку в uploadFile на:

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

Это скажет вашему редуктору обновить эти поля этими значениями. Но это неправильное использование useReducer.

Похоже, что то, что вы делаете, можно просто преобразовать для использования useState, поскольку, как я понимаю из вашей функции редуктора, вы просто пытаетесь объединить два объекта в новый объект состояния. React docs Рекомендуем разбивать состояние на отдельные части, которые часто меняются вместе, но для простоты мы будем придерживаться одного большого объекта.

В CreateListing мы хотим использовать useState:

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

Затем в handleChange:

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

И так же в uploadFile:

setState((oldState) => ({
  ...oldState,
  image: file.secure_url,
  largeImage: file.eager[0].secure_url,
}));
person Andrew Dibble    schedule 17.05.2020
comment
Спасибо за ваше время, теперь я понимаю это - person GIlbert Lucas; 17.05.2020