How to create a add image input in formik - reactjs

I want to create a customized input field in formik for an image upload. I want to pass binary file data to backend, but it is taking the file location. Can you help me?
Here is the boilerplate code:
import React from 'react';
import { useField } from 'formik';
import { Form, Col, Row } from 'react-bootstrap';
const FileInput = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<>
<Form.Group as={Row} controlId="">
<Form.Label htmlFor={props.id || props.name} column sm={3}>
{label}
</Form.Label>
{props.initalImage ? (
<img height="35px" alt={props.initalImage} src={props.initalImage} />
) : null}
<Col sm={4}>
<Form.Control
{...field}
{...props}
accept={props.accept}
isValid={meta.touched && !meta.error}
isInvalid={Boolean(meta.touched && meta.error)}
/>
{meta.error ? (
<>
<Form.Control.Feedback type="invalid">
{meta.error}
</Form.Control.Feedback>
</>
) : null}
<span className="form-text text-muted mt-3">
Please choose an icon which is min. 256x256px.
</span>
</Col>
</Form.Group>
</>
);
};
export { FileInput };
and here is where I am calling it:
<FileInput
label="Light Icon:"
type="file"
name="lightIcon"
accept="image/x-png"
onBlur
/>
I tried this but it is not working:
<FileInput
label="Light Icon:"
type="file"
name="lightIcon"
accept="image/x-png"
onBlur
onChange={(e) => {
setFieldValue('lightIcon', e.target.files[0]);
}}
/>

The trick with <input> elements with type="file" is that we shouldn't set their value manually, it actually uses it's own mechanism along with browser's security implementations and set it itself on the client's machine.
So we don't have access to the real path of the file on the disk, instead we are able to use a FileReader object to asynchronously read contents of the file as blob of buffer and pass it to our parent component.
There are cases where you can change file input's value
programmatically, like to null to reset your input.
The following example implements an UploadFiled component with type="file" get wrapped with a custom Field component, whenever child component getting update, a file is being uploaded, and parent component will knows and starts reading the contents of the specified Blob, once finished, the result attribute contains a data url representing the file's data will set as it's value. It currently accepts an image base on the validation that is set for it using Yup.
The traditional application/json type won’t help in uploading the image to your server, the FormData will. You need to write your handleSubmit() using the form data and pass the values handled by Formik.
Check my CodeSandbox for the working demo.
const handleSubmit= () => {
// Create an object of formData
const formData = new FormData();
// Update the formData object
formData.append("myFile", file, file.name);
// Details of the uploaded file
console.log(file);
// Request made to the backend api
// Send formData object
axios.post("api/uploadfile", formData);
};
// UploadForm.jsx
import React, { useState, useEffect } from "react";
import { Field, useField } from "formik";
import { Grid, FormHelperText } from "#material-ui/core";
import UploadField from "../../FormFields/UploadField";
import Thumb from "../Helper/Thumb";
const ImageForm = (props) => {
const {
formField: { image }
} = props;
const [field, meta, helper] = useField(image.name);
const { touched, error } = meta;
const { setValue } = helper;
const isError = touched && error && true;
const { value } = field;
const [fileName, setFileName] = useState(value.name);
const [file, setFile] = useState(value.file);
const [src, setSrc] = useState(value.src);
const _onChange = (e) => {
let reader = new FileReader();
let file = e.target.files[0];
if (file) {
reader.onloadend = () => setFileName(file.name);
if (file.name !== fileName) {
reader.readAsDataURL(file);
setSrc(reader);
setFile(file);
}
}
};
useEffect(() => {
if (file && fileName && src) {
setValue({ file: file, src: src, name: fileName });
console.log(fileName);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [src, fileName, file]);
return (
<React.Fragment>
<Grid container spacing={3} justify="center" alignItems="center">
<Grid item xs={12}>
<label>
{image.label}
</label>
<br />
<div
style={{
display: "flex",
justifyContent: "flex-start",
fontSize: "1.2em"
}}
>
<Field
variant="outlined"
field={field}
component={UploadField}
onChange={_onChange}
isError={isError}
/>
{isError && <FormHelperText color={"red"}>{error}</FormHelperText>}
</div>
</Grid>
<Grid item>{file && src && <Thumb file={file} src={src}></Thumb>}</Grid>
</Grid>
</React.Fragment>
);
};
export default ImageForm;
import React from "react";
import { Field } from "formik";
const UploadField = ({
field,
form: { touched, errors },
name,
label,
isError,
...props
}) => {
return (
<>
<Field
variant="outlined"
name="uploader"
title={label}
type={"file"}
{...props}
/>
</>
);
};
export default UploadField;

Related

How to restrict numbers (even in String type) from MaterialUI TextField?

Consider the following code:
<Grid item lg={6} md={6} xs={12} >
<TextField fullWidth type={'text'} label="Product Color" value={productColor} onChange={(e) => setProductColor(e.target.value)} />
</Grid>
Even if it is set to type text, how do I completely prevent numbers from being entered and only allow text from A-z or a-z? Any Ideas?
Use regex like so:
import { Stack, TextField } from "#mui/material";
import { useState } from "react";
const isLetters = (str) => /^[A-Za-z]*$/.test(str);
export default function IndexPage() {
const [val, setVal] = useState("");
const onInputChange = (e) => {
const { value } = e.target;
if (isLetters(value)) {
setVal(value);
}
};
return (
<Stack spacing={2} padding={2} height={"100vh"}>
<TextField label="Letters only" value={val} onChange={onInputChange} />
</Stack>
);
}
One way would be to to utilise the fact that you can pass in certain props to text fields and combine this with regex. This wouldn't necessarily prevent them from allowing it to be entered, however, it will prevent submission of the text field if it contains any numbers. You would then just need to add error handling to display the associated message
<TextField
inputProps={{ pattern: "([^0-9]*)" }} error={'Error Message'}
/>
Check out the textfield api here for further details
Instead of re-inventing the wheel you could use something like react-number-format.
They have an example in their docs:
<NumberFormat customInput={TextField} format="#### #### #### ####" />
It is also possible to do it the other way around, giving more control over the TextField but needs a bit more work.
import React from "react";
import { InputBaseComponentProps } from "#mui/material";
import NumberFormat from "react-number-format";
export const NumberInput = React.forwardRef<
NumberFormat,
InputBaseComponentProps
>((props, ref) => {
const { onChange, ...other } = props;
return (
<NumberFormat
isNumericString
{...(other as any)}
getInputRef={ref}
onValueChange={(values) =>
onChange && onChange({ target: { value: values.value } } as any)
}
/>
);
});
Then use it like this, note that the price value is still a string.
const [price, setPrice] = useState("0");
return <TextField
value={price}
onChange={(evt) => setPrice(evt.target.value) }
InputProps={{
inputComponent: NumberInput,
inputProps: {
decimalSeparator: ",",
decimalScale: 2,
fixedDecimalScale: true,
allowNegative: false,
prefix: "€ ",
},
}}
This gives you something like this:

React hooks form onChange misbehave after error validation occured

In my simple crud application, when I try to add a new author with an invalid name format and try to submit form display an error and after that when I have press backspace twice to erase last letter in textbox.
Here is my AuthorForm.tsx
import React, {useEffect, useState} from 'react';
import {Row, Col, Form, Button} from 'react-bootstrap';
import {IAuthor} from "../../assets/types/LibraryTypes";
import {XCircle} from "react-feather";
import {useForm} from "react-hook-form";
interface IFormInputs {
authorName: string
}
type CreateFormProps = {
onClose: () => void,
onAuthorAdded: (author:IAuthor)=>void,
onAuthorToUpdate: IAuthor | null,
updateAuthorIndex : number | null,
onAuthorUpdated : (updatedAuthor:IAuthor, index:number)=>void,
}
const AuthorForm:React.FC<CreateFormProps> = (props) =>{
const [authorName, setAuthorName] = useState<string|null>(null);
const { register, errors, handleSubmit } = useForm<IFormInputs>({mode:'onTouched'});
useEffect(()=>{
if(!props.onAuthorToUpdate){
setAuthorName(null);
return;
}
setAuthorName(props.onAuthorToUpdate.name);
},[props.onAuthorToUpdate]);
const handleOnNameChange = (event:React.ChangeEvent<HTMLInputElement>)=>{
event.preventDefault();
setAuthorName(event.target.value);
}
const handleOnCreate = () =>{
if(!authorName){
return;
}
if(props.onAuthorToUpdate && props.updateAuthorIndex !== null){
props.onAuthorUpdated({...props.onAuthorToUpdate,name:authorName},props.updateAuthorIndex);
setAuthorName(null);
return;
}
const newAuthor: IAuthor = {name:authorName};
props.onAuthorAdded(newAuthor);
setAuthorName(null);
};
return(
<Col className='p-0' sm={10}>
<Row className=' pb-1 mb-3 mx-1'>
<Col xs={10}>
<span className='add-book-title pt-2'>
{!props.onAuthorToUpdate && 'Create Author'}
{props.onAuthorToUpdate && 'Update Author'}
</span>
</Col>
<Col className='closeBtn text-right p-0' xs={2}>
<XCircle color='#363636' className='mt-2 mr-3' onClick={props.onClose}/>
</Col>
</Row>
<Form className='mx-4' onSubmit={handleSubmit(handleOnCreate)}>
<Form.Group>
<Form.Row>
<Form.Label column="sm" xs={6} className='label'>
Name of the Author
</Form.Label>
<Col xs={6} className='warning text-right mt-2 pr-2'>
{errors.authorName?.type === "required" && (
<p>This field is required</p>
)}
{errors.authorName?.type === "maxLength" && (
<p>Author Name name cannot exceed 50 characters</p>
)}
{errors.authorName?.type === "pattern" && (
<p>Invalid Author Name</p>
)}
</Col>
<Col sm={12}>
<Form.Control size={"sm"}
name="authorName"
ref={register({
required: true,
maxLength: 50,
pattern: /^[a-zA-Z\s]+$/
})}
onChange={
(event:React.ChangeEvent<HTMLInputElement>)=>
handleOnNameChange(event)
}
value={authorName?authorName:''}
/>
</Col>
</Form.Row>
</Form.Group>
<Col className='text-right mb-3 p-0' xs={12}>
<Button type={"submit"} variant={"primary"} size={"sm"} className={"px-3 pt-1"}>
{!props.onAuthorToUpdate && 'Create'}
{props.onAuthorToUpdate && 'Update'}
</Button>
</Col>
</Form>
</Col>
)
};
export default AuthorForm;
And this is AuthorList.tsx
import React, {useEffect, useState} from 'react';
import {Container} from 'react-bootstrap';
import AuthorAddedList from "./AuthorAddedList";
import AuthorForm from "./AuthorForm";
import AuthorWelcome from "./AuthorWelcome";
import CreateAuthor from "./CreateAuthor";
import {IAuthor} from "../../assets/types/LibraryTypes";
const AuthorList:React.FC = () =>{
const initAuthors: IAuthor[] = [];
const [authors, setAuthors] = useState<IAuthor[]>(initAuthors);
const [isFormVisible, setIsFormVisible] = useState<boolean>(false);
const [authorToUpdate, setAuthorToUpdate] = useState<IAuthor | null>(null);
const [updateAuthorIndex, setUpdateAuthorIndex] = useState<number| null>(null)
useEffect(()=>{
if(!authorToUpdate){
return;
}
setIsFormVisible(true);
},[authorToUpdate]);
const handleOnCreateClick = () => {
setIsFormVisible(true);
setAuthorToUpdate(null);
};
const handleOnFormClosed = () => {
setIsFormVisible(false);
}
const handleAuthorAdded = (newAuthor: IAuthor) => {
const allAuthors: IAuthor[] = authors.slice();
allAuthors.push(newAuthor)
setAuthors(allAuthors);
};
const handleAuthorDeleted = (index: number) => {
const allAuthors: IAuthor[] = authors.slice();
allAuthors.splice(index, 1);
setAuthors(allAuthors);
}
const handleOnUpdateRequest = (index: number) => {
setAuthorToUpdate(authors[index]);
setUpdateAuthorIndex(index);
setIsFormVisible(true);
}
const handleOnAuthorUpdated = (updatedAuthor: IAuthor, index:number) =>{
const allAuthors : IAuthor [] = authors.slice();
allAuthors.splice(index,1, updatedAuthor);
setAuthors(allAuthors)
}
return (
<Container fluid={true} className={"authors"}>
<AuthorWelcome/>
<AuthorAddedList authors={authors} onDeleted={handleAuthorDeleted} onUpdateRequested={handleOnUpdateRequest} />
<CreateAuthor onClickCreate={handleOnCreateClick}/>
{isFormVisible &&
<AuthorForm onClose={handleOnFormClosed} onAuthorAdded={handleAuthorAdded} onAuthorToUpdate={authorToUpdate} onAuthorUpdated={handleOnAuthorUpdated} updateAuthorIndex={updateAuthorIndex}/>}
</Container>
)
}
export default AuthorList;
Here is the sandbox link to my full code Click Here
to demonstrate the error,
go to the sandbox link
click add author button in webapp
enter an invalid name like 'john97'
then submit the form
then try to clear the name using backspace.
now you can see to erase last letter 'j' you have to press backspace twice
please help me to solve this issue
thank you
I think using Controller component of react-hook-form is better at handling controlled components, you also don't have to set onChange event and make your code much cleaner.
Using this in AuthorForm.tsx seems to make your weird bug fixed.
type FormData = {
authorName: string;
}
//some codes...
const { register, handleSubmit, control, errors, setValue, reset } = useForm<FormData>();
//some codes...
const handleOnCreate = (data: FormData) => {
if (!data?.authorName) {
return;
}
if (props.onAuthorToUpdate && props.updateAuthorIndex !== null) {
props.onAuthorUpdated(
{ ...props.onAuthorToUpdate, name: data.authorName },
props.updateAuthorIndex
);
reset({ authorName: "" }); // or setValue("authorName", "");
return;
}
const newAuthor: IAuthor = { name: data.authorName };
props.onAuthorAdded(newAuthor);
reset({ authorName: "" }); // or setValue("authorName", "");
};
//some codes...
<Controller
control={control}
name={"authorName"}
as={<Form.Control size={"sm"} />}
defaultValue=""
rules={{
required: true,
maxLength: 50,
pattern: /^[A-Za-z ]+$/i
}}
/>
Here is the sandbox.
the problem is in the AuthorForm.tsx file, in this line:
<Form className='mx-4' onSubmit={handleSubmit(handleOnCreate)}>
the onSubmit shouldn't accept an invoked function handleSubmit(handleOnCreate),
it should be changed to onSubmit={() => handleSubmit(handleOnCreate)}

ReactJS: How to handle Image / File upload with Formik?

I am designing a profile page for my site using ReactJS.
Now my question is how do I upload the image from local machine and save it to the database and also displaying it in the profile page
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { AccountAction } from '../../actions/user/AccountPg1Action';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
class AccountInfo extends Component {
constructor(props) {
super(props)
this.state = {
currentStep: 1,
userAccountData: {
userid: '',
useravtar: '',
attachement_id: '',
}
}
}
handleFileUpload = (event) => {
this.setState({useravtar: event.currentTarget.files[0]})
};
handleChange = event => {
const {name, value} = event.target
this.setState({
[name]: value
})
}
handleSubmit = event => {
let that = this;
const { AccountAction } = that.props;
event.preventDefault();
let accountInputs = {
userid: 49,
useravtar: that.state.image,
attachement_id: 478,
}
that.setState({
userAccountData: accountInputs,
})
AccountAction(accountInputs)
}
AccountInfoView = () => {
console.log(this.state.useravtar)
return (
<section id="account_sec" className="second_form">
<div className="container">
<React.Fragment>
<Formik
initialValues={‌{
file: null,
email: '',
phone: ''
}}
validationSchema={accountInfoSchema}
render={(values) => {
return(
<Form onSubmit={this.handleSubmit}>
<Step1
currentStep={this.state.currentStep}
handleChange={this.handleChange}
file= {this.state.useravtar}
handleFileUpload={this.handleFileUpload}
/>
</Form>
);
}}
/>
</React.Fragment>
)
}
render() {
return (
<div>{this.authView()}</div>
)
}
}
function Step1(props) {
console.log(props.useravtar)
if (props.currentStep !== 1) {
return null
}
return(
<div className="upload">
<label htmlFor="profile">
<div className="imgbox">
<img src="images/trans_116X116.png" alt="" />
<img src={props.useravtar} className="absoImg" alt="" />
</div>
</label>
<input id="file" name="file" type="file" accept="image/*" onChange={props.handleFileUpload}/>
<span className="guide_leb">Add your avatar</span>
</div>
)
}
When I do console in handleChange action for event.target.file[0] it responds with undefined.
Also, doing a console.log(this.state.useravtar) in handleSubmit action it shows a pathname like c:/fakepath/imgname.jpg
P.S: I have a multiple forms so I am using it in a Step wise. And i am using Redux Reducer for storing the data.
I have referred this link but my requirement is not looking like this.
Formik doesnot support fileupload by default, But you can try the following
<input id="file" name="file" type="file" onChange={(event) => {
setFieldValue("file", event.currentTarget.files[0]);
}} />
Here "file" represents the key that you are using for holding the file
And on submit you can get the filename, size etc for the file by using
onSubmit={(values) => {
console.log({
fileName: values.file.name,
type: values.file.type,
size: `${values.file.size} bytes`
})
If you want to set the file into components state then you can use
onChange={(event) => {
this.setState({"file": event.currentTarget.files[0]})};
}}
According to your code, you have to handle file upload as below
In AccountInfo add a function to handle file upload
handleFileUpload = (event) => {
this.setState({WAHTEVETKEYYOUNEED: event.currentTarget.files[0]})};
}
And pass the same function to Step1 Component as below
<Step1
currentStep={this.state.currentStep}
handleChange={this.handleChange}
file= {this.state.image}
handleFileUpload={this.handleFileUpload}
/>
In Step1 Component where you upload the file, Change the input as
<input id="file" name="file" type="file" accept="image/*" onChange={props.handleFileUpload}/>
If you need to preview the uploaded image then you can create a blob and pass the same as source for image as below
<img src={URL.createObjectURL(FILE_OBJECT)} />
EDIT-1
As URL.createObjectURL method is deprecated due to security issues, we need to use srcObject for Media Elements, to use that you can use ref to assign srcObject, for example
Assuming you are using class Components,
Constructor
in constructor you can use
constructor(props) {
super(props)
this.imageElRef = React.createRef(null)
}
HANDLE CHANGE FUNCTION
handleFileUpload = (event) => {
let reader = new FileReader();
let file = event.target.files[0];
reader.onloadend = () => {
this.setState({
file: reader.result
});
};
reader.readAsDataURL(file);
}
Element
<img src={this.state.file} />
Here is how I resolved it with Formik and Material UI
in your JS file, just declare a variable avatarPreview like below
const [avatarPreview, setAvatarPreview] = useState('/avatars/default.png');
<Box
display='flex'
textAlign='center'
justifyContent='center'
flexDirection='column'>
<ImageAvatar size='md' src={avatarPreview || user?.avatar} />
<Button
variant='contained'
component='label'
startIcon={<CloudUploadIcon />}>
Choose Avatar
<input
name='avatar'
accept='image/*'
id='contained-button-file'
type='file'
hidden
onChange={(e) => {
const fileReader = new FileReader();
fileReader.onload = () => {
if (fileReader.readyState === 2) {
setFieldValue('avatar', fileReader.result);
setAvatarPreview(fileReader.result);
}
};
fileReader.readAsDataURL(e.target.files[0]);
}}
/>
</Button>
</Box>
Default Preview:
After choosing avatar:
You can upload single or multiple files with validation using Formik as follows:
import "./App.css";
import { useEffect, useState } from "react";
import * as Yup from "yup";
import { Formik, Field, Form, ErrorMessage, useField } from "formik";
import axios from "axios";
function App() {
return (
<Formik
initialValues={{
profile: [],
}}
validationSchema={Yup.object({
profile:Yup.array().min(1,"select at least 1 file")
})}
onSubmit={(values, props) => {
let data = new FormData();
values.profile.forEach((photo, index) => {
data.append(`photo${index}`, values.profile[index]);
});
axios
.post("you_api_for_file_upload", data, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
console.log(response);
})
.catch((err) => {
console.log(err);
});
}}
>
{(formik) => {
return (
<>
<Form>
<input
id="file"
name="profile"
type="file"
onChange={(event) => {
const files = event.target.files;
let myFiles =Array.from(files);
formik.setFieldValue("profile", myFiles);
}}
multiple
/>
<ErrorMessage name="profile"/>
<button type="submit" disabled={formik.isSubmitting}>
Submit
</button>
</Form>
</>
);
}}
</Formik>
);
}
export default App;
Note: you can customize min(your choice, "your message") as per your need.
validationSchema={Yup.object({
profile:Yup.array().min(1,"select at least 1 file")
})}
FormIk does not support file uploading and we need to do it in custom way.
Just trigger onChange event and set the file.
If you face any error of setFieldValue in TypeScript, then you simply do this:
onChange={(event) => {
if (event.currentTarget.files) {
formik.setFieldValue(
"file",
event.currentTarget.files[0]
);
}
To handle file in formik with backend all you need to do add the input given below (you can change avatar to anything you want):
<input
type="file"
name="avatar"
onChange={(event) => {
setFieldValue('avatar', event.currentTarget.files[0]);
}}
/>
onSubmit you can access file using values obj like values.avatar.
On server-side (express) to access the file we use req.file
You also have to use multer that will automatically detect file to handle it on the server-side
To handle file in formik with backend all you need to do add the input given below (you can change avatar to anything you want):
<input
type="file"
name="avatar"
onChange={(event) => {
setFieldValue('avatar', event.currentTarget.files[0]);
}}
/>
onSubmit you can access file using values obj like values.avatar.
On server-side (express) to access the file we use req.file You also have to use multer & cloudinary that will detect file to handle it on the server-side
I used simple trick
<Field name="image">
{ (form , meta , value ) =>
const {setFieldValue} = form
return (
<input name="image" type="file" onChange={(e) => setFieldValue(e.target.file[0])}
)
}
</Field>
for multiple image
<input name="image" type="file" onChange={(e) => setFieldValue(e.target.files)}
or use loop
for( i=0 ; i < e.target.file; i++){
setFieldValue([...image , e.target.file])
}

Ant design date and time pickers do not pass value through Formik (react)

I'm currently working on a booking form which is in React using Formik. I've also incorporated Ant Design's Date Picker and Time Picker for the booking date and time respectively, but I'm having difficulties getting the values to be passed back to the component.
Here is how I've set it up in the form component (I've omitted the other unrelated fields):
const { booking, handleSubmit, mode } = this.props;
...
<Formik
initialValues={booking}
onSubmit={handleSubmit}
render={({errors, touched, isSubmitting}) => (
<Form>
...
<div className="form-group col-sm-4 col-md-6 col-lg-4">
<label htmlFor="booking_date">
Booking Date <span className="required">*</span>
</label>
<DatePicker onChange={ (date, dateString) => setFieldValue('booking_date', dateString)} defaultValue={this.state.bookingDate}
className="form-control" format={this.state.dateFormat} />
</div>
<div className="form-group col-sm-4 col-md-6 col-lg-4">
<label htmlFor="start_time">
Start Time <span className="required">*</span>
</label>
<TimePicker
defaultValue={this.state.startTime}
format={this.state.timeFormat}
className="form-control"
onChange={this.handleStartTimeChange}
minuteStep={5}
id="start_time"
name="start_time"
/>
</div>
This is the function that handles the time change (just a state set):
handleStartTimeChange(time) {
this.setState({
startTime: time
});
}
And then on the parent, the component is set up like so:
<BookingForm
show={true}
booking={null}
handleSubmit={this.saveBooking.bind(this)}
mode="add"
/>
And the saveBooking function simply console logs the params out. However, it only ever logs out the other fields such as firstname, surname and email. The dates are completely overlooked and I don't know how to be able to get the form to recognise them - I even tried creating a Formik hidden field to replicate the date value when submit but it still ignores it. The field name and ID are correct, and correlate with the database as do all the others - so I don't understand why it won't read that data?
Put simply, you'll need to utilize Ant Design's Form.Item inside of a Formik Field's component prop.
You'll be able to add other Antd form items as well, however, there are a few quirks. As such, I'd only recommend using one or the other (not both).
Working example: https://codesandbox.io/s/4x47oznvvx
components/AntFields.js (the reason behind creating two different onChange functions is because one of the ant components passes back an event (event.target.value) while the other passes back a value -- unfortunately, a quirk when using Formik with Antd)
import map from "lodash/map";
import React from "react";
import { DatePicker, Form, Input, TimePicker, Select } from "antd";
const FormItem = Form.Item;
const { Option } = Select;
const CreateAntField = Component => ({
field,
form,
hasFeedback,
label,
selectOptions,
submitCount,
type,
...props
}) => {
const touched = form.touched[field.name];
const submitted = submitCount > 0;
const hasError = form.errors[field.name];
const submittedError = hasError && submitted;
const touchedError = hasError && touched;
const onInputChange = ({ target: { value } }) =>
form.setFieldValue(field.name, value);
const onChange = value => form.setFieldValue(field.name, value);
const onBlur = () => form.setFieldTouched(field.name, true);
return (
<div className="field-container">
<FormItem
label={label}
hasFeedback={
(hasFeedback && submitted) || (hasFeedback && touched) ? true : false
}
help={submittedError || touchedError ? hasError : false}
validateStatus={submittedError || touchedError ? "error" : "success"}
>
<Component
{...field}
{...props}
onBlur={onBlur}
onChange={type ? onInputChange : onChange}
>
{selectOptions &&
map(selectOptions, name => <Option key={name}>{name}</Option>)}
</Component>
</FormItem>
</div>
);
};
export const AntSelect = CreateAntField(Select);
export const AntDatePicker = CreateAntField(DatePicker);
export const AntInput = CreateAntField(Input);
export const AntTimePicker = CreateAntField(TimePicker);
components/FieldFormats.js
export const dateFormat = "MM-DD-YYYY";
export const timeFormat = "HH:mm";
components/ValidateFields.js
import moment from "moment";
import { dateFormat } from "./FieldFormats";
export const validateDate = value => {
let errors;
if (!value) {
errors = "Required!";
} else if (
moment(value).format(dateFormat) < moment(Date.now()).format(dateFormat)
) {
errors = "Invalid date!";
}
return errors;
};
export const validateEmail = value => {
let errors;
if (!value) {
errors = "Required!";
} else if (!/^\w+([.-]?\w+)*#\w+([.-]?\w+)*(\.\w{2,3})+$/.test(value)) {
errors = "Invalid email address!";
}
return errors;
};
export const isRequired = value => (!value ? "Required!" : "");
components/RenderBookingForm.js
import React from "react";
import { Form, Field } from "formik";
import { AntDatePicker, AntInput, AntSelect, AntTimePicker } from "./AntFields";
import { dateFormat, timeFormat } from "./FieldFormats";
import { validateDate, validateEmail, isRequired } from "./ValidateFields";
export default ({ handleSubmit, values, submitCount }) => (
<Form className="form-container" onSubmit={handleSubmit}>
<Field
component={AntInput}
name="email"
type="email"
label="Email"
validate={validateEmail}
submitCount={submitCount}
hasFeedback
/>
<Field
component={AntDatePicker}
name="bookingDate"
label="Booking Date"
defaultValue={values.bookingDate}
format={dateFormat}
validate={validateDate}
submitCount={submitCount}
hasFeedback
/>
<Field
component={AntTimePicker}
name="bookingTime"
label="Booking Time"
defaultValue={values.bookingTime}
format={timeFormat}
hourStep={1}
minuteStep={5}
validate={isRequired}
submitCount={submitCount}
hasFeedback
/>
<Field
component={AntSelect}
name="bookingClient"
label="Client"
defaultValue={values.bookingClient}
selectOptions={values.selectOptions}
validate={isRequired}
submitCount={submitCount}
tokenSeparators={[","]}
style={{ width: 200 }}
hasFeedback
/>
<div className="submit-container">
<button className="ant-btn ant-btn-primary" type="submit">
Submit
</button>
</div>
</Form>
);
components/BookingForm.js
import React, { PureComponent } from "react";
import { Formik } from "formik";
import RenderBookingForm from "./RenderBookingForm";
import { dateFormat, timeFormat } from "./FieldFormats";
import moment from "moment";
const initialValues = {
bookingClient: "",
bookingDate: moment(Date.now()),
bookingTime: moment(Date.now()),
selectOptions: ["Mark", "Bob", "Anthony"]
};
const handleSubmit = formProps => {
const { bookingClient, bookingDate, bookingTime, email } = formProps;
const selectedDate = moment(bookingDate).format(dateFormat);
const selectedTime = moment(bookingTime).format(timeFormat);
alert(
`Email: ${email} \nSelected Date: ${selectedDate} \nSelected Time: ${selectedTime}\nSelected Client: ${bookingClient}`
);
};
export default () => (
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
render={RenderBookingForm}
/>
);
I don't understand why it won't read that data?
Formik passes values as values prop, they are updated using setFieldValue. When you store values in the state, Formik does't know anything about it.
Of course there is nothing wrong in storing values to the state (assuming it works) but you have to define internal submit handler to attach these values to others. By simple calling prop:
onSubmit={handleSubmit}
you have no chance to do that. Only Formik handled values will be passed. You need to define internal submit handler, sth like:
const handleSubmit = values => {
// init with other Formik fields
let preparedValues = { ...values };
// values from state
const { startTime, startDate } = this.state;
// attach directly or format with moment
preparedValues["startTime"] = startTime;
preparedValues["startDate"] = startDate;
// of course w/o formatting it can be done shorter
// let preparedValues = { ...values, ...this.state };
console.log(preparedValues);
// call external handler with all values
this.prop.handleSubmit( preparedValues );
}

How to upload an image in React JS?

<div className="mb-1">
Image <span className="font-css top">*</span>
<div className="">
<input type="file" id="file-input" name="ImageStyle"/>
</div>
</div>
This is the snippet i provided that i was using to pick the file from the device in react js,
Using this i can select the file and that filename is also shown as well
What i want is now to store this file on S3 or anywhere and get its URL from there and POST it to my server using fetch api call.
import React, { useState } from "react";
const UploadAndDisplayImage = () => {
const [selectedImage, setSelectedImage] = useState(null);
return (
<div>
<h1>Upload and Display Image usign React Hook's</h1>
{selectedImage && (
<div>
<img
alt="not found"
width={"250px"}
src={URL.createObjectURL(selectedImage)}
/>
<br />
<button onClick={() => setSelectedImage(null)}>Remove</button>
</div>
)}
<br />
<br />
<input
type="file"
name="myImage"
onChange={(event) => {
console.log(event.target.files[0]);
setSelectedImage(event.target.files[0]);
}}
/>
</div>
);
};
export default UploadAndDisplayImage;
Upload the image from your file and display it on your page in react,
you can also get the image object in the state when we select the image
to display on the webpage you have to convert the image object to object using URL.createObjectURL(fileObject)
import React, { Component } from "react";
class DisplayImage extends Component {
constructor(props) {
super(props);
this.state = {
image: null
};
// if we are using arrow function binding is not required
// this.onImageChange = this.onImageChange.bind(this);
}
onImageChange = event => {
if (event.target.files && event.target.files[0]) {
let img = event.target.files[0];
this.setState({
image: URL.createObjectURL(img)
});
}
};
render() {
return (
<div>
<div>
<div>
<img src={this.state.image} />
<h1>Select Image</h1>
<input type="file" name="myImage" onChange={this.onImageChange} />
</div>
</div>
</div>
);
}
}
export default DisplayImage;
If you want to upload image and post it to an API. Then you install react-image-uploader. It saves the image to your local port and also in your database by raising a POST request.
This code let you upload image to the server,the backend code is written in nestjs,and display the image which will be uploaded.I have used the formdata.
import React, { useEffect, useState } from "react";
function Product() {
const { REACT_APP_REST } = process.env;
const [file, setFile] = useState([]);
const handleFile = event => {
setFile(
URL.createObjectURL(event.target.files[0])
);
const formData = new FormData();
formData.append("fileupload", event.target.files[0]);
fetch(REACT_APP_REST + "/product/upload", {
method: 'POST',
body: formData,
dataType: "jsonp"
})
};
return (
<>
<Container fluid>
<Col md="4">
<Card className="card-user">
<img src={file} />
<Card.Body>
<Form.Group>
<label>IMAGE</label>
<Form.Control
type="file"
required="required"
onChange={handleFile}
></Form.Control>
</Form.Group>
</Card.Body>
<hr></hr>
</Card>
</Col>
</Container>
</>
);
}
export default Product;
using react-uploady you can do this very easily:
import React from "react";
import Uploady from "#rpldy/uploady";
import UploadButton from "#rpldy/upload-button";
import UploadPreview from "#rpldy/upload-preview";
const filterBySize = (file) => {
//filter out images larger than 5MB
return file.size <= 5242880;
};
const App = () => (
<Uploady
destination={{ url: "my-server.com/upload" }}
fileFilter={filterBySize}
accept="image/*"
>
<UploadButton />
<UploadPreview />
</Uploady>
);
Failed to execute 'createObjectURL' on 'URL': Overload resolution failed.
For some reason I coudn't use URL.createObjectURL(image) as
const [image, setImage] = useState(null);
const [imgURL, setImgURL] = useState();
<img src={URL.createObjectURL(image)}/>
So I save the Url in the state for instant display on the button click method. This worked!
setImgURL(URL.createObjectURL(image));
Unfortunately, I was still getting the same error when I use useEffect.
useEffect(() => {
setImgURL(URL.createObjectURL(image));
}, [image]);

Resources