formik-material-ui not working for TogglleButtonGroup Component - reactjs

In my React code, along with other input types, I also have to use ToggleButtonGroup component of material-ui. This component is not directly supported by formik-material-ui and I've been trying to write a custom wrapper without much success. This is how my component looks(formikToggleButtonGroup.tsx):
import * as React from 'react';
import MuiToggleButtonGroup, {
ToggleButtonGroupProps as MuiToggleButtonGroupProps,
} from '#material-ui/lab/ToggleButtonGroup';
import { FieldProps } from 'formik';
export interface ToggleButtonGroupProps
extends FieldProps,
Omit<MuiToggleButtonGroupProps, 'name' | 'value'> {}
export function fieldToToggleButtonGroup({
field,
// Exclude Form
form,
...props
}: ToggleButtonGroupProps): MuiToggleButtonGroupProps {
return {
...props,
...field,
};
}
export default function ToggleButtonGroup(props: ToggleButtonGroupProps) {
return <MuiToggleButtonGroup {...fieldToToggleButtonGroup(props)} />;
}
ToggleButtonGroup.displayName = 'FormikMaterialUIToggleButtonGroup';`
Then, I'm trying to use it like this:
import Layout from '../../components/layout'
import Header from '../../components/header'
import ToggleButtonGroup from '../../components/formikToggleButtonGroup.tsx'
import React, { useState } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import Container from '#material-ui/core/Container';
import CssBaseline from '#material-ui/core/CssBaseline';
import Grid from '#material-ui/core/Grid';
import Box from '#material-ui/core/Box';
import ToggleButton from '#material-ui/lab/ToggleButton';
import { withStyles } from '#material-ui/core/styles';
import { green, orange } from '#material-ui/core/colors';
import Radio from '#material-ui/core/Radio';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import FormControl from '#material-ui/core/FormControl';
import FormLabel from '#material-ui/core/FormLabel';
import * as Yup from "yup";
import { Formik, Form, Field} from 'formik';
import { TextField, RadioGroup } from 'formik-material-ui';
import ToggleButtonGroup from '../../components/formikToggleButtonGroup.tsx'
import ToggleButton from '#material-ui/lab/ToggleButton';
const StyledButton = withStyles((theme)=>({
root: {
background: theme.palette.background.paper,
color: theme.palette.text.primary,
marginBottom: theme.spacing(3),
marginRight: theme.spacing(4),
borderRadius: '0',
borderWidth: '1px',
borderColor: theme.palette.divider,
borderLeft:'1px solid rgba(0, 0, 0, 0.12) !important',
padding: '0 30px',
fontSize:'0.7rem',
fontWeight:'400',
'&$selected': {
color: theme.palette.background.default,
backgroundColor: theme.palette.warning.main,
},
'&:hover': {
color: theme.palette.background.default,
backgroundColor: theme.palette.warning.light,
},
'&$selected:hover': {
color: theme.palette.background.default,
backgroundColor: theme.palette.warning.light,
},
},
selected:{
color: theme.palette.background.default,
backgroundColor: theme.palette.warning.main,
},
hover:{
color: theme.palette.background.default,
backgroundColor: theme.palette.warning.light,
},
label: {
textTransform: 'capitalize',
},
}))(ToggleButton);
export default function GetUserInfo() {
const classes = useStyles();
const [selectedValue, setSelectedValue] = useState('yes');
const handleRadioChange = (event) => {
setSelectedValue(event.target.value);
};
const handleSubmit = (evt) => {
evt.preventDefault();
alert(`Submitting Name ${name} ${phone} ${email}`)
}
const handleChange = (event, newAlignment) => {
alert(newAlignment)
if (newAlignment !== null) {
setGrade(newAlignment);
}
};
return (
<Layout title="My first Next page" meta_desc="Best online coding classes for kids & children">
<CssBaseline />
<Header />
<Container component="main" >
<Grid container spacing={4}>
<Grid item xs={12} sm={8} className={classes.boxStyle}>
<Box mx="auto" p={2}>
<Formik
initialValues={initialValues}
validate={
values => {
const errors = {};
if (!values.parentEmail) {
errors.parentEmail = 'Required';
} else if (
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.parentEmail)
) {
errors.parentEmail = 'Invalid email address';
} else if (!values.parentName){
errors.parentName = 'Required';
} else if (!values.countryCode){
errors.countryCode = 'Required';
} else if (!values.phone){
errors.phone = 'Required';
} else if (!values.childName){
errors.childName = 'Required';
} else if (!values.grade){
errors.grade = 'Required';
} else if (!values.computer){
errors.computer = 'Required';
}
return errors;
}
}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
setSubmitting(false);
alert(JSON.stringify(values, null, 2));
}, 500);
}}
>
{({ submitForm, isSubmitting }) => (
<Form className={classes.root} autoComplete="off">
<Box>
<Field
component={TextField}
fullWidth
name="childName"
onChange={e => setChildName(e.target.value)}
required id="standard-required" label="Kid's Name"
helperText="Certificate will be issued with this name"/>
</Box>
<Box mt={3} className={classes.labelFont}>
Kid's Grade?
</Box>
<Box mt={2}>
<ToggleButtonGroup
component={ToggleButtonGroup}
mt={7}
name="grade"
//value={grade}
exclusive="true"
//onChange={handleChange}
aria-label="text alignment"
>
<StyledButton className={classes.buttonMargin}
value="1" aria-label="left aligned">
<label class="btn btn-secondary m-2 rounded">
Grade <br/> <strong>1 - 3</strong>
</label>
</StyledButton>
<StyledButton className={classes.buttonMargin} value="2" aria-label="centered">
<label class="btn btn-secondary m-2 rounded ">
Grade <br/> <strong>4 - 6</strong>
</label>
</StyledButton>
<StyledButton className={classes.buttonMargin} value="3" aria-label="right aligned">
<label class="btn btn-secondary m-2 rounded">
Grade <br/> <strong>7 - 9</strong>
</label>
</StyledButton>
<StyledButton className={classes.buttonMargin} value="4" aria-label="justified">
<label class="btn btn-secondary m-2 rounded">
Grade <br/><strong>10 - 12</strong>
</label>
</StyledButton>
</ToggleButtonGroup>
</Box>
<Button
disabled={isSubmitting}
onClick={submitForm}
//type="submit"
//onClick={handleSubmit}
fullWidth
variant="contained"
color="primary"
className={classes.submit}>Let's Start
</Button>
</Form>
)}
</Formik>
</Box>
</Grid>
</Grid>
</Container>
</Layout>
)
}
The problem I am facing is that value of ToggleButtonGroup is not getting set by Formik. It always shows the initial value.

I was able to make formik work with ToggleButtonGroup by manually handling its onChange event, and manually updating the formik instance:
export default function MyComponent() {
const formik = useFormik({
initialValues: {
roleType: 0,
},
onSubmit: async (values) => {
// send ajax request
},
});
// handler for ToggleButtonGroup
const handleRoleChange = (event: any, role: string) => {
// manually update formik
formik.setFieldValue('roleType', role);
};
return (
<form noValidate onSubmit={formik.handleSubmit}>
<ToggleButtonGroup
exclusive
id="roleType"
value={formik.values.roleType}
onChange={handleRoleChange}
>
<ToggleButton value={0}>
<PersonIcon />
<div>Role 1</div>
</ToggleButton>
<ToggleButton value={1}>
<RestaurantIcon />
<div>Role 2</div>
</ToggleButton>
</ToggleButtonGroup>
<Button type="submit">
Submit
</Button>
</form>
);
}

Related

Type '{}' is not assignable to type 'IProps'

I am using react with formik and typescript in my project. I am using withFormik HOC in my forms, code is below
import React from "react";
//Libraries import........
import {
Container,
Box,
Link,
Grid,
Checkbox,
FormControlLabel,
TextField,
CssBaseline,
Avatar,
Typography,
makeStyles,
Theme,
} from "#material-ui/core";
import LockOutlinedIcon from "#material-ui/icons/LockOutlined";
import { Form, FormikProps, FormikValues, useFormik, withFormik } from "formik";
import * as Yup from "yup";
//Components import........
import { Button } from "#components/button";
//Interfaces ..........
interface FormValues {
email?:string,
password?:string
}
interface IProps extends FormikProps<FormValues> {
// onSubmit(e:React.FormEvent<HTMLInputElement>):void
}
const useStyles = makeStyles((theme: Theme) => ({
paper: {
marginTop: theme.spacing(8),
display: "flex",
flexDirection: "column",
alignItems: "center",
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
const Login: React.FC<IProps> = (props) => {
const classes = useStyles();
const { handleSubmit, handleBlur, handleChange, handleReset, errors } = props;
console.log(errors);
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form className={classes.form} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoFocus
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign In
</Button>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</div>
<Box mt={8}></Box>
</Container>
);
};
export const LoginView = withFormik<IProps, FormValues>({
enableReinitialize: true,
mapPropsToValues: (props) => {
console.log(props);
return { email: "", password: "" };
},
validationSchema: Yup.object({
// email: Yup.string().required(),
}),
handleSubmit: (values) => {
console.log(values);
},
})(Login);
and then when in my project i import and use LoginView inside my pages like
import React from "react";
import { LoginView } from "#components/user/login";
const Home: React.FC = (props: any) => {
return (
<div
style={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}
>
<LoginView />
</div>
);
};
export default Home;
i get an error where i write
Type '{}' is not assignable to type 'IProps'. Type '{}' is missing
the following properties from type 'FormikState': values,
errors, touched, isSubmitting, and 2 more.
First Generic Type provided to withFormik shouldn't extend from FormikValues.
Define IProps like this
interface IProps {
// onSubmit(e:React.FormEvent<HTMLInputElement>):void
}
See this https://codesandbox.io/s/stackoverflow-69143042-wlvz9

React and Antd, form data destroys on fulllscreen drawer out of focus

I have tried to remove the destroy on close, use same form hook, different form hook, removed from hook, but nothing worked.
I have consoled logged the form data on show modal and close modal. Show modal has the form data but inside the log of close modal the data is reset to undefined. As soon as the modal opens the Data resets to null.
In the following code you can see the creation of Full screen modal which is made using AntDrawer:
import React, { useEffect, useState } from "react";
import FullscreenDialog from "Components/FullScreenDialog";
import Form from "Components/Form";
import { Input, Typography, Button, Space, Menu, Dropdown } from "antd";
import { PlusCircleOutlined, CloseOutlined, DownOutlined } from "#ant-design/icons";
import HeaderInput from "Components/Inputs/HeaderInput";
import Table from "Components/Table";
import styled from "styled-components";
import { Services } from "Utilities/network";
import DetailsModal from "./DetailsModal";
const RootWrapper = styled.div`
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
`;
const CreateVoucher = ({ visible, initalValue = {}, onSuccess = (e) => e, onClose = (e) => e }) => {
const [form] = Form.useForm();
const [voucherData, setVoucherData] = useState([]);
const [newModal, setNewModal] = useState(false);
const [formData, setFormData] = useState({});
const handleSubmit = async () => {
const { _id, name, header } = form.getFieldsValue(true);
if (name && header && voucherData) {
let resp = await Services.createVoucher({
_id,
name,
header,
voucherData,
});
if (resp.key) onSuccess();
}
};
if (initalValue) {
const { _id, name, header, voucherData } = initalValue;
form.setFieldsValue({
_id,
name,
header,
voucherData,
});
}
useEffect(() => {
const { voucherData } = initalValue;
if (voucherData && voucherData.length) setVoucherData(voucherData);
else setVoucherData([]);
}, [initalValue]);
const handleDelete = (index) => {
const tableData = voucherData.filter((data, dataIndex) => {
if (index !== dataIndex) return data;
return null;
});
setVoucherData(tableData);
};
return (
<>
<FullscreenDialog visible={visible} onClose={onClose}>
<FullscreenDialog.Paper>
<RootWrapper>
<Typography.Text strong style={{ fontSize: "30px" }}>
Generate Voucher
</Typography.Text>
<Form form={form}>
<Space direction="vertical" style={{ width: "100%" }}>
<Form.Item label="Voucher ID" name="name" rules={[{ required: true, message: "Please input ID" }]}>
<Input />
</Form.Item>
<HeaderInput
fieldProps={{
name: "header",
label: "Inoivce Header",
rules: [{ required: true, message: "Please select header" }],
}}
itemProps={{ placeholder: "Select Header for Voucher" }}
/>
<Space style={{ float: "right", marginTop: "20px" }}>
<Button
type="primary"
shape="round"
icon={<PlusCircleOutlined />}
onClick={() => {
setFormData(form.getFieldsValue(true));
setNewModal(true);
}}
>
Add Details
</Button>
</Space>
<Table
dataSource={voucherData}
count={voucherData?.length || 0}
columns={[
{
title: "Label",
key: "label",
dataIndex: "label",
render: (_, record) => record.label,
},
{
title: "Details",
key: "detail",
dataIndex: "detail",
render: (_, record) => record.detail,
},
{
align: "right",
render: (_, record, index) => {
return (
<Dropdown
trigger={["click"]}
overlay={
<Menu>
<Menu.Item
style={{ color: "red " }}
onClick={() => {
handleDelete(index);
}}
>
<CloseOutlined /> Delete
</Menu.Item>
</Menu>
}
>
<Button type="primary">
Edit
<DownOutlined />
</Button>
</Dropdown>
);
},
},
]}
/>
<Space style={{ float: "right", marginTop: "30px" }}>
<Button type="outline" onClick={onClose}>
Cancel
</Button>
<Button type="primary" htmlType="submit" onClick={handleSubmit}>
Submit
</Button>
</Space>
</Space>
</Form>
</RootWrapper>
</FullscreenDialog.Paper>
</FullscreenDialog>
<DetailsModal
visible={newModal}
onClose={() => {
setNewModal(false);
console.log(formData);
form.setFieldsValue({ name: "2222" });
console.log(form.getFieldsValue(true));
}}
onSuccess={(data) => {
setVoucherData([...voucherData, data]);
setNewModal(false);
form.setFieldsValue(formData);
}}
/>
</>
);
};
export default CreateVoucher;
Below is the code for modal to use another form:
import React from "react";
import { Modal, Space, Input, Button } from "antd";
import Form from "Components/Form";
import styled from "styled-components";
const RootWrapper = styled.div`
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
`;
const DetailsModal = ({ visible, onClose = (e) => e, onSuccess = (e) => e }) => {
const [form] = Form.useForm();
return (
<Modal
visible={visible}
destroyOnClose={true}
onClose={() => {
form.resetFields();
onClose();
}}
onCancel={() => {
form.resetFields();
onClose(false);
}}
title="Add Details"
footer={false}
>
<Form form={form}>
<Space direction="vertical" style={{ width: "100%" }}>
<RootWrapper>
<Form.Item name="label" label="Label" rules={[{ required: true, message: "Please input label" }]}>
<Input />
</Form.Item>
<Form.Item name="detail" label="Details" rules={[{ required: true, message: "Please input details" }]}>
<Input />
</Form.Item>
</RootWrapper>
<Space style={{ float: "right", marginTop: "20px" }}>
<Button
type="outline"
onClick={() => {
form.resetFields();
onClose();
}}
>
Cancel
</Button>
<Button
type="primary"
htmlType="submit"
onClick={() => {
const { detail, label } = form.getFieldsValue(true);
onSuccess({ detail, label });
}}
>
Submit
</Button>
</Space>
</Space>
</Form>
</Modal>
);
};
export default DetailsModal;

Redux initial value null while clicking on edit button

I am trying to edit form. After first rendering I am not getting existing data from database. Because of state is null. If I click on second button first data is appearing. ( Because of state is null. If I click on second button first data is appearing. )
Reducer:
const intialState ={ channel:null}
Please click here for the image
//form Component
import React, { Fragment } from "react";
import Button from "#material-ui/core/Button";
import TextField from "#material-ui/core/TextField";
import Dialog from "#material-ui/core/Dialog";
import DialogActions from "#material-ui/core/DialogActions";
import DialogContent from "#material-ui/core/DialogContent";
import DialogTitle from "#material-ui/core/DialogTitle";
const FormChannel = ({ channelData, ouSubmit, open }) => {
let [formData, setFromData] = React.useState({
channel: channelData ? channelData.channel : "",
channelName: channelData ? channelData.channelName : "",
introduction: channelData ? channelData.introduction : "",
language: channelData ? channelData.language : "",
keywords: channelData ? channelData.keywords : ""
});
const { channel, channelName, introduction, language, keywords } = formData;
const handleClose = () => {
ouSubmit(formData);
setFromData({
channel: "",
channelName: "",
introduction: "",
language: "",
keywords: ""
});
};
const handleChange = channel => e => {
setFromData({ ...formData, [channel]: e.target.value });
};
const view = (
<Fragment>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Create Channel</DialogTitle>
<DialogContent>
<TextField
autoFocus
value={channel}
onChange={handleChange("channel")}
id="channel"
label="Name"
type="emachannelil"
margin="normal"
variant="outlined"
fullWidth
/>
<TextField
value={channelName}
onChange={handleChange("channelName")}
id="channelName"
label="Channel Name"
type="text"
margin="normal"
variant="outlined"
fullWidth
/>
<TextField
label="Language"
id="language"
value={language}
onChange={handleChange("language")}
type="text"
margin="normal"
variant="outlined"
fullWidth
/>
<TextField
value={introduction}
onChange={handleChange("introduction")}
id="introduction"
label="Introduction"
type="text"
margin="normal"
variant="outlined"
fullWidth
/>
<TextField
value={keywords}
onChange={handleChange("keywords")}
id="kewords"
label="Keywords"
type="text"
margin="normal"
variant="outlined"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleClose} color="primary" variant="contained">
Create
</Button>
</DialogActions>
</Dialog>
</Fragment>
);
return <Fragment>{view}</Fragment>;
};
export default FormChannel
Edit
import React, { Fragment } from "react";
import { connect } from "react-redux";
import { Button } from "#material-ui/core";
import FormChannel from "./Form";
const EditChannel = ({ channels: { channel }, open, setOpen }) => {
console.log(channel);
return (
<Fragment>
<FormChannel
open={open}
channelData={channel}
ouSubmit={formData => {
setOpen(false);
console.log(formData);
}}
/>
</Fragment>
);
};
const mapStateToProps = (state, props) => {
console.log(state);
return {
channels: state.channels
};
};
export default connect(mapStateToProps)(EditChannel);
card
import React, { Fragment } from "react";
import { makeStyles } from "#material-ui/core/styles";
import { Typography, Button } from "#material-ui/core";
import Avatar from "#material-ui/core/Avatar";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { channelFollow, channelFetchById } from "../../../redux/action/channel";
import EditChannel from "../../Channels/List/Edit";
const useStyles = makeStyles(theme => ({
card: {
flexGrow: 1
},
img: {
height: "60%",
overflow: "hidden",
width: "100%"
},
link: {
textDecoration: "none",
color: "black"
},
link1: {
textDecoration: "none",
color: "white"
},
root: {
display: "flex",
"& > *": {
margin: theme.spacing(0.5)
},
justifyContent: "center"
},
button: {
display: "flex",
justifyContent: "center"
},
text: {
fontWeight: "bold",
fontSize: 15
},
text1: {
color: "gray",
fontSize: 15
},
bigAvatar: {
width: 140,
height: 140
},
buttons: {
marginRight: 5
}
}));
const ChannelCard = props => {
const classes = useStyles();
const { channel, channelFetchById } = props;
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const view = (
<div className={classes.card}>
<Link to={`/channels/${channel._id}`} className={classes.link}>
<div className={classes.root}>
<Avatar
alt="Remy Sharp"
src={channel.avatar}
className={classes.bigAvatar}
/>
</div>
<div className={classes.title}>
<Typography
variant="subtitle1"
align="center"
className={classes.text}
>
{channel.channelName}
</Typography>
<Typography variant="body2" align="center">
{channel.introduction}
</Typography>
</div>
</Link>
<Typography variant="body2" align="center" className={classes.text1}>
{channel.follows ? channel.follows.length : 0} followers <br />
Language:{channel.language}
</Typography>
<div className={classes.center}>
<div className={classes.button}>
<div className={classes.buttons}>
<Button
variant="contained"
color="primary"
onClick={() => {
channelFetchById(channel._id);
handleClickOpen();
}}
>
Edit
</Button>
{open ? <EditChannel setOpen={setOpen} open={open} /> : ""}
</div>
<div>
<Button color="primary" variant="contained">
Delete
</Button>
</div>
</div>
<br />
</div>
</div>
);
return <Fragment>{view}</Fragment>;
};
export default connect(null, { channelFollow, channelFetchById })(ChannelCard);
Reducer
import {
CHANNEL_FETCH,
CHANNEL_FETCH_BY_ID,
CHANNEL_UNFOLLOWED,
CHANNEL_EDIT
} from "../action/typeof";
const initialState = {
channels: [],
channel: null,
loading: true
};
export default (state = initialState, action) => {
const { payload } = action;
switch (action.type) {
case CHANNEL_FETCH:
return { ...state, channels: payload, loading: false };
case CHANNEL_FETCH_BY_ID:
return {
...state,
channel: payload,
loading: false
};
case CHANNEL_UNFOLLOWED:
return {
...state,
channelUnfollowedUser: payload,
loading: false
};
case CHANNEL_EDIT:
const channelEdit = state.channes.map(single =>
single._id === payload.id ? { single, ...payload.data } : single
);
return {
...state,
channels: channelEdit,
loading: false
};
default:
return state;
}
};

Material UI TextField Not Showing Value

I have a form in react where I'm using a Material UI TextField. I want a value present in the TextField and I can see that it is being set when I use the Inspect option of Google Chrome. In addition to that, I see that the type="hidden". I have changed this to type="text" and to no avail, the value is still not presented. Curious if anyone here has any understanding as to why this would happen. Below is the primary code that is causing the problem:
<TextField
name="Property"
select
fullWidth
margin="normal"
className={clsx(selectInputStyle.margin, selectInputStyle.textField, selectInputStyle.root)}
value={viewProperties[index].name}
onChange={this.handleSelectChange}
>
{propertyKeys.map((key, index) => (
<MenuItem value={key} key={index}>
{key}
</MenuItem>
))}
</TextField>
Here is the full code file just for a complete context of what is going on.
import React, { Component } from 'react';
import { Container, Form, Button, Row, Col, Nav, NavItem, NavLink, Input, FormGroup } from 'reactstrap';
import { connect } from 'react-redux';
import { reduxForm, FieldArray, arrayRemoveAll } from 'redux-form/immutable';
import * as Immutable from 'immutable';
import _ from 'lodash';
import { bindActionCreators } from 'redux';
import BlockUi from 'react-block-ui';
import MaterialButton from '#material-ui/core/Button';
import DeleteIcon from '#material-ui/icons/Delete';
import { makeStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import MenuItem from '#material-ui/core/MenuItem';
import Select from '#material-ui/core/Select';
import InputMaterial from '#material-ui/core/Input';
import FormControl from '#material-ui/core/FormControl';
import TextField from '#material-ui/core/TextField';
import OutlinedInput from '#material-ui/core/OutlinedInput';
import clsx from 'clsx';
import { AvText, AvSelect } from '../forms/components';
import { showNotification, loadPayerProperties, updatePayerProperties } from '../actions';
class PayerPropertiesEditor extends Component {
constructor(props) {
super(props);
this.uploadRef = React.createRef();
this.state = {
errors: [],
refeshProperties: false,
blocking: false
};
this.showButton = false;
this.divPadding = { padding: '20px' };
this.doSubmit = this.doSubmit.bind(this);
this.handleInvalidSubmit = this.handleInvalidSubmit.bind(this);
this.renderProperties = this.renderProperties.bind(this);
this.handleSelectChange = this.handleSelectChange.bind(this);
this.useStyles = makeStyles(theme => ({
button: {
margin: theme.spacing(1)
},
leftIcon: {
marginRight: theme.spacing(1)
},
rightIcon: {
marginLeft: theme.spacing(1)
},
iconSmall: {
fontSize: 20
},
root: {
display: 'flex',
flexWrap: 'wrap'
},
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
},
container: {
display: 'flex',
flexWrap: 'wrap'
},
input: {
margin: theme.spacing(1)
}
}));
}
componentDidMount() {
this.setState({ view: 'payer' });
}
componentDidUpdate(prevProps) {
const { loadPayerProperties } = this.props;
if (this.state.refeshProperties) {
this.props.arrayRemoveAll('payerPropertiesEditorForm', 'properties');
loadPayerProperties();
this.setState({ refeshProperties: false });
this.setState({ blocking: false });
this.showButton = false;
}
if (!prevProps.properties && this.props.properties) {
this.props.change('properties', Immutable.fromJS(this.props.properties));
}
}
doSubmit(values) {
const { updatePayerProperties } = this.props;
return new Promise(resolve => {
this.setState({
blocking: true
});
updatePayerProperties(values.toJS(), () => {
this.setState({ refeshProperties: true });
});
resolve();
});
}
handleInvalidSubmit() {
this.props.showNotification({
level: 'error',
message: 'Errors were found.'
});
}
handleSelectChange(event) {
console.log(event);
}
renderProperties({ fields }) {
const inputUseStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexWrap: 'wrap'
},
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
},
container: {
display: 'flex',
flexWrap: 'wrap'
},
margin: {
margin: theme.spacing(1)
},
textField: {
flexBasis: 200
},
input: {
margin: theme.spacing(1)
}
}));
const selectInputStyle = inputUseStyles().input;
const useStyles = this.useStyles();
const { formProperties, unsetPropertiesKeys } = this.props;
const viewProperties = formProperties[`${this.state.view}`];
const propertyKeys = unsetPropertiesKeys[`${this.state.view}`];
return (
<div maxWidth="sm" className={selectInputStyle.root}>
{fields.map((property, index) => (
<Row
key={index}
style={{
display: viewProperties[index].action === 'remove' ? 'none' : ''
}}
>
<Col xs={5}>
<TextField
select
fullWidth
margin="normal"
className={clsx(selectInputStyle.margin, selectInputStyle.textField, selectInputStyle.root)}
value={viewProperties[index].name}
onChange={this.handleSelectChange}
>
{propertyKeys.map((key, index) => (
<MenuItem value={key} key={index}>
{key}
</MenuItem>
))}
</TextField>
</Col>
<Col xs={5}>
<AvText
name={`${property}.value`}
onChange={() => {
if (viewProperties[index].action !== 'add') {
this.props.change(`${property}.action`, 'update');
this.showButton = true;
}
}}
/>
</Col>
<Col xs={1}>
<MaterialButton
variant="contained"
className="{classes.button}"
onClick={() => {
fields.remove(index);
if (viewProperties[index].action !== 'add') {
fields.insert(
index,
Immutable.fromJS(Object.assign({}, viewProperties[index], { action: 'remove' }))
);
this.showButton = true;
}
}}
>
Delete
<DeleteIcon className={useStyles.rightIcon} />
</MaterialButton>
</Col>
</Row>
))}
<Row>
<Col xs={12}>
<Button
color="primary"
onClick={() => {
fields.push(
Immutable.fromJS({
owner: this.props.ownerKeys[this.state.view],
action: 'add'
})
);
this.showButton = true;
}}
>
Add Property
</Button>
</Col>
</Row>
<br />
{this.showButton === true && (
<Row>
<Col xs={12}>
<Button color="primary" type="submit">
Submit
</Button>
</Col>
</Row>
)}
</div>
);
}
render() {
const { handleSubmit, properties, payerName, chsId, parentChsId } = this.props;
const formStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexWrap: 'wrap'
},
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
}
}));
const navItems = ['payer', 'clearinghouse', 'parent'].map(key => (
<Tab
textColor="primary"
key={key}
label={
key === 'payer'
? 'PAYER: ' + payerName
: key === 'clearinghouse'
? 'CLEARING HOUSE: ' + chsId
: 'PARENT: ' + parentChsId
}
onClick={() => this.setState({ view: key })}
/>
));
const overrides = this.state.view === 'payer' ? ['clearinghouse'] : this.state.view === 'parent' ? ['parent'] : [];
const readonly = properties
? overrides
.filter(key => key !== this.state.view)
.map(key => properties[key])
.reduce((acc, val) => acc.concat(val), [])
.map((property, idx) => {
return (
<Row key={idx}>
<Col xs={5}>
<FormGroup>
<Input value={property.name} disabled />
</FormGroup>
</Col>
<Col xs={5}>
<FormGroup>
<Input value={property.value} disabled />
</FormGroup>
</Col>
</Row>
);
})
: [];
return (
<BlockUi tag="div" blocking={this.state.blocking} className="my-2">
<Container maxWidth="sm">
<AppBar position="static" color="default">
<Tabs variant="fullWidth" textColor="primary">
{navItems}
</Tabs>
</AppBar>
<FormControl
fullWidth
className="mt-4"
onSubmit={handleSubmit(this.doSubmit)}
ref={form => (this.formRef = form)}
>
{readonly}
<FieldArray
name={`properties.${this.state.view}`}
component={this.renderProperties}
rerenderOnEveryChange
/>
</FormControl>
</Container>
</BlockUi>
);
}
}
const mapStateToProps = state => {
const {
payerPropertiesStore: {
payer: { payerId, payerName, chsId, parentChsId },
properties,
propertyKeys
},
form: {
payerPropertiesEditorForm: {
values: { properties: formProperties }
}
}
} = state.toJS();
const unsetPropertiesKeys = {};
for (const view of ['payer', 'clearinghouse', 'parent']) {
unsetPropertiesKeys[view] = propertyKeys.filter(key => !_.find(formProperties[view], { name: key }));
}
const ownerKeys = { payer: payerId, clearinghouse: chsId, parent: parentChsId };
return { formProperties, properties, ownerKeys, unsetPropertiesKeys, payerId, payerName, chsId, parentChsId };
};
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
showNotification,
loadPayerProperties,
updatePayerProperties,
arrayRemoveAll
},
dispatch
);
export default reduxForm({
form: 'payerPropertiesEditorForm',
enableReinitialize: true,
initialValues: Immutable.fromJS({ properties: {} })
})(
connect(
mapStateToProps,
mapDispatchToProps
)(PayerPropertiesEditor)
);
In the TextField you are setting value from viewProperties like,
value={viewProperties[index].name}
You are getting data in viewProperties from formProperties based on this.state.view
const viewProperties = formProperties[`${this.state.view}`];
As you are setting view state in componentDidMount, on intial render view is not set and don't have value so you are not getting any value from formProperties.
You need to have a default state,
this.state = {
errors: [],
refeshProperties: false,
blocking: false,
view: 'payer' // default set
};

Login mutation not working consistently in material-ui.

I am trying to create a sample login form in React using material-ui through graphql mutation. But the login form is not working as expected.
i am able to log in only some times, but even in those times, I do not see any data being passed via loginMutation props in console. Could some one please tell me what am i doing wrong here?
Here is the Login Component that I am trying to create
import React, { Component } from 'react';
import { AUTH_TOKEN } from '../constants';
import { graphql, compose } from 'react-apollo';
import {LOGIN_MUTATION} from "../gql/loginGQL";
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField'
import PropTypes from 'prop-types';
import Avatar from '#material-ui/core/Avatar';
import CssBaseline from '#material-ui/core/CssBaseline';
import FormControl from '#material-ui/core/FormControl';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
//import Input from '#material-ui/core/Input';
//import InputLabel from '#material-ui/core/InputLabel';
import LockIcon from '#material-ui/icons/LockOutlined';
import Typography from '#material-ui/core/Typography';
import withStyles from '#material-ui/core/styles/withStyles';
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 * 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${theme.spacing.unit * 3}px`,
},
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 * 3,
},
});
class Login extends Component {
state = {
email: '',
password: '',
errors: null
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<CssBaseline />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Avatar className={classes.avatar}>
<LockIcon />
</Avatar>
<Typography variant="headline">Sign in</Typography>
<form className={classes.form}>
<FormControl margin="normal" required fullWidth>
<TextField
id='email'
value={this.state.email}
onChange={e => this.setState({ email: e.target.value })}
type='text'
label='Your email address'
/>
</FormControl>
<FormControl margin="normal" required fullWidth>
<TextField
id='password'
value={this.state.password}
onChange={e => this.setState({ password: e.target.value })}
type='password'
label='Password'
/>
</FormControl>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
id="submit"
type="submit"
fullWidth
variant="raised"
color="primary"
className={classes.submit}
onClick={() => this._confirm()}
>
Sign in
</Button>
</form>
</Paper>
</main>
</React.Fragment>
);
}
_confirm = async () => {
const { email, password } = this.state
try{
const result = await this.props.loginMutation({
variables: {
email,
password,
},
});
console.log(result); // Here no data is being displayed !
const { jwt } = result.data.signInUser;
this._saveUserData(jwt);
this.props.history.push(`/`)
} catch(error) {
const errors = error.graphQLErrors.map(error => error.message);
this.setState({ errors });
}
}
_saveUserData = (token) => {
localStorage.setItem(AUTH_TOKEN, token)
}
}
Login.propTypes = {
classes: PropTypes.object.isRequired,
};
export default compose(
graphql(LOGIN_MUTATION, { name: 'loginMutation' }),
withStyles(styles),
)(Login)
The error was being occurred due to the Button tag. Although, it had type="submit", the form was still not being submitted due to which the input data was not being appearing in the "_confirm". I am wondering why, I must have missed something here.
For now, I created a div tag inside the Button and added onClick handler there instead of Button tag.
So, the edited code just have this small change
<Button color="primary" className = {classes.submit} variant="raised" fullWidth>
<div className="test" onClick={() => this._confirm()} >
Sign in
</div>
</Button>
Here is the full source code:
import React, { Component } from 'react';
import { AUTH_TOKEN } from '../constants';
import { graphql, compose } from 'react-apollo';
import {LOGIN_MUTATION} from "../gql/loginGQL";
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField'
import PropTypes from 'prop-types';
import Avatar from '#material-ui/core/Avatar';
import CssBaseline from '#material-ui/core/CssBaseline';
import FormControl from '#material-ui/core/FormControl';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
//import Input from '#material-ui/core/Input';
//import InputLabel from '#material-ui/core/InputLabel';
import LockIcon from '#material-ui/icons/LockOutlined';
import Typography from '#material-ui/core/Typography';
import withStyles from '#material-ui/core/styles/withStyles';
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 * 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${theme.spacing.unit * 3}px`,
},
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 * 3,
},
});
class Login extends Component {
state = {
email: '',
password: '',
errors: null
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<CssBaseline />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Avatar className={classes.avatar}>
<LockIcon />
</Avatar>
<Typography variant="headline">Sign in</Typography>
<form className={classes.form}>
<FormControl margin="normal" required fullWidth>
<TextField
id='email'
value={this.state.email}
onChange={e => this.setState({ email: e.target.value })}
type='text'
label='Your email address'
/>
</FormControl>
<FormControl margin="normal" required fullWidth>
<TextField
id='password'
value={this.state.password}
onChange={e => this.setState({ password: e.target.value })}
type='password'
label='Password'
/>
</FormControl>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
// Here is the change
<Button color="primary" className = {classes.submit}
variant="raised"fullWidth >
<div className="test" onClick={() => this._confirm()} >
Sign in
</div>
</Button>
// Change ends here
</form>
</Paper>
</main>
</React.Fragment>
);
}
_confirm = async () => {
const { email, password } = this.state
try{
const result = await this.props.loginMutation({
variables: {
email,
password,
},
});
console.log(result); // Here no data is being displayed !
const { jwt } = result.data.signInUser;
this._saveUserData(jwt);
this.props.history.push(`/`)
} catch(error) {
const errors = error.graphQLErrors.map(error => error.message);
this.setState({ errors });
}
}
_saveUserData = (token) => {
localStorage.setItem(AUTH_TOKEN, token)
}
}
Login.propTypes = {
classes: PropTypes.object.isRequired,
};
export default compose(
graphql(LOGIN_MUTATION, { name: 'loginMutation' }),
withStyles(styles),
)(Login)

Resources