Invoke form submit with a onChange event - reactjs

Sorry if the title is confusing.
Basically I have a form that contains a single input with the type file that is disguised as an icon
I want the form to submit when a file is selected, but I have no clue how to invoke the form submission with the input onChange event (tbh I'm not even sure that would be the adequate way of doing it).
Here is the code:
<Form onSubmit={handleSubmit((data) => updateProfilePicture(data))}>
<IconButton
component='label'
htmlFor='image-input'
style={{
position: "absolute",
bottom: "0",
right: "0",
backgroundColor: "#eee",
}}
>
<Input
type='file'
id='image-input'
name='file'
style={{ display: "none" }}
inputRef={register}
/>
<PhotoCameraIcon style={{ fontSize: "25px" }} />
</IconButton>
</Form>
Here what it looks like:
note: there is two forms, one for the image, one for the data, I don't want to handle the through the "update" button, otherwise it would be one form only and I wouldn't be having this question

You don't need a form to submit after image is added.
you can just use the input field and add onChange event handler get the Image details. Check this out if interested
https://stackblitz.com/edit/react-qjqt3f
import React from "react";
import "./style.css";
export default class App extends React.Component {
constructor() {
super();
this.state = {
url: "https://www.w3schools.com/howto/img_avatar.png"
};
}
onSelectFile = e => {
if (e.target.files && e.target.files[0]) {
var reader = new FileReader();
reader.readAsDataURL(e.target.files[0]); // read file as data url
reader.onload = event => {
// called once readAsDataURL is completed
console.log(event.target.result);
this.setState({ url: event.target.result });
//you can do the HTTP post call here
};
}
};
render() {
return (
<div>
<label className="hoverable" htmlFor="fileInput">
<img src={this.state.url} />
<div className="hover-text">Choose file</div>
<div className="background" />
</label>
<br />
<input id="fileInput" type="file" onChange={this.onSelectFile} />
</div>
);
}
}

You can give the form a id and submit the form per javascript:
document.getElementById("imageForm").submit();
or handle the data without submit:
onChange={() => { handleSubmit(this.files) }}
<Form id="imageForm" onSubmit={handleSubmit((data) => updateProfilePicture(data))}>
<IconButton
component='label'
htmlFor='image-input'
style={{
position: "absolute",
bottom: "0",
right: "0",
backgroundColor: "#eee",
}}
>
<Input
type='file'
id='image-input'
name='file'
style={{ display: "none" }}
inputRef={register}
onChange={() => { document.getElementById("imageForm").submit(); }}
// or:
onChange={() => { handleSubmit(this.files) }}
/>
<PhotoCameraIcon style={{ fontSize: "25px" }} />
</IconButton>
</Form>

Submitting forms programmatically is not the way to go specially within react. But for the sake of it you could achieve something like this by dispatching a submit event.
<Form ref={formRef} onSubmit={handleSubmit((data) => updateProfilePicture(data))}>
...
<Input
type='file'
id='image-input'
name='file'
style={{ display: "none" }}
onChange={(e) => formRef.dispatchEvent(new Event('submit'))}
inputRef={register}
/>
</Form>
better yet, if you do not want to fire any sort of html form validation, putting this file input inside a <form/> is not mandatory. You could call your updateProfilePicture(data) function directly inside of <Input/> s onChange handler.

Related

history.push on form submit

I'm a newbie to react and I'm trying to route on a different set of components after a form (in my case a search bar) has been submitted (using onSubmit event). I have my search component wrapped in withRouter and have the history deconstructed. I've tried using onClick and the push does trigger, but does not seem to do anything with onSubmit. Hoping for your advise.
Thank you
Search component
const SearchAppBar = (props) => {
const {
history,
onPlatformDrop,
onPlatformChange,
onPriceRangeDrop,
clearPriceRange,
searchQuery,
setSearchQuery,
clearGenre,
onDropDownChange,
} = props;
return (
<Box sx={{ flexGrow: 1 }}>
<Search
sx={{
width: { xs: "85vw", md: "50vw", lg: "30vw" },
margin: "auto",
marginBottom: "20px",
}}
>
<form action="/" method="get" style={{ display: "flex" }}>
<StyledInputBase
defaultValue={searchQuery}
autoComplete="off"
placeholder="Search All Games…"
inputProps={{ "aria-label": "search" }}
type="search"
name="s"
id="site-search"
/>
<SearchIconWrapper>
<IconButton
type="submit"
onSubmit={(e) => {
history.push("/find");
clearGenre();
clearPriceRange();
onPlatformChange("");
onPlatformDrop("All Platforms");
onPriceRangeDrop("All Price Range");
onDropDownChange("All Genres");
setSearchQuery(e.target.value);
}}
>
<SearchIcon style={{ color: "#55597d" }} />
</IconButton>
</SearchIconWrapper>
</form>
</Search>
</Box>
);
};
export default withRouter(SearchAppBar);
Then on my App, i have something like this:
<Route
path="/find"
exact
render={(props) => (
<div>
<Search
onPlatformChange={onPlatformChange}
onPlatformDrop={onPlatformDrop}
clearPriceRange={clearPriceRange}
onPriceRangeDrop={onPriceRangeDrop}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
clearGenre={clearGenre}
onDropDownChange={onDropDownChange}
/>
</div>
)}
/>;
The onSubmit handler should be on the form element, not the button submitting the form. Remove the form action and method. You will also need to prevent the default form action from occurring, if you don't then the form is submitted and the page reloads.
<form
style={{ display: "flex" }}
onSubmit={(e) => { // handles form submission
e.preventDefault(); // prevents default form action
history.push("/find"); // navigate
...
setSearchQuery(e.target.s.value); // <-- access field value
}}
>
<StyledInputBase
...
name="s" // <-- access in submit handler
...
/>
<SearchIconWrapper>
<IconButton type="submit">
<SearchIcon style={{ color: "#55597d" }} />
</IconButton>
</SearchIconWrapper>
</form>

React - Component Lifecycle when pressing "back" from an external URL

Context:
I am creating a simple web app where the user can login, select the amount they wish to pay, and then press a Pay button which redirects them to a payment gateway in order for them to pay.
Problem:
When they press the Pay button, I set a mobX observable variable to loading so as to show a loading spinner and disable the button. This works fine. However, if the user proceeds to the external payment gateway, and then press the browser Back button, on certain browsers (Firefox and Safari), my state is still loading, despite setting it to not loading on componentDidMount. What component lifecycle method is called when navigating to the component from the back button on all browsers?
code:
#observer
class DuesForm extends React.Component<Props, {}> {
private form;
private loadingStore: LoadingStore = new LoadingStore();
constructor(props: Props) {
super(props);
this.form = React.createRef();
}
public componentDidMount() {
this.loadingStore.setLoadingToComplete();
}
public componentWillUnmount() {
this.loadingStore.setLoadingToComplete();
}
public render() {
return (
<form
name="dues-form"
ref={(f) => (this.form = f)}
method="POST"
action="www.external-payment-gateway.com"
>
<div>
<h4 style={{ marginBottom: '0.5em' }}>Currency</h4>
<Radio.Group
options={currencyOptions}
optionType="button"
buttonStyle="solid"
/>
</div>
<div style={{ marginTop: '1em' }}>
<h4 style={{ marginBottom: '0.5em' }}>Amount</h4>
<InputNumber
type="number"
style={{ minWidth: '8em', justifyContent: 'center' }}
min={0}
step={0.01}
precision={2}
/>
</div>
{this.loadingStore.isLoading ? (
<Space size="middle" style={{ marginBottom: '1em', marginTop: '1em' }}>
<Spin indicator={<LoadingOutlined />} />
</Space>
) : null}
<div style={{ marginTop: '1.5em' }}>
<Button
className="primary-button"
style={{ width: '100%' }}
type="primary"
onClick={this.handleButtonClick.bind(this)}
disabled={this.loadingStore.isLoading}
>
Pay
</Button>
</div>
<PaymentForm /> // This is a hidden form given to me by the payment gateway to send the data
</form>
);
}
private handleSubmit(e: any): void {
e.preventDefault();
this.loadingStore.setLoadingToLoading();
this.form.submit(); // the reason I am doing it this way is I have to fetch some data from the API before submitting
}
}

Antd Forms, get values from custom component?

I'm trying to add some custom components within getFieldDecorator and obtain the values onCreate added. Not sure how I can go about it since the state is found within the Custom components. Ideally, the custom component will handle all user input value but unsure about how to pass those values as part of the object onCreate.
import React from "react";
import { Icon, Modal, Form, Input } from "antd";
import Tags from "./EditableTagGroup";
const Taskform = Form.create({ name: "event_form" })(props => {
const { visible, onCancel, onCreate, form } = props;
const { getFieldDecorator } = form;
const handleCreate = () => {
form.validateFields((err, values) => {
if (err) {
return;
}
form.resetFields();
onCreate(values);
});
};
return (
<Modal
visible={visible}
title="Task"
closeIcon={
<Icon
type="close"
style={{ fontSize: "14px", fontWeight: "600", marginTop: "30px" }}
/>
}
okText="Create"
onCancel={onCancel}
onOk={handleCreate}
>
<Form layout="vertical">
<Form.Item label="Name">
{getFieldDecorator("name", {
rules: [
{
required: true,
message: "Please type the name of task!"
}
]
})(<Input placeholder="Write a task name" />)}
</Form.Item>
<Form.Item label="Description">
{getFieldDecorator("description")(
<Input.TextArea
style={{ minHeight: "60px" }}
autoSize={{ minRows: 3, maxRows: 6 }}
placeholder="Write a description"
/>
)}
</Form.Item>
<Form.Item>{getFieldDecorator("tags")(<Tags />)}</Form.Item>
</Form>
</Modal>
);
});
export default Taskform;
I have checked your code on sandbox. You may need to pass the props getFieldDecorator down to the EditableFormTag.js like below:
Step one: from the taskform.js
<Form.Item><Tags getFieldDecorator={getFieldDecorator} /></Form.Item>
Step two: Inside the EditableTagGroup.js
const { getFieldDecorator } = this.props;
{inputVisible &&
<Input
ref={this.saveInputRef}
onChange={this.handleInputChange}
onPressEnter={this.handleInputConfirm}
value={inputValue}
onBlur={this.handleInputConfirm}
type="text"
size="small"
style={{ width: 78 }}
/>
}
{getFieldDecorator("tags", {
initialValue: { tags }
})(
<Input
ref={this.saveInputRef}
type="text"
size="small"
style={{ display: "none" }}
/>
)
}
End result

How to align css image to right side of page and make it cover half the page?

Im trying to make an image cover the right side of my page with full height. I want 50% of the screen to be covered by my login form and other 50% to be covered by background image. Im using antd UI library , so maybe i will have to overide some designs. But right now im just getting an image that is very small and isnt aligned to the right. Can someone please help?
import React, { Component } from 'react'
import { Form, Input, Button, Checkbox } from 'antd'
import { Helmet } from 'react-helmet'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import styles from './style.module.scss'
#Form.create()
#connect(({ user }) => ({ user }))
class Login extends Component {
onSubmit = event => {
event.preventDefault()
const { form, dispatch } = this.props
form.validateFields((error, values) => {
if (!error) {
dispatch({
type: 'user/LOGIN',
payload: values,
})
}
})
}
render() {
const {
form,
user: { fetching },
} = this.props
return (
<div className={styles.header}>
<Helmet title="Login" />
<div className={`${styles.title} login-heading`}>
<h1>Welcome</h1>
<p className={styles.par}> Please enter your credentials to proceed.</p>
</div>
<div className={styles.block}>
<div className="row">
<div className="col">
<div className={styles.inner}>
<div className={styles.form}>
<Form layout="vertical" hideRequiredMark onSubmit={this.onSubmit}>
<Form.Item label="EMAIL ADDRESS">
{form.getFieldDecorator('email', {
initialValue: 'surfside-strapi',
rules: [{ required: true, message: 'Please input your e-mail address' }],
})(<Input style={{ width: '80%' }} size="default" />)}
</Form.Item>
<Form.Item label="PASSWORD">
{form.getFieldDecorator('password', {
initialValue: 'SurfsideAdmin',
rules: [{ required: true, message: 'Please input your password' }],
})(<Input style={{ width: '80%' }} size="default" type="password" />)}
</Form.Item>
<Form.Item>
{form.getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: true,
})(<Checkbox>Remember me</Checkbox>)}
<Link to="/user/forgot" className={styles.forgot}>
Forgot password?
</Link>
</Form.Item>
<div className="form-actions">
<Button
type="primary"
className="width-150 mr-4"
htmlType="submit"
style={{ width: '80%' }}
loading={fetching}
>
Sign in
</Button>
<span className="ml-3 register-link">
<p className={styles.paragraph}> Dont have an account yet ?</p>
<Link
to="/user/register"
style={{
position: 'relative',
/* left: 0%; */
right: '-270px',
top: '-33px',
bottom: '0%',
fontfamily: 'Rubik',
fontsize: '15px',
color: '#8C54FF',
}}
className={styles.register}
>
Sign up
</Link>{' '}
</span>
</div>
</Form>
//image that i want to be aligned to right side <div className="col-xl-7"> <img src="https://media.gettyimages.com/photos/clear-empty-photographer-studio-background-picture-id538493760?b=1&k=6&m=538493760&s=612x612&w=0&h=8zp1RB3PQuARtpemaBvtrdqecBLg_258_XZOuOkzsxc="/></div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default Login
Create 2 divs and give 50% width for each. Put form in one and image in the other one.
for image give object-fit:cover so that it will cover the entire div.
Give display:flex for the parent which contains the above two divs.
Here's a pen
I have used object-position: right which depends on the which side image of image should appear for you.

How to enable file upload on React's Material UI simple input?

I am creating a simple form to upload file using electron-react-boilerplate with redux form & material ui.
The problem is that I do not know how to create input file field because material ui does not support upload file input.
Any ideas on how to achieve this?
The API provides component for this purpose.
<Button
variant="contained"
component="label"
>
Upload File
<input
type="file"
hidden
/>
</Button>
newer MUI version:
<input
accept="image/*"
className={classes.input}
style={{ display: 'none' }}
id="raised-button-file"
multiple
type="file"
/>
<label htmlFor="raised-button-file">
<Button variant="raised" component="span" className={classes.button}>
Upload
</Button>
</label>
You need to wrap your input with component, and add containerElement property with value 'label' ...
<RaisedButton
containerElement='label' // <-- Just add me!
label='My Label'>
<input type="file" />
</RaisedButton>
You can read more about it in this GitHub issue.
EDIT: Update 2019.
Check at the bottom answer from #galki
TLDR;
<input
accept="image/*"
className={classes.input}
style={{ display: 'none' }}
id="raised-button-file"
multiple
type="file"
/>
<label htmlFor="raised-button-file">
<Button variant="raised" component="span" className={classes.button}>
Upload
</Button>
</label>
Here's an example using an IconButton to capture input (photo/video capture) using v3.9.2:
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import IconButton from '#material-ui/core/IconButton';
import PhotoCamera from '#material-ui/icons/PhotoCamera';
import Videocam from '#material-ui/icons/Videocam';
const styles = (theme) => ({
input: {
display: 'none'
}
});
class MediaCapture extends Component {
static propTypes = {
classes: PropTypes.object.isRequired
};
state: {
images: [],
videos: []
};
handleCapture = ({ target }) => {
const fileReader = new FileReader();
const name = target.accept.includes('image') ? 'images' : 'videos';
fileReader.readAsDataURL(target.files[0]);
fileReader.onload = (e) => {
this.setState((prevState) => ({
[name]: [...prevState[name], e.target.result]
}));
};
};
render() {
const { classes } = this.props;
return (
<Fragment>
<input
accept="image/*"
className={classes.input}
id="icon-button-photo"
onChange={this.handleCapture}
type="file"
/>
<label htmlFor="icon-button-photo">
<IconButton color="primary" component="span">
<PhotoCamera />
</IconButton>
</label>
<input
accept="video/*"
capture="camcorder"
className={classes.input}
id="icon-button-video"
onChange={this.handleCapture}
type="file"
/>
<label htmlFor="icon-button-video">
<IconButton color="primary" component="span">
<Videocam />
</IconButton>
</label>
</Fragment>
);
}
}
export default withStyles(styles, { withTheme: true })(MediaCapture);
It is work for me ("#material-ui/core": "^4.3.1"):
<Fragment>
<input
color="primary"
accept="image/*"
type="file"
onChange={onChange}
id="icon-button-file"
style={{ display: 'none', }}
/>
<label htmlFor="icon-button-file">
<Button
variant="contained"
component="span"
className={classes.button}
size="large"
color="primary"
>
<ImageIcon className={classes.extendedIcon} />
</Button>
</label>
</Fragment>
If you're using React function components, and you don't like to work with labels or IDs, you can also use a reference.
const uploadInputRef = useRef(null);
return (
<Fragment>
<input
ref={uploadInputRef}
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={onChange}
/>
<Button
onClick={() => uploadInputRef.current && uploadInputRef.current.click()}
variant="contained"
>
Upload
</Button>
</Fragment>
);
Official recommendation
import * as React from 'react';
import { styled } from '#mui/material/styles';
import Button from '#mui/material/Button';
import IconButton from '#mui/material/IconButton';
import PhotoCamera from '#mui/icons-material/PhotoCamera';
import Stack from '#mui/material/Stack';
const Input = styled('input')({
display: 'none',
});
export default function UploadButtons() {
return (
<Stack direction="row" alignItems="center" spacing={2}>
<label htmlFor="contained-button-file">
<Input accept="image/*" id="contained-button-file" multiple type="file" />
<Button variant="contained" component="span">
Upload
</Button>
</label>
<label htmlFor="icon-button-file">
<Input accept="image/*" id="icon-button-file" type="file" />
<IconButton color="primary" aria-label="upload picture" component="span">
<PhotoCamera />
</IconButton>
</label>
</Stack>
);
}
Nov 2020
With Material-UI and React Hooks
import * as React from "react";
import {
Button,
IconButton,
Tooltip,
makeStyles,
Theme,
} from "#material-ui/core";
import { PhotoCamera } from "#material-ui/icons";
const useStyles = makeStyles((theme: Theme) => ({
root: {
"& > *": {
margin: theme.spacing(1),
},
},
input: {
display: "none",
},
faceImage: {
color: theme.palette.primary.light,
},
}));
interface FormProps {
saveFace: any; //(fileName:Blob) => Promise<void>, // callback taking a string and then dispatching a store actions
}
export const FaceForm: React.FunctionComponent<FormProps> = ({ saveFace }) => {
const classes = useStyles();
const [selectedFile, setSelectedFile] = React.useState(null);
const handleCapture = ({ target }: any) => {
setSelectedFile(target.files[0]);
};
const handleSubmit = () => {
saveFace(selectedFile);
};
return (
<>
<input
accept="image/jpeg"
className={classes.input}
id="faceImage"
type="file"
onChange={handleCapture}
/>
<Tooltip title="Select Image">
<label htmlFor="faceImage">
<IconButton
className={classes.faceImage}
color="primary"
aria-label="upload picture"
component="span"
>
<PhotoCamera fontSize="large" />
</IconButton>
</label>
</Tooltip>
<label>{selectedFile ? selectedFile.name : "Select Image"}</label>. . .
<Button onClick={() => handleSubmit()} color="primary">
Save
</Button>
</>
);
};
You can use Material UI's Input and InputLabel components. Here's an example if you were using them to input spreadsheet files.
import { Input, InputLabel } from "#material-ui/core";
const styles = {
hidden: {
display: "none",
},
importLabel: {
color: "black",
},
};
<InputLabel htmlFor="import-button" style={styles.importLabel}>
<Input
id="import-button"
inputProps={{
accept:
".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel",
}}
onChange={onInputChange}
style={styles.hidden}
type="file"
/>
Import Spreadsheet
</InputLabel>
import AddPhotoIcon from "#mui/icons-material/AddAPhoto";
import Fab from "#mui/material/Fab";
<Fab color="primary" aria-label="add-image" sx={{ position: "fixed", bottom: 16, right: 16, overflow: "hidden" }}>
<input
type="file"
onChange={imageHandler}
accept=".jpg, .jpeg, .png"
accept="image/*"
multiple
style={{ //make this hidden and display only the icon
position: "absolute",
top: "-35px",
left: 0,
height: "calc(100% + 36px)",
width: "calc(100% + 5px)",
outline: "none",
}}
/>
<AddPhotoIcon />
</Fab>
Just the same as what should be but change the button component to be label like so
<form id='uploadForm'
action='http://localhost:8000/upload'
method='post'
encType="multipart/form-data">
<input type="file" id="sampleFile" style="display: none;" />
<Button htmlFor="sampleFile" component="label" type={'submit'}>Upload</Button>
</form>
<input type="file"
id="fileUploadButton"
style={{ display: 'none' }}
onChange={onFileChange}
/>
<label htmlFor={'fileUploadButton'}>
<Button
color="secondary"
className={classes.btnUpload}
variant="contained"
component="span"
startIcon={
<SvgIcon fontSize="small">
<UploadIcon />
</SvgIcon>
}
>
Upload
</Button>
</label>
Make sure Button has component="span", that helped me.
Here an example:
return (
<Box alignItems='center' display='flex' justifyContent='center' flexDirection='column'>
<Box>
<input accept="image/*" id="upload-company-logo" type='file' hidden />
<label htmlFor="upload-company-logo">
<Button component="span" >
<Paper elevation={5}>
<Avatar src={formik.values.logo} className={classes.avatar} variant='rounded' />
</Paper>
</Button>
</label>
</Box>
</Box>
)
Typescript version of #tomatentobi's javascript solution
const uploadInputRef = useRef<HTMLInputElement | null>(null);
return (
<>
<input
ref={uploadInputRef}
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={onChange}
/>
<Button
onClick={() => uploadInputRef.current && uploadInputRef.current.click()}
variant="contained">
Upload
</Button>
</>
);
This worked for me.
<Button variant="contained" component="label" >
UPLOAD
<input accept="image/*" hidden type="file" />
</Button>
You can pursue all the comments above, those are really great, However, I have another option for customizing your component, if you want to follow.
// Import
import { styled } from '#mui/material/styles';
import { Input } from "#mui/material";
// Custom style
const CustomFileInput = styled(Input)(({ theme }) => {
return {
color: "white",
'::before': {
border: 'none',
position: 'static',
content: 'none'
},
'::after': {
border: 'none',
position: 'static',
content: 'none'
}
}
});
// Using that component
<CustomFileInput type="file" />
Both #galki and #elijahcarrel method works fine.
If anyone trying to do unit-testing(jest) for these two answers.
You wont be able to use the button component with (specially if you are using disabled=true
expect(getByRole("button", {name: "Upload"})).not.toBeEnabled();
instead use this
expect(getByLabelText("Upload")).not.toBeEnabled();
This is for Select Image File
<IconButton color="primary" component="label">
<input type="file" accept="image/*" hidden />
<AttachFileIcon fontSize="medium" />
</IconButton>
NOTE : React Material UI Component (IconButton, AttachFileIcon)
Or there is library for MUI 5 / React 18 : https://viclafouch.github.io/mui-file-input/ 🔥
import React from 'react'
import { MuiFileInput } from 'mui-file-input'
const MyComponent = () => {
const [value, setValue] = React.useState(null)
const handleChange = (newValue) => {
setValue(newValue)
}
return <MuiFileInput value={value} onChange={handleChange} />
}
Try This
import React from 'react'
import { MuiFileInput } from 'mui-file-input'
export default function MyComponent () {
const [file, setFile] = React.useState(null)
const handleChange = (newFile) => {
setFile(newFile)
}
return (
<MuiFileInput value={file} onChange={handleChange} />
)
}
npm install mui-file-input --save
npm install #mui/icons-material
or
yarn add mui-file-input
yarn add #mui/icons-material
another way is this and we can add the name of the file as a value for TextField.
<TextField
value={state.value}
label="upload profile picture"
sx={{ m: 1, width: '25ch' }}
InputProps={{
fullWidth: true,
startAdornment: (
<IconButton component="label">
<AttachFileIcon />
<input
type="file"
hidden
onChange={handleUploadInput}
name="[name]"
/>
</IconButton>
)
}}
/>

Resources