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>
)
}}
/>
Related
I'm used the material-UI to develop the react.js frontend. But when I'm gonna develop it furthermore(to implement the crud operations), it's hard to code, since every tutorial coded by using class, not a function. So, I tried to convert the code into the class. But I failed to do that properly.
So I just ask you guys to help me to convert the below code into the class. I would like to appreciate your kindness. Thank you.
import React from "react";
import ReactDom from 'react-dom';
import Button from '#material-ui/core/Button';
import { Container, Row, Col, Form } from "react-bootstrap";
import TextField from '#material-ui/core/TextField';
import { makeStyles, withStyles } from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(1),
width: '25ch',
},
},
button: {
marginRight: theme.spacing(1),
},
}));
export default function Step2(props) {
const classes = useStyles();
return (
<Container style={{marginRight: 700}}>
<Row
style={{ marginTop: "30px" }}
className="h-100 justify-content-center align-items-center"
>
<Col md={{ span: 6 }} className="text-center my-auto">
<h3 style={{ marginBottom: "1rem" }}>New Vehicle Details</h3>
<Form>
<form className={classes.root} noValidate autoComplete="off">
<TextField id="standard-basic" label="Vehicle No." required/>
</form>
<form className={classes.root} noValidate autoComplete="off">
<TextField id="standard-basic" label="Registered Year." required/>
</form>
<form className={classes.root} noValidate autoComplete="off">
<TextField id="standard-basic" label="Capacity" required/>
</form>
<form className={classes.root} noValidate autoComplete="off">
<TextField id="standard-basic" label="Type" required/>
</form>
<Button
variant="contained"
color="primary"
//onClick={handleNext}
className={classes.button}
>
Add
</Button>
<Button
variant="contained"
color="secondry"
// onClick={handleNext}
className={classes.button}
>
Cancel
</Button>
</Form>
</Col>
</Row>
</Container>
);
}
you are now getting props in parameters of this function just change the function to class.
export default class Step2 extends React.Component {
}
and use props like this.props instead of props
Sorry for the question, maybe it is very silly but I am learning and I have not been able to solve it, I took the signup template from material-ui and decided to perform the validations with react hook forms and everything works but I wanted to add the function of showing or hiding the password with it eye icon, it turns out that when I manage to do it, it is changing the field type from "text-field" to "FormControl" but they no longer show me the help messages when there is an error.
In the code there are 2 password fields, the first works as I want but without the hide / unhide and the second has the icon but as I mentioned it does not show the message.
Something that strikes me is that it turns red correctly when there is an error.
I would appreciate if you could help me make it work properly. Thank you
import React, {useState} from 'react';
import Avatar from '#material-ui/core/Avatar';
import Button from '#material-ui/core/Button';
import CssBaseline from '#material-ui/core/CssBaseline';
import TextField from '#material-ui/core/TextField';
import Link from '#material-ui/core/Link';
import Grid from '#material-ui/core/Grid';
import Box from '#material-ui/core/Box';
import LockOutlinedIcon from '#material-ui/icons/LockOutlined';
import Typography from '#material-ui/core/Typography';
import { makeStyles } from '#material-ui/core/styles';
import Container from '#material-ui/core/Container';
import Visibility from '#material-ui/icons/Visibility';
import VisibilityOff from '#material-ui/icons/VisibilityOff';
import InputAdornment from '#material-ui/core/InputAdornment';
import IconButton from '#material-ui/core/IconButton';
import FormControl from '#material-ui/core/FormControl';
import clsx from 'clsx';
import InputLabel from '#material-ui/core/InputLabel';
import * as yup from 'yup'
import { yupResolver } from '#hookform/resolvers';
import { useForm} from "react-hook-form";
import { OutlinedInput } from '#material-ui/core';
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{'Copyright © '}
<Link color="inherit" href="https://material-ui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
margin: {
margin: theme.spacing(1),
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(3),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
const RegisterSchema = yup.object().shape({
password: yup
.string()
.trim()
.required('Requerido.')
.min(8, 'Contraseña es muy corta - Debe contener al menos 8 caracteres.')
.max(15, 'Contraseña es muy larga - Debe contener máximo 15 caracteres.')
.matches(/[a-zA-Z]/, 'Contraseña solo puede contener letras latinas.')
});
export default function SignUp() {
const classes = useStyles();
const [data, setData] = useState({
password:'',
showPassword: false,
});
const handleChange = (prop) => (event) => {
setData({ ...data, [prop]: event.target.value });
};
const handleClickShowPassword = () => {
setData({ ...data, showPassword: !data.showPassword });
};
const handleMouseDownPassword = (event) => {
event.preventDefault();
};
const handleInputChange = (e) => {
console.log(e.target.value);
setData ({
...data,
fullName: (data.firstName + ' ' + data.lastName),
[e.target.name] : e.target.value,
})
}
const {register, handleSubmit, errors} = useForm({
resolver: yupResolver(RegisterSchema),
});
const onSubmit = (data) => {
console.log(data);
};
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Registro
</Typography>
<form className={classes.form} noValidate onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={2}>
{<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
name="password"
label="Contraseña"
type="password"
id="password"
autoComplete="current-password"
onChange={handleInputChange}
inputRef={register}
error ={!!errors.password}
helperText={errors.password ? errors.password.message : ''}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
>
{data.showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
/>
</Grid>}
<FormControl
className={clsx(classes.margin, classes.textField)}
variant="outlined"
autoComplete="current-password"
inputRef={register}
error ={!!errors.password}
helperText={errors.password ? errors.password.message : ''}
name="password"
type="password"
id="password"
fullWidth
required
>
<InputLabel
htmlFor="outlined-adornment-password">
Contraseña
</InputLabel>
<OutlinedInput
id="outlined-adornment-password"
type={data.showPassword ? 'text' : 'password'}
value={data.password}
onChange={handleChange('password')}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
>
{data.showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
labelWidth={92}
/>
</FormControl>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Registrate
</Button>
<Grid container justify="flex-end">
<Grid item>
<Link href="#" variant="body2">
¿Tienes una cuenta? Ingresa
</Link>
</Grid>
</Grid>
</form>
</div>
<Box mt={5}>
<Copyright />
</Box>
</Container>
);
}
In your password Text Field component
you may use this solution , try adding some state to your component that defines whether the user want to show or hide the password and name it for example "showPassword"
const [showPassword,setShow] = useState(false)
<TextField
variant="outlined"
required
fullWidth
name="password"
label="Contraseña"
type={showPassword?"text":"password"}
/>
call setShow(!showPassword) when ever the user click on eye button
Now when ever setShow changes to " true " , the type of the input will be text and thus the password will be shown
Try this. It's worked for me.
<TextField
label='Password'
fullWidth
required
type={showPassword ? 'text' : 'password'}
error={!!errors['password']}
helperText={errors['password'] ? errors['password'].message : ''}
{...register('password')}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
>
{showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
)
}}
/>
Have created a button called image upload, need to view the selected image when selected
<GridItem xs={6}>
<FormControl className={classes.formControl}>
<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 Image
</Button>
</label>
</FormControl>
</GridItem>
you can set the file object URL in the state during the onChange and use it in an img tag like this,
const [file, setFile] = useState(undefined);
const handleChange = (event) => {
setFile(URL.createObjectURL(event.target.files[0]));
}
use this handleChange method in input onChange event,
<GridItem xs={6}>
<FormControl className={classes.formControl}>
<input
accept="image/*"
className={classes.input}
style={{ display: 'none' }}
id="raised-button-file"
multiple
type="file"
onChange={handleChange}
/>
{/* preview of file */}
{ file && <img src={this.state.file}/>}
<label htmlFor="raised-button-file">
<Button
variant="raised"
component="span"
className={classes.button}
>
Upload Image
</Button>
</label>
</FormControl>
</GridItem>
also if you want to allow the user to crop the image selected there is this library react-image-crop. I use it and it works pretty good for me.
before select
select image
Typescript
import { Grid, IconButton, makeStyles, Tooltip } from '#material-ui/core'
import { PhotoCamera, Publish } from "#material-ui/icons"
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import { IRequestState } from '../../assets/interfaces'
const FILE = 'ImageUpload.tsx'
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(1),
},
}, right: {
float: "right"
}, w100: {
width: "100%"
}, input: {
display: "none",
},
faceImage: {
color: theme.palette.primary.light,
}, success: {
color: theme.palette.success.main
}
}))
interface IImageUploadProps {
Submit(event: React.MouseEvent<HTMLButtonElement>): void
}
type IImageUploadState = IRequestState & {
imageFile?: any
}
export default function ImageUpload(props: IImageUploadProps) {
const classes = useStyles()
const dispatch = useDispatch()
const initState: IImageUploadState = { inited: false, requesting: false }
const [values, setState] = useState<IImageUploadState>(initState)
const handleCapture = ({ target }: any) => {
if (target && target.files && target.files.length)
setState({ ...values, imageFile: target.files[0] })
}
return (
<>
<Grid
container
direction="row"
justify="space-evenly"
alignItems="center"
spacing={2}
>
<Grid item sm={6} md={3}>
<Grid
justify="center"
alignItems="center"
container
>
<Grid item>
<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>
</Grid>
</Grid>
</Grid>
<Grid item sm={6} md={3}>
<img className={classes.w100} hidden={!values.imageFile} src={values.imageFile && URL.createObjectURL(values.imageFile)} alt="Logo" />
</Grid>
<Grid item sm={6} md={3}>
<Grid
justify="center"
alignItems="center"
container
>
<Grid item>
<Tooltip title="upload image">
<IconButton
className={classes.success}
aria-label="upload"
onClick={props.Submit}
edge="end"
disabled={!values.imageFile}
>
<Publish />
</IconButton>
</Tooltip>
</Grid>
</Grid>
</Grid>
</Grid>
</>
)
}
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
I am pretty new to React, and I am making my first app using material-ui, and I have managed to render the components I intend, but I haven't been able to print them stacked, they show in line:
I have tried to wrap them with div and p, and I get the same result.
This is the code of my component:
import React, { Component } from 'react';
import Button from '#material-ui/core/Button';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Input from '#material-ui/core/Input';
import InputLabel from '#material-ui/core/InputLabel';
import InputAdornment from '#material-ui/core/InputAdornment';
import FormControl from '#material-ui/core/FormControl';
import Visibility from '#material-ui/icons/Visibility';
import VisibilityOff from '#material-ui/icons/VisibilityOff';
import IconButton from '#material-ui/core/IconButton';
const styles = theme => ({
root: {
display: 'flex',
flexWrap: 'wrap',
},
margin: {
margin: theme.spacing.unit,
},
withoutLabel: {
marginTop: theme.spacing.unit * 3,
},
textField: {
flexBasis: 200,
},
button: {
margin: theme.spacing.unit,
},
input: {
display: 'none',
},
});
class LoginModal extends Component {
state = {
password: '',
showPassword: false,
};
render() {
const { classes } = this.props
return (
<div className={classes.root}>
<h1>Welcome to My App...</h1>
<FormControl className={classNames(classes.margin, classes.textField)}>
<InputLabel htmlFor="adornment-email">eMail</InputLabel>
<Input i
d="adornment-email"
variant="filled"
/>
</FormControl>
<FormControl className={classNames(classes.margin, classes.textField)}>
<InputLabel htmlFor="adornment-password">Password</InputLabel>
<Input
id="adornment-password"
variant="filled"
type={this.state.showPassword ? 'text' : 'password'}
value={this.state.password}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="Toggle password visibility"
onClick={this.handleClickShowPassword}
>
{this.state.showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
/>
<p>Did you forget your password?</p>
<Button color="primary" variant="contained">Login</Button>
</FormControl>
</div>
);
};
}
LoginModal.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(LoginModal);
Only the two latest components show stacked, but the rest show in line.
How can I instruct React to display components in an stacked fashion?
update your root style to:
root: {
display: 'flex',
flex-direction: column;
},
you may need to adjust other flex properties to get the alignment you want.
I found that Material-UI uses Grid components to manage responsiveness, and you can use it to arrange objects in display.
This is how I used Grid to display my objects the way I wanted:
class LoginModal extends Component {
state = {
password: '',
showPassword: false,
};
render() {
const { classes } = this.props
return (
<div className={classes.root}>
<Grid container spacing={24}>
<Grid item xs={12}>
<h1>Welcome to My App...</h1>
</Grid>
<Grid item xs={12}>
<FormControl className={classNames(classes.margin, classes.textField)}>
<InputLabel htmlFor="adornment-email">eMail</InputLabel>
<Input
id="adornment-email"
variant="filled"
/>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl className={classNames(classes.margin, classes.textField)}>
<InputLabel htmlFor="adornment-password">Password</InputLabel>
<Input
id="adornment-password"
variant="filled"
type={this.state.showPassword ? 'text' : 'password'}
value={this.state.password}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="Toggle password visibility"
onClick={this.handleClickShowPassword}
>
{this.state.showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
/>
<p>Did you forget your password?</p>
<Button color="primary" variant="contained">Login</Button>
</FormControl>
</Grid>
</Grid>
</div>
);
};
}
And this is the result: