redux form input custom component not submitting - redux

I have a custom component:
import React from 'react';
import styled from 'styled-components';
const InputText = ({ value, onChange, name }) =>
{
return(<StyledInput value={value} onChange={() => onChange(value)}/>)
}
const StyledInput = styled.input.attrs({
type: 'text',
name: props => props.name,
value: props => props.value,
placeholder: props => props.placeholder,
width: props => props.width,
onChange: props => props.onChange,
})`
padding: 10px;
border: none;
border-bottom: solid 1px #999;
transition: border 0.3s;
margin: 8px 5px 3px 8px;
width: ${props => props.width || 275}px;
`;
export default InputText
that I am using in a Redux form:
const CreateNewAssignmentForm = (props) => {
const { handleSubmit, closeModal } = props
return(<div>
<Modal id="AssignmentModal">
<ModalBody width={600}>
<form onSubmit={handleSubmit}>
<TextRight>
<CloseIconButton stroke={color.primary} onClick={() => closeModal()} />
</TextRight>
<Box pad={{ left: 30 }}>
<FormTitle> Add Assignment</FormTitle>
</Box>
<Box pad={{ left: 40, top: 10 }}>
<StyledFormSubHeading>Essay Settings</StyledFormSubHeading>
<Split>
<StyledFormLabel>Essay Title</StyledFormLabel>
<StyledFormLabel>Select Template</StyledFormLabel>
</Split>
<Split>
<Field name="title" component={InputText} type="text" placeholder="Title" />
<button type="submit">Submit</button>
</Split>
</Box>
</form>
</ModalBody>
</Modal>
</div>)
}
From the redux form documentation, it says that the value and onChange props should be passed for custom components to work. But when I try this, I get a TypeError: _onChange is not a function error
if I omit the onChange event, the input works, but when I submit the form, the input is not present in the data returned

So upon more careful reading of the documentation, the props should be input.value and input.onChange, like so:
const InputText = ({ input: { value, onChange }, width, placeholder }) =>
{
return(<StyledInput value={value} onChange={onChange} />)
}

same as snowflakekiller.
If you want to use the Field on a component only trigger props.onChange but without trigger props.input.onChange, you can extend it:
import {TextArea} from 'react-weui';
import {Field, reduxForm} from 'redux-form';
class MyTextArea extends TextArea {
handleChange(e) {
super.handleChange(e);
if (this.props.input && this.props.input.onChange) this.props.input.onChange(e);
}
}
...
<Field component={MyTextArea} name='xxx' placeholder="Enter lettering" rows="3"
maxLength={200}></Field>

Related

React Beautiful DnD delay when dropping

I am using react-beautiful-dnd. I have it working, except when I drag and drag an item into one of my lists, the item is positioned incorrrectly, has a short delay, then jumps to the correct position.
Here is what it looks like: Link to issue
As you can see, after the item is dropped in a list, the item readjusts itself to fit within the div.
Here is the code for the item:
import React, { useState } from "react";
import styled from "styled-components";
import { Draggable } from "react-beautiful-dnd";
const Container = styled.div`
margin: 0 0 8px 0;
background-color: rgba(140, 240, 255);
`;
const Title = styled.div`
font-size: 1.5rem;
`;
const Gradient = styled.div`
background: black;
height: 2px;
margin: 0.5rem;
`;
const Description = styled.div`
font-size: 1rem;
`;
const Ticket = ({ ticket, setCategories, id, index }) => {
const [isDeleted, setIsDeleted] = useState(false);
const handleDelete = (e) => {
e.preventDefault();
fetch(`/tickets/${ticket.id}`, {
method: "DELETE",
}).then(
fetch("/categories")
.then((r) => r.json())
.then(setCategories)
);
setIsDeleted(true);
};
return (
<Draggable draggableId={id.toString()} index={index}>
{(provided, snapshot) =>
isDeleted ? null : (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Container
style={{
backgroundColor: snapshot.isDragging
? "aquamarine"
: "rgba(140, 240, 255)",
}}
>
<Title>{ticket.title}</Title>
<Gradient></Gradient>
<Description>{ticket.description}</Description>
<button onClick={handleDelete}>Delete</button>
</Container>
</div>
)
}
</Draggable>
);
};
export default Ticket;
And here is for the list:
import React, { useState } from "react";
import styled from "styled-components";
import Ticket from "./Ticket";
import { Droppable } from "react-beautiful-dnd";
import { transformData } from "./Categories";
const Container = styled.div`
background-color: rgba(255, 255, 255, 0.8);
border-radius: 0.25em;
box-shadow: 0 0 0.25em rgba(0, 0, 0, 0.25);
text-align: center;
width: 20rem;
font-size: 1.5rem;
padding: 4px;
margin: 1rem;
`;
const Gradient = styled.div`
background: black;
height: 2px;
margin: 1rem;
`;
const FormContainer = styled.div`
margin: 1rem;
border: 1px solid black;
backgroundColor: rgba(140, 240, 255);
`;
const Button = styled.button`
margin-left: 1rem;
`;
const DropDiv = styled.div`
min-height: 50vh;
padding: 4px;
`;
const Category = ({ category, user, setCategories, id }) => {
const [isClicked, setIsClicked] = useState(false);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTicket = {
title: title,
description: description,
user_id: user.id,
category_id: id,
};
fetch("/tickets", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newTicket),
}).then(
fetch("/categories")
.then((r) => r.json())
.then(transformData)
.then(setCategories)
);
setIsClicked(false);
};
return (
<Container>
{category.title}
<Button onClick={() => setIsClicked(!isClicked)}>Add</Button>
<Gradient></Gradient>
{isClicked ? (
<FormContainer>
<form onSubmit={handleSubmit}>
<label>Title</label>
<input onChange={(e) => setTitle(e.target.value)}></input>
<label>Description</label>
<input onChange={(e) => setDescription(e.target.value)}></input>
<button type="submit">Submit</button>
</form>
</FormContainer>
) : null}
<Droppable droppableId={id.toString()}>
{(provided, snapshot) => (
<DropDiv
{...provided.droppableProps}
ref={provided.innerRef}
style={{
background: snapshot.isDraggingOver ? "lightblue" : "",
}}
>
{category.tickets.map((ticket, index) => {
return (
<Ticket
ticket={ticket}
key={ticket.id}
setCategories={setCategories}
id={ticket.id}
index={index}
/>
);
})}
{provided.placeholder}
</DropDiv>
)}
</Droppable>
</Container>
);
};
export default Category;
I have tried flexbox styling and messed with margin and padding. If i remove the margin and padding it seems to go away, but in beautiful-dnd examples they all have space between items and theres no delay like this. Does anyone have any ideas?
It looks like the placeholder might not have the 8px bottom margin that the rest of the Draggables have.
You'll notice that when you pick up something (without changing it's position) from somewhere other than the end of the list, the list will shift up a bit right away, and when you drop something at the end of a list, you don't see the issue.
The placeholder gets its margins from the item being dragged. You can see this happening by looking at the inline styles on the placeholder element that appears at the end of the droppable while you are dragging.
So, you might want to try putting the provided innerRef, draggableProps, and dragHandleProps on the Ticket Container itself instead of a parent div, as it's possible that because they are on a different element, react-beautiful-dnd isn't taking the margins in to account.
The delay could be caused because your list is changing size while dragging an element, and this provokes the library to recalculate and animate slower.
The solution is to avoid this size change while dragging.
The causes of the issue could be:
---- Cause A ----
The {provided.placeholder} auto inserted in the DOM while dragging doesn't have the same margins/padding that the other Draggable elements
Solution
Be sure to add the styles that separate the elements (margin/padding) to the element that you are applying the provided innerRef, draggableProps, and dragHandleProps because in this way the {provided.placeholder} will inherit those styles.
---- Cause B ----
You are using flexbox or css-grid with a gap property to separate the elements.
Solution
Stop using gap and just add margin to the element with provided innerRef, draggableProps, and dragHandleProps (do not use inline styles but css classes)
Extra: To confirm that size change is the cause
Drag some elements from somewhere other than the end of the list and notice how another element will move up a bit.
Also, when you drop something at the end of a list, you don't see the problem.
I was having this same issue- but as seen in the docs, inline styling with provided props did the trick for me:
const ListItem = ({ item, index }) => {
return (
<Draggable draggableId={item.id} className="draggableItem" index={index}>
{(provided, snapshot) => {
return (
<div
ref={provided.innerRef}
snapshot={snapshot}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
userSelect: "none",
padding: 12,
margin: "0 0 8px 0",
minHeight: "50px",
borderRadius: "4px",
backgroundColor: snapshot.isDragging
? "rgb(247, 247, 247)"
: "#fff",
...provided.draggableProps.style,
}}
>
<div className="cardHeader">Header</div>
<span>Content</span>
<div className="cardFooter">
<span>{item.content}</span>
<div className="author">
{item.id}
<img className="avatar" />
</div>
</div>
</div>
);
}}
</Draggable>
);
};

Change the position of the buttons

I have a project, and in this project I have an interface that contains several fields in addition to two buttons, the first is “Create” and the second is “cancel”, and as it is clear in the picture the two buttons at the end, but I want the two buttons to be in the area in which there is writing in color red
How can I do that?
And the file for the interface is large, so I only put the method of writing the two buttons
main.js:
<div className="p-16 sm:p-24 max-w-2xl ">
<div className={tabValue !== 0 ? "hidden" : ""}>
<ShippingTab />
</div>
</div>
shipping.js:
import React, { useState } from "react";
import InputAdornment from "#material-ui/core/InputAdornment";
import TextField from "#material-ui/core/TextField";
import Grid from "#material-ui/core/Grid";
import "date-fns";
import DateFnsUtils from "#date-io/date-fns";
import {
KeyboardDatePicker,
MuiPickersUtilsProvider,
DatePicker,
} from "#material-ui/pickers";
import { makeStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import CloudUploadIcon from "#material-ui/icons/CloudUpload";
import { addInvoice } from "../../../store/invoiceSlice";
import { motion } from "framer-motion";
import { useDispatch } from "react-redux";
import "react-datepicker/dist/react-datepicker.css";
import Slide from "#material-ui/core/Slide";
import { useSnackbar } from "notistack";
const useStyles = makeStyles((theme) => ({
root: {
"& > *": {
margin: theme.spacing(1),
},
},
input: {
display: "none",
},
button: {
margin: theme.spacing(1),
// padding: theme.spacing(4),
},
}));
function ShippingTab(props) {
const dispatch = useDispatch();
const classes = useStyles();
const [issueDate, setIssueDate] = useState(new Date());
const [dueDate, setDueDate] = useState(new Date());
const [netAmount, setNetAmount] = useState("");
const [taxNumber, setTaxNumber] = useState("");
const [grossAmount, setGrossAmount] = useState("");
const [file, setFile] = useState(null);
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const handleUploadPDFFileMessageClick = () => {
enqueueSnackbar(
"PDF file has been uploaded successfully",
{ variant: "success" },
{
anchorOrigin: {
vertical: "top",
horizontal: "right",
},
},
{ TransitionComponent: Slide }
);
};
const handleCreateInvoiceMessageClick = () => {
enqueueSnackbar(
"Invoice created successfully",
{ variant: "success" },
{
anchorOrigin: {
vertical: "top",
horizontal: "right",
},
},
{ TransitionComponent: Slide }
);
};
const fileSelectedHandler = (event) => {
console.log(event.target.files[0]);
const file = event.target.files[0];
if (event.target && file) {
// formData.append("invoice", file);
setFile(file);
}
};
const uploadHandler = (event) => {
const formData = new FormData();
formData.append("grossAmount", grossAmount);
formData.append("taxNumber", taxNumber);
formData.append("netAmount", netAmount);
formData.append("issueDate", issueDate);
formData.append("dueDate", dueDate);
formData.append("invoice", file);
console.log(
" invoice grossAmount,taxNumber,netAmount,",
file,
grossAmount,
taxNumber,
netAmount
);
console.log("dueDate,issueDate: ", dueDate, issueDate);
// call api
dispatch(addInvoice(formData));
};
const handleissueDateChange = (date) => {
setIssueDate(date);
console.log("date issssssssss: ", date);
console.log("date issssssssss: ", issueDate);
};
const handleDueDateChange = (date) => {
setDueDate(date);
};
const handleNetAmountChange = (event) => {
setNetAmount(event.target.value);
};
const handleTaxAmountChange = (event) => {
setTaxNumber(event.target.value);
};
const handleGrossAmountChange = (event) => {
setGrossAmount(event.target.value);
};
return (
<>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<div className="flex -mx-4">
<KeyboardDatePicker
inputVariant="outlined"
className="mt-8 mb-16"
margin="normal"
id="date-picker-dialog"
label="issue Date"
format="MM/dd/yyyy"
KeyboardButtonProps={{
"aria-label": "change date",
}}
value={issueDate}
onChange={handleissueDateChange}
/>
<KeyboardDatePicker
inputVariant="outlined"
className="mt-8 mb-16 ml-6"
margin="normal"
id="date-picker-dialog"
label="Due Date"
format="MM/dd/yyyy"
KeyboardButtonProps={{
"aria-label": "change date",
}}
value={dueDate}
onChange={handleDueDateChange}
/>
</div>
</MuiPickersUtilsProvider>
<TextField
className="mt-8 mb-16"
label="Net Amount"
id="extraShippingFee"
variant="outlined"
InputProps={{
startAdornment: <InputAdornment position="start">$</InputAdornment>,
}}
value={netAmount}
onChange={handleNetAmountChange}
fullWidth
/>
<TextField
className="mt-8 mb-16"
label="Tax Number"
id="extraShippingFee"
variant="outlined"
InputProps={{
startAdornment: <InputAdornment position="start">$</InputAdornment>,
}}
value={taxNumber}
onChange={handleTaxAmountChange}
fullWidth
/>
<TextField
className="mt-8 mb-16"
label="Gross Amount"
id="extraShippingFee"
variant="outlined"
InputProps={{
startAdornment: <InputAdornment position="start">$</InputAdornment>,
}}
value={grossAmount}
onChange={handleGrossAmountChange}
fullWidth
/>
<div className={classes.root}>
<input
accept="application/pdf"
className={classes.input}
id="contained-button-file"
// multiple
type="file"
onChange={fileSelectedHandler}
/>
<label htmlFor="contained-button-file">
<Button
variant="contained"
color="primary"
size="large"
component="span"
className={classes.button}
startIcon={<CloudUploadIcon />}
>
{/* <Button variant="contained" color="primary" component="span"> */}{" "}
Upload
</Button>
{/* </Button> */}
</label>
</div>
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0, transition: { delay: 0.3 } }}
>
<Grid
container
direction="row-reverse"
justifyContent="flex-start"
alignItems="flex-end"
>
<Grid item>
<Button
className="whitespace-nowrap mx-4"
variant="contained"
color="secondary"
// onClick={handleRemoveProduct}
>
Cancel
</Button>
</Grid>
<Grid item>
<Button
className="whitespace-nowrap mx-4"
variant="contained"
color="secondary"
// disabled={_.isEmpty(dirtyFields) || !isValid}
onClick={(ev) => {
uploadHandler();
ev.stopPropagation();
handleCreateInvoiceMessageClick(ev);
}}
>
Create
</Button>
</Grid>
</Grid>
</motion.div>
</>
);
}
export default ShippingTab;
You can align the two buttons to the left bottom using flex and height: 100%.
All the parents of the two buttons from the motion.div upwards including html and body need to have 100% height.
The grid that contains the buttons and motion.div need to have flex-grow:1 so that they occupy all the free space in their respective container.
html,
body {
height: 100%;
}
div.container {
height: 100%;
display: flex;
flex-direction: column;
}
section.red {
background: red;
padding: 5rem 0
}
section.blue {
background: blue;
padding: 5rem 0
}
section.green {
background: green;
flex-grow: 1;
display: flex;
}
section.green>div {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
}
section.left {
flex-grow: 1;
display: flex;
justify-content: flex-end;
align-items: flex-end;
}
button {
padding: 1rem 2rem;
}
<div class="container">
<section class="red"></section>
<section class="blue"></section>
<section class="green">
<div>
<section class="right">
<button>Right</button>
</section>
<section class="left">
<button>One</button>
<button>TWO</button>
</section>
</div>
</section>
</div>
If ShippingTab has too many parents, it is hard to set 100% height on all of its parent. In that case, you may absolutely postion it either in the body or in its nearest relative container that has / can have 100% height.
The parent is a flex element, so try margin-top: auto; on the children (buttons or container thereof)

Generating a grid-container style when mapping through an array

Summary
I have the following chart image (react-vis):
My React code (below) is creating this with a map through an array. How should I modify the code so the charts go across the page and are contained in some kind of fluid wrapper like this:
What have I looked at/tried?
I have basic understanding of HTML and CSS but would not know how to approach this kind of task and modify the code.
Would I need to use something like this and integrate with the code above?
<div class="grid-container">
<div className="grid-item">1</div>
<div className="grid-item">2</div>
<div className="grid-item">3</div>
<div className="grid-item">4</div>
</div>
I would like to understand an effective way to do this please using CSS, bootstrap or whatever would be considered best practice.
Code:
MyComp.js
import React, { useState, useEffect } from "react"
import Example from "./plotBar.js"
function getJson() {
return fetch("http://secstat.info/testthechartdata3.json")
.then(response => response.json())
.catch(error => {
console.error(error)
})
}
const MyComp = () => {
const [list, setList] = useState([])
useEffect(() => {
getJson().then(list => setList(list))
}, [])
return (
<div>
{list.map((data, index) => (
<Example
key={index}
data={data.map(({ id, count }) => ({
x: id,
y: count,
}))}
/>
))}
</div>
)
}
export default MyComp
plotBar.js
import React from "react"
import {
XYPlot,
XAxis,
YAxis,
VerticalGridLines,
HorizontalGridLines,
VerticalBarSeries,
} from "react-vis"
export default function Example({ data }) {
return (
<XYPlot margin={{ bottom: 70 }} xType="ordinal" width={300} height={300}>
<VerticalGridLines />
<HorizontalGridLines />
<XAxis tickLabelAngle={-45} />
<YAxis />
<VerticalBarSeries data={data} />
</XYPlot>
)
}
The data looks like this:
URL for JSON
http://secstat.info/testthechartdata3.json
You should read about flex and flex-flow, after that it just applying minor styling, this is CSS-in-JS example:
const Item = styled(Example)``;
const Container = styled.div`
display: flex;
flex-flow: row wrap;
background: lightgray;
padding: 0.5rem;
${Item} {
margin: 0.5rem;
padding: 1rem;
background: white;
}
`;
const Item = styled(Example)``;
const Container = styled.div`
display: flex;
flex-flow: row wrap;
background: lightgray;
padding: 0.5rem;
${Item} {
margin: 0.5rem;
padding: 1rem;
background: white;
}
`;
export default function Example({ data, className }) {
return (
<XYPlot className={className} xType="ordinal" width={200} height={200}>
<VerticalGridLines />
<HorizontalGridLines />
<XAxis tickLabelAngle={-45} />
<YAxis />
<VerticalBarSeries data={data} />
</XYPlot>
);
}
const list = // fetch on mount
const MyComp = () => {
return (
<Container>
{list.map((data, index) => (
<Item
key={index}
data={data.map(({ id, count }) => ({
x: id,
y: count,
}))}
/>
))}
</Container>
);
};

Apply different background image for different pages in react js

I want to have a different background image for the login page and home page. If I apply the image to body tag then it gets applied to all the pages. And when I apply it to a specific login page, it works but it only gets applied according to the width and height of the login page.
Applied in body in index.css
body{
background-image: url("http://web3canvas.com/wp-content/uploads/2014/02/littlevisuals-high-quality-hd-banner-images-stock-photos-free-laptop-topview-download.jpg");
background-size: cover;
}
Here is the login page
And this is the home page
And I want background image in only login page.
When I apply background image to only login page, this happens.
I have applied that image in the outermost div of the login page.
<div className={classes.body}>
<React.Fragment>
<CssBaseline />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Avatar className={classes.avatar}>
<LockIcon />
</Avatar>
<Typography variant="headline" className={classes.color}>Sign in</Typography>
<form onSubmit={this.handleSubmit}>
<FormControl margin="normal" required fullWidth>
<Field
component={renderTextField}
label="Email"
id="email"
name="email"
value={email}
onChange={this.handleChange}
fullWidth
/>
</FormControl>
<FormControl margin="normal" required fullWidth>
<Field
value={password}
onChange={this.handleChange}
name="password"
type="password"
id="password"
component={renderTextField}
label="Password"
className={classes.color}
fullWidth />
</FormControl>
<Button type="submit"
disabled={pristine || submitting}
fullWidth
variant="outlined"
color="secondary"
className={classes.submit}
>
Submit
</Button>
</form>
<Button
fullWidth
variant="outlined"
color="primary"
className={classes.submit}>
<Link to="/register">
New User ? Register Here
</Link>
</Button>
<SocialButton
fullWidth
variant="raised"
color="primary"
className="btn btn-danger form-control"
provider='google'
appId='967209745915-djav2bq5ic5r4ad9on3itp5a079s1ruu.apps.googleusercontent.com'
onLoginSuccess={this.handleSocialLogin}
onLoginFailure={this.handleSocialLoginFailure}
key={'google'}
>
Login with Google
</SocialButton>
</Paper>
</main>
</React.Fragment>
</div>
Help me out someone.
Edit --
I am using Material UI
This is the full login page code.
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { userActions } from '../../_actions';
import '../../index.css';
import axios from 'axios';
import { history } from '../../_helpers/history';
import SocialButton from '../../SocialButton';
import compose from 'recompose/compose';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Avatar from '#material-ui/core/Avatar';
import Button from '#material-ui/core/Button';
import CssBaseline from '#material-ui/core/CssBaseline';
import FormControl from '#material-ui/core/FormControl';
import Input from '#material-ui/core/Input';
import InputLabel from '#material-ui/core/InputLabel';
import LockIcon from '#material-ui/icons/LockOutlined';
import Paper from '#material-ui/core/Paper';
import Typography from '#material-ui/core/Typography';
import withStyles from '#material-ui/core/styles/withStyles';
import { Field, reduxForm } from 'redux-form'
import TextField from '#material-ui/core/TextField'
import { IndefiniteObservable } from '../../../node_modules/indefinite-observable';
const styles = theme => ({
layout: {
width: 'auto',
display: 'block', // Fix IE11 issue.
marginLeft: theme.spacing.unit * 3,
marginRight: theme.spacing.unit * 3,
[theme.breakpoints.up(400 + theme.spacing.unit * 3 * 2)]: {
width: 400,
marginLeft: 'auto',
marginRight: 'auto',
},
},
paper: {
marginTop: theme.spacing.unit * 13,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 5}px ${theme.spacing.unit * 5}px`,
background: 'linear-gradient(to bottom, rgba(146, 135, 187, 0.8) 0%, rgba(0,0,0,0.6) 100%)',
transition: 'opacity 0.1s, transform 0.3s cubic-bezier(0.17, -0.65, 0.665, 1.25)',
transform: 'scale(1)'
// backgroundImage: `url("http://web3canvas.com/wp-content/uploads/2014/02/littlevisuals-high-quality-hd-banner-images-stock-photos-free-laptop-topview-download.jpg")`,
},
body: {
backgroundImage: `url("http://web3canvas.com/wp-content/uploads/2014/02/littlevisuals-high-quality-hd-banner-images-stock-photos-free-laptop-topview-download.jpg")`,
backgroundSize: 'cover',
},
color: {
color: 'black',
},
avatar: {
margin: theme.spacing.unit,
backgroundColor: theme.palette.secondary.main,
},
form: {
width: '100%', // Fix IE11 issue.
marginTop: theme.spacing.unit,
},
submit: {
marginTop: theme.spacing.unit * 2,
marginBottom: theme.spacing.unit * 2
},
});
const renderTextField = ({
input,
label,
meta: { touched, error },
...custom
}) => (
<TextField
hintText={label}
floatingLabelText={label}
errorText={touched && error}
{...input}
{...custom}
/>
)
const validate = values => {
const errors = {}
const requiredFields = [
'firstName',
'lastName',
'email',
'favoriteColor',
'notes'
]
requiredFields.forEach(field => {
if (!values[field]) {
errors[field] = 'Required'
}
})
if (
values.email &&
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
) {
errors.email = 'Invalid email address'
}
return errors
}
class LoginPage extends Component {
constructor(props) {
super(props);
// reset login status
this.props.dispatch(userActions.logout());
this.state = {
email: '',
password: '',
submitted: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleSocialLogin = this.handleSocialLogin.bind(this);
this.handleSocialLoginFailure = this.handleSocialLoginFailure.bind(this);
}
handleSocialLogin(user) {
console.log(user)
debugger;
if (user) {
this.props.dispatch(userActions.googlelogin(user));
}
}
handleSocialLoginFailure = (err) => {
console.error(err)
}
handleChange(e) {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit(e) {
debugger;
e.preventDefault();
this.setState({ submitted: true });
const { email, password } = this.state;
const { dispatch } = this.props;
if (email && password) {
dispatch(userActions.login(email, password));
}
}
render() {
const { classes } = this.props;
const { email, password, submitted } = this.state;
const { handleSubmit, pristine, reset, submitting } = this.props
return (
<div className={classes.body}>
<React.Fragment>
<CssBaseline />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Avatar className={classes.avatar}>
<LockIcon />
</Avatar>
<Typography variant="headline" className={classes.color}>Sign in</Typography>
<form onSubmit={this.handleSubmit}>
<FormControl margin="normal" required fullWidth>
<Field
component={renderTextField}
label="Email"
id="email"
name="email"
value={email}
onChange={this.handleChange}
fullWidth
/>
</FormControl>
<FormControl margin="normal" required fullWidth>
<Field
value={password}
onChange={this.handleChange}
name="password"
type="password"
id="password"
component={renderTextField}
label="Password"
className={classes.color}
fullWidth />
</FormControl>
<Button type="submit"
disabled={pristine || submitting}
fullWidth
variant="outlined"
color="secondary"
className={classes.submit}
>
Submit
</Button>
</form>
<Button
fullWidth
variant="outlined"
color="primary"
className={classes.submit}>
<Link to="/register">
New User ? Register Here
</Link>
</Button>
<SocialButton
fullWidth
variant="raised"
color="primary"
className="btn btn-danger form-control"
provider='google'
appId='967209745915-djav2bq5ic5r4ad9on3itp5a079s1ruu.apps.googleusercontent.com'
onLoginSuccess={this.handleSocialLogin}
onLoginFailure={this.handleSocialLoginFailure}
key={'google'}
>
Login with Google
</SocialButton>
</Paper>
</main>
</React.Fragment>
</div>
);
}
}
LoginPage.propTypes = {
classes: PropTypes.object.isRequired,
};
function mapStateToProps(state) {
const { loggingIn } = state.authentication;
return {
loggingIn
};
}
export default compose(
withStyles(styles),
connect(mapStateToProps, null),
reduxForm({
form: 'MaterialUIForm',
validate
})
)(LoginPage);
I will put the code on stackblitz in a few minutes
Create unique classNames for each component, wrap other content inside the classNames div element.
for Login component
render() {
return (
<div className="login">
//other code
</div>
)
}
for Home component
render() {
return (
<div className="home">
//other code
</div>
)
}
In index.css apply the color for the classNames.
.login {
background: url("http://web3canvas.com/wp-content/uploads/2014/02/littlevisuals-high-quality-hd-banner-images-stock-photos-free-laptop-topview-download.jpg") no-repeat;
background-size: cover;
min-width: 100%;
min-height: 100%;
}
.home {
background: url("https://www.w3schools.com/cssref/mountain.jpg") no-repeat; background-size: cover;
min-width: 100%;
min-height: 100%;
}
If you want to use styled Components, which is fully compatible with Material UI,
you can just replace the <div className={classes.body}> with your custom <StyledBody>
import styled from 'styled-components';
const StyledBody = styled.div`
background: url('http://web3canvas.com/wp-content/uploads/2014/02/littlevisuals-high-quality-hd-banner-images-stock-photos-free-laptop-topview-download.jpg');
background-size: cover;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
`;
export default function Login(props) {
return (
<StyledBody>
// your login component
</StyledBody>
);
}
If you haven't already, be sure to install styled-components first with npm i styled-components
Of course the same css code would work however you want to use it.
document.body.style.backgroundImage = "url('image url')";

How to upload file with redux-form?

I can't get correct value into the store when trying to upload a file. Instead of file content, I get something like { 0: {} }.
Here's the code:
const renderInput = field => (
<div>
<input {...field.input} type={field.type}/>
{
field.meta.touched &&
field.meta.error &&
<span className={styles.error}>{field.meta.error}</span>
}
</div>
);
render() {
...
<form className={styles.form} onSubmit={handleSubmit(submit)}>
<div className={styles.interface}>
<label>userpic</label>
<Field
name="userpic"
component={renderInput}
type="file"
/>
</div>
<div>
<button type="submit" disabled={submitting}>Submit</button>
<div>
</form>
...
}
All the examples on the web that I found were made using v5 of redux-form.
How do I do file input in redux-form v6?
Create a Field Component like:
import React, {Component} from 'react'
export default class FieldFileInput extends Component{
constructor(props) {
super(props)
this.onChange = this.onChange.bind(this)
}
onChange(e) {
const { input: { onChange } } = this.props
onChange(e.target.files[0])
}
render(){
const { input: { value } } = this.props
const {input,label, required, meta, } = this.props //whatever props you send to the component from redux-form Field
return(
<div><label>{label}</label>
<div>
<input
type='file'
accept='.jpg, .png, .jpeg'
onChange={this.onChange}
/>
</div>
</div>
)
}
}
Pass this component to the Field component where you needed. No need of additional Dropzone or other libraries if you are after a simple file upload functionality.
My example of redux form input wrapper with Dropzone
import React, {Component, PropTypes} from 'react';
import Dropzone from 'react-dropzone';
import { Form } from 'elements';
import { Field } from 'redux-form';
class FileInput extends Component {
static propTypes = {
dropzone_options: PropTypes.object,
meta: PropTypes.object,
label: PropTypes.string,
classNameLabel: PropTypes.string,
input: PropTypes.object,
className: PropTypes.string,
children: PropTypes.node,
cbFunction: PropTypes.func,
};
static defaultProps = {
className: '',
cbFunction: () => {},
};
render() {
const { className, input: { onChange }, dropzone_options, meta: { error, touched }, label, classNameLabel, children, name, cbFunction } = this.props;
return (
<div className={`${className}` + (error && touched ? ' has-error ' : '')}>
{label && <p className={classNameLabel || ''}>{label}</p>}
<Dropzone
{...dropzone_options}
onDrop={(f) => {
cbFunction(f);
return onChange(f);
}}
className="dropzone-input"
name={name}
>
{children}
</Dropzone>
{error && touched ? error : ''}
</div>
);
}
}
export default props => <Field {...props} component={FileInput} />;
Hot to use it:
<FileInput
name="add_photo"
label="Others:"
classNameLabel="file-input-label"
className="file-input"
dropzone_options={{
multiple: false,
accept: 'image/*'
}}
>
<span>Add more</span>
</FileInput>
Another way to do it that will render a preview image (the below example uses React 16+ syntax and only accepts a single image file to send to an API; however, with some minor tweaks, it can also scale to multiple images and other fields inputs):
Working example: https://codesandbox.io/s/m58q8l054x
Working example (outdated): https://codesandbox.io/s/8kywn8q9xl
Before:
After:
containers/UploadForm.js
import React, { Component } from "react";
import { Form, Field, reduxForm } from "redux-form";
import DropZoneField from "../components/dropzoneField";
const imageIsRequired = value => (!value ? "Required" : undefined);
class UploadImageForm extends Component {
state = { imageFile: [] };
handleFormSubmit = formProps => {
const fd = new FormData();
fd.append("imageFile", formProps.imageToUpload.file);
// append any additional Redux form fields
// create an AJAX request here with the created formData
alert(JSON.stringify(formProps, null, 4));
};
handleOnDrop = (newImageFile, onChange) => {
const imageFile = {
file: newImageFile[0],
name: newImageFile[0].name,
preview: URL.createObjectURL(newImageFile[0]),
size: newImageFile[0].size
};
this.setState({ imageFile: [imageFile] }, () => onChange(imageFile));
};
resetForm = () => this.setState({ imageFile: [] }, () => this.props.reset());
render = () => (
<div className="app-container">
<h1 className="title">Upload An Image</h1>
<hr />
<Form onSubmit={this.props.handleSubmit(this.handleFormSubmit)}>
<Field
name="imageToUpload"
component={DropZoneField}
type="file"
imagefile={this.state.imageFile}
handleOnDrop={this.handleOnDrop}
validate={[imageIsRequired]}
/>
<button
type="submit"
className="uk-button uk-button-primary uk-button-large"
disabled={this.props.submitting}
>
Submit
</button>
<button
type="button"
className="uk-button uk-button-default uk-button-large"
disabled={this.props.pristine || this.props.submitting}
onClick={this.resetForm}
style={{ float: "right" }}
>
Clear
</button>
</Form>
<div className="clear" />
</div>
);
}
export default reduxForm({ form: "UploadImageForm" })(UploadImageForm);
components/dropzoneField.js
import React from "react";
import PropTypes from "prop-types";
import DropZone from "react-dropzone";
import ImagePreview from "./imagePreview";
import Placeholder from "./placeholder";
import ShowError from "./showError";
const DropZoneField = ({
handleOnDrop,
input: { onChange },
imagefile,
meta: { error, touched }
}) => (
<div className="preview-container">
<DropZone
accept="image/jpeg, image/png, image/gif, image/bmp"
className="upload-container"
onDrop={file => handleOnDrop(file, onChange)}
>
{({ getRootProps, getInputProps }) =>
imagefile && imagefile.length > 0 ? (
<ImagePreview imagefile={imagefile} />
) : (
<Placeholder
error={error}
touched={touched}
getInputProps={getInputProps}
getRootProps={getRootProps}
/>
)
}
</DropZone>
<ShowError error={error} touched={touched} />
</div>
);
DropZoneField.propTypes = {
error: PropTypes.string,
handleOnDrop: PropTypes.func.isRequired,
imagefile: PropTypes.arrayOf(
PropTypes.shape({
file: PropTypes.file,
name: PropTypes.string,
preview: PropTypes.string,
size: PropTypes.number
})
),
label: PropTypes.string,
onChange: PropTypes.func,
touched: PropTypes.bool
};
export default DropZoneField;
components/imagePreview.js
import React from "react";
import PropTypes from "prop-types";
const ImagePreview = ({ imagefile }) =>
imagefile.map(({ name, preview, size }) => (
<div key={name} className="render-preview">
<div className="image-container">
<img src={preview} alt={name} />
</div>
<div className="details">
{name} - {(size / 1024000).toFixed(2)}MB
</div>
</div>
));
ImagePreview.propTypes = {
imagefile: PropTypes.arrayOf(
PropTypes.shape({
file: PropTypes.file,
name: PropTypes.string,
preview: PropTypes.string,
size: PropTypes.number
})
)
};
export default ImagePreview;
components/placeholder.js
import React from "react";
import PropTypes from "prop-types";
import { MdCloudUpload } from "react-icons/md";
const Placeholder = ({ getInputProps, getRootProps, error, touched }) => (
<div
{...getRootProps()}
className={`placeholder-preview ${error && touched ? "has-error" : ""}`}
>
<input {...getInputProps()} />
<MdCloudUpload style={{ fontSize: 100, paddingTop: 85 }} />
<p>Click or drag image file to this area to upload.</p>
</div>
);
Placeholder.propTypes = {
error: PropTypes.string,
getInputProps: PropTypes.func.isRequired,
getRootProps: PropTypes.func.isRequired,
touched: PropTypes.bool
};
export default Placeholder;
components/showError.js
import React from "react";
import PropTypes from "prop-types";
import { MdInfoOutline } from "react-icons/md";
const ShowError = ({ error, touched }) =>
touched && error ? (
<div className="error">
<MdInfoOutline
style={{ position: "relative", top: -2, marginRight: 2 }}
/>
{error}
</div>
) : null;
ShowError.propTypes = {
error: PropTypes.string,
touched: PropTypes.bool
};
export default ShowError;
styles.css
img {
max-height: 240px;
margin: 0 auto;
}
.app-container {
width: 500px;
margin: 30px auto;
}
.clear {
clear: both;
}
.details,
.title {
text-align: center;
}
.error {
margin-top: 4px;
color: red;
}
.has-error {
border: 1px dotted red;
}
.image-container {
align-items: center;
display: flex;
width: 85%;
height: 80%;
float: left;
margin: 15px 10px 10px 37px;
text-align: center;
}
.preview-container {
height: 335px;
width: 100%;
margin-bottom: 40px;
}
.placeholder-preview,
.render-preview {
text-align: center;
background-color: #efebeb;
height: 100%;
width: 100%;
border-radius: 5px;
}
.upload-container {
cursor: pointer;
height: 300px;
}
I managed to do it with redux-form on material-ui wrapping TextField like this:
B4 edit:
After edit:
<Field name="image" component={FileTextField} floatingLabelText={messages.chooseImage} fullWidth={true} />
with component defined as:
const styles = {
button: {
margin: 12
},
exampleImageInput: {
cursor: 'pointer',
position: 'absolute',
top: 0,
bottom: 0,
right: 0,
left: 0,
width: '100%',
opacity: 0
},
FFS:{
position: 'absolute',
lineHeight: '1.5',
top: '38',
transition: 'none',
zIndex: '1',
transform: 'none',
transformOrigin: 'none',
pointerEvents: 'none',
userSelect: 'none',
fontSize: '16',
color: 'rgba(0, 0, 0, 0.8)',
}
};
export const FileTextField = ({
floatingLabelText,
fullWidth,
input,
label,
meta: { touched, error },
...custom })=>{
if (input.value && input.value[0] && input.value[0].name) {
floatingLabelText = input.value[0].name;
}
delete input.value;
return (
<TextField
hintText={label}
fullWidth={fullWidth}
floatingLabelShrinkStyle={styles.FFS}
floatingLabelText={floatingLabelText}
inputStyle={styles.exampleImageInput}
type="file"
errorText={error}
{...input}
{...custom}
/>
)
}
If you need base64 encoding to send it to your backend, here is a modified version that worked for me:
export class FileInput extends React.Component {
getBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
onFileChange = async (e) => {
const { input } = this.props
const targetFile = e.target.files[0]
if (targetFile) {
const val = await this.getBase64(targetFile)
input.onChange(val)
} else {
input.onChange(null)
}
}
render() {
return (
<input
type="file"
onChange={this.onFileChange}
/>
)
}
}
Then your field component would look like:
<Field component={FileInput} name="primary_image" type="file" />
For React >= 16 and ReduxForm >= 8 (tested version are 16.8.6 for React and 8.2.5)
works following component.
(Solution posted in related GitHub issue by DarkBitz)
const adaptFileEventToValue = delegate => e => delegate(e.target.files[0]);
const FileInput = ({
input: { value: omitValue, onChange, onBlur, ...inputProps },
meta: omitMeta,
...props
}) => {
return (
<input
onChange={adaptFileEventToValue(onChange)}
onBlur={adaptFileEventToValue(onBlur)}
type="file"
{...props.input}
{...props}
/>
);
};
export const FileUpload = (props) => {
const { handleSubmit } = props;
const onFormSubmit = (data) => {
console.log(data);
}
return (
<form onSubmit={handleSubmit(onFormSubmit)}>
<div>
<label>Attachment</label>
<Field name="attachment" component={FileInput} type="file"/>
</div>
<button type="submit">Submit</button>
</form>
)
}
With Redux Form
const { handleSubmit } = props;
//make a const file to hold the file prop.
const file = useRef();
// create a function to replace the redux-form input-file value to custom value.
const fileUpload = () => {
// jsx to take file input
// on change store the files /file[0] to file variable
return (
<div className='file-upload'>
<input
type='file'
id='file-input'
accept='.png'
onChange={(ev) => {
file.current = ev.target.files;
}}
required
/>
</div>
);
};
//catch the redux-form values!
//loop through the files and add into formdata
//form data takes key and value
//enter the key name as multer-config fieldname
//then add remaining data into the formdata
//make a request and send data.
const onSubmitFormValues = (formValues) => {
const data = new FormData();
for (let i = 0; i < file.current.length; i++) {
data.append("categoryImage", file.current[i]);
}
data.append("categoryName", formValues.categoryName);
Axios.post("http://localhost:8080/api/v1/dev/addNewCategory", data)
.then((response) => console.log(response))
.catch((err) => console.log(err));
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You can also use react-dropzone for this purpose. The below code worked fine for me
filecomponent.js
import React from 'react'
import { useDropzone } from 'react-dropzone'
function MyDropzone(props) {
const onDrop = (filesToUpload) => {
return props.input.onChange(filesToUpload[0]);
}
const onChange = (filesToUpload) => {
return props.input.onChange(filesToUpload[0]);
}
const { getRootProps, getInputProps } = useDropzone({ onDrop });
return (
<div {...getRootProps()}>
<input {...getInputProps()} onChange={e => onChange(e.target.files)} />
<p> Drop or select yout file</p>
</div>
)
}
export default MyDropzone;
In form use this
<Field
name="myfile"
component={renderFile}
/>

Resources