I'm using { useReducer } to manage my form's state. but I have two separate handlers
const handleChange = async (e) => {
dispatch({field: e.target.name, value: e.target.value});
}
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. 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. You use reducers with actions to determine how the state should be updated based off of the dispatched action. A basic example can be seen in the useReducer
docs.
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:
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. uploadFile
dispatches an object without these two properties. In fact, your current reducer will only ever be able to update one key/value pair at a time. A quick fix would be to change the dispatch in uploadFile
to be:
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
.
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.
In CreateListing
we want to use useState
:
const [state, setState] = useState(initialState);
Then in handleChange
:
const handleChange = (e) => {
setState((oldState) => ({ ...oldState, [e.target.name]: e.target.value }));
};
And likewise in uploadFile
:
setState((oldState) => ({
...oldState,
image: file.secure_url,
largeImage: file.eager[0].secure_url,
}));