This might be a duplicate question but I still couldn't find an answer. I am new to react and is struggling to create a login page. I am trying t set states with the values I have just entered in the application for further usage. Using these values I will be making a backed call for authentication.
My React Code
import React from "react";
// core components are added
class LoginPage extends React.Component {
constructor(props) {
super(props);
// we use this to make the card to appear after the page has been rendered
this.state = {
cardAnimaton: "cardHidden"
};
}
handleSubmit() {
console.log(this);
}
handleChange(event) {
console.log(event)
}
componentDidMount() {
// we add a hidden class to the card and after 700 ms we delete it and the transition appears
setTimeout(
function() {
this.setState({ cardAnimaton: "" });
}.bind(this),
700
);
}
render() {
const { classes, ...rest } = this.props;
return (
<div>
<div className={classes.container}>
<GridContainer justify="center">
<GridItem xs={12} sm={12} md={12}>
<Card className={classes[this.state.cardAnimaton]}>
<form className={classes.form} onSubmit = {this.handleSubmit}>
<CardHeader color="primary" className={classes.cardHeader}>
<h4>Login</h4>
</CardHeader>
<CardBody>
<CustomInput
labelText="Email..."
id="email"
onChange={this.handleChange}
value = {this.state.email}
formControlProps={{
fullWidth: true
}}
inputProps={{
type: "email",
endAdornment: (
<InputAdornment position="end">
<Email className={classes.inputIconsColor} />
</InputAdornment>
)
}}
/>
</CardBody>
<CardFooter className={classes.cardFooter}>
<Button color="primary" size="lg" type="submit">
Login
</Button>
</CardFooter>
</form>
</Card>
</GridItem>
</GridContainer>
</div>
</div>
);
}
}
export default withStyles(loginPageStyle)(LoginPage);
I am trying to send values from my HTML to state in handleChange but it is not reacting whenever I make a change.
If it something with states, please let me know. I am using a custom theme and hope that the issue is in my implementation and have nothing to do with the theme.
Custom Input Component
import React from "react";
// nodejs library to set properties for components
import PropTypes from "prop-types";
// nodejs library that concatenates classes
import classNames from "classnames";
// #material-ui/core components
import withStyles from "#material-ui/core/styles/withStyles";
import FormControl from "#material-ui/core/FormControl";
import InputLabel from "#material-ui/core/InputLabel";
import Input from "#material-ui/core/Input";
import customInputStyle from "assets/jss/material-kit-react/components/customInputStyle.jsx";
function CustomInput({ ...props }) {
const {
classes,
formControlProps,
labelText,
id,
labelProps,
inputProps,
error,
white,
inputRootCustomClasses,
success
} = props;
const labelClasses = classNames({
[" " + classes.labelRootError]: error,
[" " + classes.labelRootSuccess]: success && !error
});
const underlineClasses = classNames({
[classes.underlineError]: error,
[classes.underlineSuccess]: success && !error,
[classes.underline]: true,
[classes.whiteUnderline]: white
});
const marginTop = classNames({
[inputRootCustomClasses]: inputRootCustomClasses !== undefined
});
const inputClasses = classNames({
[classes.input]: true,
[classes.whiteInput]: white
});
var formControlClasses;
if (formControlProps !== undefined) {
formControlClasses = classNames(
formControlProps.className,
classes.formControl
);
} else {
formControlClasses = classes.formControl;
}
return (
<FormControl {...formControlProps} className={formControlClasses}>
{labelText !== undefined ? (
<InputLabel
className={classes.labelRoot + " " + labelClasses}
htmlFor={id}
{...labelProps}
>
{labelText}
</InputLabel>
) : null}
<Input
classes={{
input: inputClasses,
root: marginTop,
disabled: classes.disabled,
underline: underlineClasses
}}
id={id}
{...inputProps}
/>
</FormControl>
);
}
CustomInput.propTypes = {
classes: PropTypes.object.isRequired,
labelText: PropTypes.node,
labelProps: PropTypes.object,
id: PropTypes.string,
inputProps: PropTypes.object,
formControlProps: PropTypes.object,
inputRootCustomClasses: PropTypes.string,
error: PropTypes.bool,
success: PropTypes.bool,
white: PropTypes.bool
};
export default withStyles(customInputStyle)(CustomInput);
Related
I am working on an app with React and Redux and displaying some data from API in TextInput control. But now I am not able to edit the data in the TextInput. Following is my complete code of the class:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Article from "grommet/components/Article";
import Box from "grommet/components/Box";
import Button from "grommet/components/Button";
import Header from "grommet/components/Header";
import Heading from "grommet/components/Heading";
import Section from "grommet/components/Section";
import AdminMenu from "../../components/Nav/Admin";
import NavControl from "../../components/Nav/Control";
import { getMessage } from "grommet/utils/Intl";
import Notices from "../../components/Notices";
import CheckBox from "grommet/components/CheckBox";
import TextInput from "grommet/components/TextInput";
import { pageLoaded } from "../utils";
import {
recognitionSettingsLoaded,
recognitionSettingsSaved,
} from "../../actions/settings-recognition";
import dashboard from "../../reducers/dashboard";
class Settings extends Component {
constructor(props) {
super(props);
this.handleDaysChange = this.handleDaysChange.bind(this);
this.handleActiveChange = this.handleActiveChange.bind(this);
}
componentDidMount() {
const { dispatch, settingRecognition } = this.props;
console.log(this.props.state);
console.log(dashboard);
dispatch(recognitionSettingsLoaded("2"));
pageLoaded("Configuration");
}
onSave() {
const { survey, dispatch } = this.props;
dispatch(
recognitionSettingsSaved(
this.props.settingRecognition.days,
this.props.settingRecognition.active
)
);
}
handleDaysChange(e) {
const days = e.target.value;
settingRecognition.days = days;
}
handleActiveChange(e) {
const active = e.target.value;
settingRecognition.active = active;
}
render() {
const { dispatch, settingRecognition } = this.props;
console.log("render method");
console.log(settingRecognition);
const { intl } = this.context;
return (
<Article primary={true}>
<Header
direction="row"
justify="between"
size="large"
pad={{ horizontal: "medium", between: "small" }}
>
<NavControl name={getMessage(intl, "Configuration")} />
<AdminMenu />
</Header>
<Box pad={{ horizontal: "medium", vertical: "medium" }}>
<Heading tag="h4" margin="none">
{getMessage(intl, "RecognitionLifetime")}
</Heading>
<Heading tag="h5" margin="none">
{getMessage(intl, "DefineIsRecognitionTemporary")}
</Heading>
<Box direction="row">
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={this.handleActiveChange}
/>{" "}
<Heading tag="h3" margin="none">
{getMessage(intl, "NewUserActive")}
</Heading>
</Box>
<Heading tag="h3" margin="none">
{getMessage(intl, "HideAfter")}
</Heading>
<Box direction="row">
<TextInput
placeholder="type here"
value={settingRecognition.days.toString()}
onChange={this.handleDaysChange}
/>{" "}
<Heading tag="h3" margin="none">
{getMessage(intl, "Days")}
</Heading>
</Box>
<Button
path="/recognition-settings"
label={getMessage(intl, "NewUserSave")}
primary={true}
onClick={() => {
this.onSave();
}}
/>
</Box>
<Notices />
</Article>
);
}
}
Settings.propTypes = {
dispatch: PropTypes.func.isRequired,
settingRecognition: PropTypes.object.isRequired,
};
Settings.contextTypes = {
intl: PropTypes.object,
};
const mapStateToProps = (state) => ({
settingRecognition: state.settingRecognition,
});
export default connect(mapStateToProps)(Settings);
I have created handleDaysChange function which should run on the text change of TextInput control. I have done similar thing for the checkbox and that works fine but I am not able to get it working for the TextInput.
You are not binding your change events.
Try this....
class Settings extends Component {
constructor(props){
super(props);
this.handleDaysChange = this.handleDaysChange.bind(this);
this.handleActiveChange = this.handleActiveChange.bind(this);
}
componentDidMount(){
....
}
......
}
and change this
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={(e) => this.handleActiveChange(e)}
/>
To this
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={this.handleActiveChange}
/>
same for text input
<TextInput
placeholder="type here"
value={settingRecognition.days.toString()}
onChange={this.handleDaysChange}
/>
You need to set up two-way-binding so that the content of the textInput reflects the prop that you set in your onChange function. Try giving your textInput a property of value={this.settingRecognition.days}
I am implementing pagination for my blog page by using react-js-pagination.
I am unable to fix this pagination ui and its content per page
Bind my data accordingly per page
I have tried to import less from bootstrap but it is not rendering for this.
Any suggestion on this to solve this pagination issue?
Updated code: it is working now
//blog.js
import React, {Component} from 'react';
import {Card, Grid, Cell, Dialog, CardMenu, Button, CardTitle,
CardText, CardActions, FABButton, Icon} from'react-mdl';
import { Container} from 'reactstrap';
import { connect } from 'react-redux';
import { getBlog, deleteBlog } from '../../actions/resumeActions';
import PropTypes from 'prop-types';
import Loading from './Loading';
import Moment from 'moment';
import BlogModal from "./BlogModal";
import Pagination from "react-js-pagination";
class Blogs extends Component{
constructor(props) {
super(props);
this.state = {
modal: false,
justClicked: null,
activePage: 1
};
this.handleOpenDialog = this.handleOpenDialog.bind(this);
this.handleCloseDialog = this.handleCloseDialog.bind(this);
}
static propTypes = {
getContact: PropTypes.func.isRequired,
deleteContact: PropTypes.func.isRequired,
resume: PropTypes.object.isRequired,
isAuthenticated: PropTypes.bool,
auth: PropTypes.object.isRequired,
loading: PropTypes.object.isRequired
}
toggle = () => {
this.setState({
modal: !this.state.modal
});
}
handleOpenDialog(id) {
this.setState({
openDialog: true,
justClicked: id
});
}
handleCloseDialog() {
this.setState({
openDialog: false
});
}
componentDidMount() {
this.props.getBlog();
}
onDeleteBlogClick = (id) => {
this.props.deleteBlog(id);
};
handlePageChange(pageNumber) {
console.log(`active page is ${pageNumber}`);
this.setState({activePage: pageNumber});
}
cardDialog(blogs, user){
return(
<Grid style={{padding: 0, display: 'contents'}}>
{blogs.map(({ _id, blog_name, blog_desc, blog_image_link, blog_by }) => (
<Cell col={12}>
<Dialog open={this.state.openDialog && this.state.justClicked === _id} className="open-dialog">
<CardTitle style={{color: '#fff', height: '176px', backgroundImage: `url(${blog_image_link})`, backgroundPosition: 'center',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat'}}>{blog_name}</CardTitle>
<CardText>
{blog_desc}
</CardText>
<CardActions border>
<p style={{float:'right', fontWeight:'bold'}}>Author: {blog_by}</p>
</CardActions>
<CardMenu style={{color: '#fff'}}>
<FABButton onClick={this.handleCloseDialog} className="close-button" >
<Icon name="close" />
</FABButton>
</CardMenu>
</Dialog>
</Cell>
))}
</Grid>
)
}
render(){
const { blogs, loading} = this.props.resume;
const { isAuthenticated, user } = this.props.auth;
const itemsPerPage = 1;
let activeBlogs = blogs.slice (itemsPerPage * this.state.activePage - itemsPerPage, itemsPerPage * this.state.activePage)
return(
<Container>
{loading ? (
<div><Loading/></div>
) : (
<div>
{/* blog modal */}
<BlogModal />
{/* card dialog */}
{this.cardDialog(blogs, user)}
<Grid style={{padding: 0}} id="todo">
{activeBlogs.map(({ _id, blog_name, blog_by, date }) => (
<Cell key={_id} data-id={_id}>
{ this.props.isAuthenticated && (user.is_admin === true) ?
<Button className="remove-btn"
color="danger"
size="sm"
onClick= {this.onDeleteBlogClick.bind(this, _id)}>
×
</Button> : null }
<Card shadow={5} className="cards-grid">
<CardTitle className="card-title-image"></CardTitle>
<CardText>
<b>{blog_name}</b>
</CardText>
<CardActions border>
<Button className="blog-read-me-button" onClick={this.handleOpenDialog.bind(this, _id)}>Read </Button>
<p style={{ fontStyle:'italic', fontWeight:'bold'}}>By-{blog_by} <span style={{float:'right',}}>{Moment(date).format('Do MMMM YYYY')}</span></p>
</CardActions>
</Card>
</Cell>
))}
</Grid>
</div>
)}
<Pagination
activePage={this.state.activePage}
itemsCountPerPage={1}
totalItemsCount={2}
pageRangeDisplayed={1}
onChange={this.handlePageChange.bind(this)}
itemClass='page-item'
linkClass='page-link'
/>
</Container>
)
}
}
const mapStateToProps = (state) => ({
resume: state.resume,
isAuthenticated : state.auth.isAuthenticated,
auth: state.auth,
loading: state.apiCallsInProgress > 0
});
export default connect(mapStateToProps, {getBlog, deleteBlog }) (Blogs);
//current ui
This is the default style of react-js-pagination. You have to style it yourself. However as far as I see, you are using bootstrap in your application, so you could use their styles in the following way:
<Pagination
activePage={this.state.activePage}
itemsCountPerPage={1}
totalItemsCount={2}
pageRangeDisplayed={1}
onChange={this.handlePageChange.bind(this)}
itemClass='page-item'
linkClass='page-link'
/>
I'm beginner for Reactjs and trying to improve myself with a project. As progressing with my project, I now need to setup a structure such that my UI component will connect to some REST API, and use the returned data. This has many examples on the internet and fine.
My question is, if and how I could separate the API connection from the actual UI components. I believe it could be good, as I would have the chance to reuse the API connection functions within various UI components. (It would be ideal to do the common tasks related with API connections within these helper methods.)
For that, I created a PrimaryForm.js file, that is the UI component. And for API calls, I created an APIManager.js file. Ideally, APIManager should not have any jsx, but only functions that return API call results to the PrimaryForm.js.
I am sharing the code I wrote so far, to achieve that.
PrimaryForm.js, that deletages the API call to APIManager.js (see handleTestConnection part below):
import React from 'react';
import withStyles from '#material-ui/styles/withStyles';
import {Link, withRouter } from 'react-router-dom';
import Paper from '#material-ui/core/Paper';
import TextField from '#material-ui/core/TextField';
import Typography from '#material-ui/core/Typography';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import InputLabel from '#material-ui/core/InputLabel';
import OutlinedInput from '#material-ui/core/OutlinedInput';
import Select from '#material-ui/core/Select';
import FormHelperText from '#material-ui/core/FormHelperText';
import PrimaryFormValidator from '../../validators/PrimaryFormValidator'
import styles from '../../Styles';
import Grid from '#material-ui/core/Grid';
import Tooltip from '#material-ui/core/Tooltip';
import CancelIcon from '#material-ui/icons/Cancel';
import BackIcon from '#material-ui/icons/ArrowBackIosRounded';
import TestIcon from '#material-ui/icons/Power';
import ForwardIcon from '#material-ui/icons/ArrowForwardIosRounded';
import Button from '#material-ui/core/Button';
import APIManager from '../../managers/APIManager';
function PrimaryForm(props) {
const { classes } = props;
const inputLabel = React.useRef(null);
const [labelWidth, setLabelWidth] = React.useState(0);
React.useEffect(() => {setLabelWidth(inputLabel.current.offsetWidth);}, []);
const [state, setState] = React.useState({
hostname: {
value: "test",
isError: false,
errorText: "",
},
serverIp: {
value: "192.168.16.1",
isError: false,
errorText: "",
},
osVariant: {
value: "Linux",
isError: false,
errorText: "",
},
databaseSid: {
value: "mysql",
isError: false,
errorText: "",
},
listenerPort: {
value: "3306",
isError: false,
errorText: "",
},
isFormValid: true,
isPrimaryDbValid: false,
});
const evaluateFormValid = (prevState) => {
return ((prevState.hostname.value!=="" && !prevState.hostname.isError) &&
(prevState.serverIp.value!=="" && !prevState.serverIp.isError) &&
(prevState.osVariant.value!=="" && !prevState.osVariant.isError) &&
(prevState.databaseSid.value!=="" && !prevState.databaseSid.isError) &&
(prevState.listenerPort.value!=="" && !prevState.listenerPort.isError));
};
const handleChange = event => {
var valResult;
switch (event.target.id) {
case 'hostname':
valResult = PrimaryFormValidator.validateHostname(event.target.value, event.target.labels[0].textContent);
setState({
...state,
hostname:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
},
});
break;
case 'serverIp':
valResult = PrimaryFormValidator.validateIpAddress(event.target.value, event.target.labels[0].textContent);
setState({
...state,
serverIp:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
}
});
break;
case 'databaseSid':
valResult = PrimaryFormValidator.validateDatabaseSid(event.target.value, event.target.labels[0].textContent);
setState({
...state,
databaseSid:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
}
});
break;
case 'listenerPort':
valResult = PrimaryFormValidator.validateListenerPort(event.target.value, event.target.labels[0].textContent);
setState({
...state,
listenerPort:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
}
});
break;
default:
//setState({...state,});
}
setState(prevState => ({
...prevState,
isFormValid: evaluateFormValid(prevState),
}));
}
const handleTestConnection = event => {
APIManager.testConnection(state.hostname.value, state.serverIp.value, state.osVariant.value, state.databaseSid.value, state.listenerPort.value);
//console.log("Data:" + APIManager.state.testConnectionResult);
}
const handleSelect = osVariant => event => {
var valResult = PrimaryFormValidator.validateOsVariant(event.target.value, inputLabel.current.textContent);
setState(prevState => ({
...prevState,
osVariant:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
},
}));
setState(prevState => ({
...prevState,
isFormValid: evaluateFormValid(prevState),
}));
}
return (
<React.Fragment>
<div className={classes.bigContainer}>
<Paper className={classes.paper}>
<div>
<div>
<Typography variant="subtitle1" gutterBottom className={classes.subtitle1} color='secondary'>
Primary Database System
</Typography>
<Typography variant="body1" gutterBottom>
Information related with the primary database system. Please note that the primary database has to be up and running.
</Typography>
</div>
<div className={classes.bigContainer}>
<form className={classes.formArea}>
<TextField className={classes.formControl}
id="hostname"
label="FQDN Hostname *"
onChange={handleChange}
value={state.hostname.value}
error={state.hostname.isError}
helperText={state.hostname.errorText}
variant="outlined" autoComplete="off" />
<TextField className={classes.formControl}
id="serverIp"
label="Server Ip Address *"
onChange={handleChange}
value={state.serverIp.value}
error={state.serverIp.isError}
helperText={state.serverIp.errorText}
variant="outlined" autoComplete="off" />
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel id="osVarLabel" htmlFor="osVariant" ref={inputLabel}>OS Variant *</InputLabel>
<Select
id="osVariant"
label="OS Variant *"
value={state.osVariant.value}
error={state.osVariant.isError}
onChange={handleSelect("osVariant")}
input={<OutlinedInput id="outlinedinput" labelWidth={labelWidth}/>}>
<MenuItem value={''}></MenuItem>
<MenuItem value={'Linux'}>Linux</MenuItem>
<MenuItem value={'Windows'}>Windows</MenuItem>
</Select>
<FormHelperText error={state.osVariant.isError} hidden={!state.osVariant.isError}>
{state.osVariant.errorText}
</FormHelperText>
</FormControl>
<TextField className={classes.formControl}
id="databaseSid"
label="Database SID"
onChange={handleChange}
value={state.databaseSid.value}
error={state.databaseSid.isError}
helperText={state.databaseSid.errorText}
variant="outlined" autoComplete="off" />
<TextField className={classes.formControl}
id="listenerPort"
label="Listener Port"
onChange={handleChange}
value={state.listenerPort.value}
error={state.listenerPort.isError}
helperText={state.listenerPort.errorText}
variant="outlined" autoComplete="off" />
{/* <TextField className={classes.formControl}
disabled={true}
id="isFormValid"
label="Is Form Valid Now?"
value={state.isFormValid}
variant="outlined" autoComplete="off" /> */}
</form>
</div>
</div>
</Paper>
<Grid container spacing={2} className={classes.grid}>
<Grid item xs={12}>
<div className={classes.flexBar}>
<Tooltip title="Back to previous step">
<div>
<Button variant="contained"
disabled={true}
className={classes.actionButton}
size='large'>
<BackIcon className={classes.rightIcon}/>Back
</Button>
</div>
</Tooltip>
<Tooltip title="Test Connection">
<div>
<Button variant="contained" className={classes.actionButton}
color="primary"
size='large'
disabled={!state.isFormValid}
onClick={handleTestConnection}>
<TestIcon className={classes.rightIcon}/>Test
</Button>
</div>
</Tooltip>
<Tooltip title="Proceed the next step">
<div>
<Button
variant="contained" className={classes.actionButton}
color="primary"
size='large'
disabled={!state.isPrimaryDbValid}>
<ForwardIcon className={classes.rightIcon} />Next
</Button>
</div>
</Tooltip>
<Tooltip title="Cancel creating new configuration">
<Button variant="contained" color="default" className={classes.actionButton}
component={Link} to={'/configs'} style={{ marginLeft: 'auto' }}>
<CancelIcon className={classes.rightIcon} />Cancel
</Button>
</Tooltip>
</div>
</Grid>
</Grid>
</div>
</React.Fragment>
)
}
export default withRouter(withStyles(styles)(PrimaryForm));
And here is my APIManager.js file:
import React, { Component } from 'react';
export default class APIManager extends Component{
constructor(props) {
super(props);
this.state = {
testConnectionResult: {},
...this.props,
}
this.testConnection = this.testConnection.bind(this);
}
static testConnection(hostname, serverIp, osVariant, databaseSid, listenerPort) {
fetch(`http://localhost:8000/api?objtype=ConnectionDef&hostname=${hostname}&serverIp=${serverIp}&osVariant=${osVariant}&databaseSid=${databaseSid}&listenerPort=${listenerPort}`)
.then(response => response.json())
.then(result => this.setState({testConnectionResult: result}));
//.catch((error) => console.error(error));
console.log("Data:" + this.testConnectionResult);
return this.testConnectionResult;
}
}
Now I could not solve getting below error:
APIManager.js:16 Uncaught (in promise) TypeError: _this2.setState is
not a function
at APIManager.js:16
I guess I'm struggling now to pass the result taken from API call to the external (callee) class / function.
I Googled, also checked other questions on Stackoverflow with this error, but that really didn't help me figure out the issue. I also wanted to ask if I'm making a principal mistake.
Any help would be appreciated. Thanks.
you don't have to create a component just to hold API call logic. in react components are used to declare visual stuff(so they will have jsx).
If you want to extract api call logic out of component, you can put that logic in some function which returns a promise. like so:
function testApi(...args) {
return fetch(/* url parms */)
}
then in your component, on some event say click you can make API call and set response data to component state to change view:
handleTestEvent(...args) {
testApi(...args)
.then(response => response.json())
.then(result => this.setState({testConnectionResult: result}));
.catch((error) => {
console.error(error));
this.setState({testConnectionResult: []});
});
}
You can't access this (an instance context) from a static method. Make it a regular instance method and it should work.
I have a simple component with some inputs, I am trying to verify whether a mock function is called after a button is clicked.
Here is what I tried in my test file:
import React from 'react'
import { mount } from 'enzyme'
import AddUser from '../Components/AddUserWithFormik'
import { Router } from 'react-router-dom'
import { createBrowserHistory } from 'history'
import * as Constants from '../actions/actionTypes'
const hist = createBrowserHistory()
describe('AddUser page', () => {
let wrapper
const mockFetchDetailsActions = jest.fn()
const mockHandleCancel = jest.fn()
const mockHandleInputChangeAction = jest.fn()
const mockHandleSubmit = jest.fn()
const match = {
params: {
id: '12345'
}
}
const location = {
pathname: '/add_user',
search: '',
hash: '',
key: 'ph0ovl'
}
const user = {
first_name: '',
last_name: '',
email: '',
email_secondary: '',
mobile_phone: '',
work_phone: ''
}
beforeEach(() => {
wrapper = mount(
<Router history={hist}>
<AddUser
match={match}
location={location}
user={user}
handleSubmit={mockHandleSubmit}
actions={{
fetchDetailsActions: mockFetchDetailsActions,
handleCancel: mockHandleCancel,
handleInputChangeAction: mockHandleInputChangeAction
}}
/>
</Router>
)
})
describe('#ComponentsRendered', () => {
it('verify simulate change on all input elements', () => {
wrapper
.find('button')
.at(0)
.simulate('click')
expect(mockHandleSubmit).toHaveBeenCalled()
})
})
})
Here is my component:
/* eslint-disable no-invalid-this */
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '#material-ui/core/styles'
import GridContainer from './Grid/GridContainer'
import GridItem from './Grid/GridItem'
import { TextField } from 'formik-material-ui'
import { Field, Form } from 'formik'
import dashboardStyle from '../styles/dashboardStyle'
import Card from './Card/Card'
import CardBody from './Card/CardBody'
import * as Constants from '../actions/actionTypes'
import SaveAndCancelButtons from './Common/saveAndCancelButtons'
class AddUser extends React.Component {
componentDidMount () {
if (this.props.match.params.id) {
this.props.actions.fetchDetailsActions(Constants.FETCH_DETAILS_API_CALL_REQUEST, this.props.match.params.id)
} else {
this.props.actions.handleCancel()
}
}
render () {
const { classes, isFetching } = this.props
return (
<Form>
<Field
name="user"
render={feildProps => (
<Fragment>
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<Card>
<h2 className={classes.cardTitleWhite}>Add User</h2>
<CardBody isFetching={isFetching}>
<GridContainer>
<GridItem xs={12} sm={12} md={4}>
<Field
label="First Name"
name={`user.first_name`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
<Field
label="Secondary Email"
name={`user.email_secondary`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
</GridItem>
<GridItem xs={12} sm={12} md={4}>
<Field
label="Last Name"
name={`user.last_name`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
<Field
label="Mobile Phone"
name={`user.mobile_phone`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
</GridItem>
<GridItem xs={12} sm={12} md={4}>
<Field
label="Email"
name={`user.email`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
<Field
label="Work Phone"
name={`user.work_phone`}
className={this.props.classes.textField}
margin="normal"
variant="outlined"
component={TextField}
/>
</GridItem>
</GridContainer>
</CardBody>
</Card>
<SaveAndCancelButtons
handleSave={() => {
this.props.handleSubmit()
}}
routingLink="/people"
label="Save"
/>
</GridItem>
</GridContainer>
</Fragment>
)}
/>
</Form>
)
}
}
AddUser.propTypes = {
classes: PropTypes.object.isRequired
}
export default withStyles(dashboardStyle)(AddUser)
Here is my withFormik() wrapper:
import { withStyles } from '#material-ui/core/styles'
import { withFormik } from 'formik'
import * as Yup from 'yup'
import AddUser from './AddUser'
import * as Constants from '../actions/actionTypes'
const styles = theme => ({
textField: {
width: '100%'
}
})
const onSave = props => {
const userDetails = {
user: props.user
}
if (userDetails && userDetails.user.id) {
props.actions.updateDetailsActions(Constants.UPDATE_USER_API_CALL_REQUEST, userDetails.user.id, userDetails)
} else {
props.actions.addNewUserAction(Constants.ADD_USER_API_CALL_REQUEST, userDetails)
}
}
const validations = Yup.object().shape({
user: Yup.object().shape({
first_name: Yup.string().required('Required'),
last_name: Yup.string().required('Required'),
email: Yup.string().required('Required')
})
})
const withFormikWrapper = withFormik({
validationSchema: validations,
enableReinitialize: true,
handleSubmit: props => {
onSave(props)
}
})(AddUser)
export default withStyles(styles)(withFormikWrapper)
Expected result:
I expect that the mockHandleSubmit is called when the simulate click is happening.
Actual Results:
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called, but it was not called.
123 | // console.log(wrapper.debug())
124 |
> 125 | expect(mockHandleSubmit).toHaveBeenCalled()
| ^
126 | })
127 | })
128 | })
at Object.toHaveBeenCalled (src/test/AddUser.test.js:125:32)
Here is the saveAndCancelButton component:
import React, { Component } from 'react'
import RoutingButton from './RoutingButton'
import { withStyles } from '#material-ui/core/styles'
import GridContainer from '../Grid/GridContainer'
import GridItem from '../Grid/GridItem'
import ClientContactsStyles from '../../styles/ClientsContactsStyles'
import SaveIcon from '#material-ui/icons/Save'
import Close from '#material-ui/icons/Close'
import classNames from 'classnames'
import Button from '#material-ui/core/Button'
class SaveAndCancelButtons extends Component {
render () {
const { classes } = this.props
const close = <Close />
return (
<GridContainer>
<GridItem xs={12} sm={12} md={6}>
{this.props.submitButtonProps ? (
<GridItem xs={12} sm={12} md={6}>
<Button
variant="contained"
size="large"
className={classes.button}
onClick={this.props.handleSubmit}
>
Submit
</Button>
</GridItem>
) : null}
</GridItem>
<GridItem xs={12} sm={12} md={3}>
<Button
variant="contained"
size="large"
className={classes.button}
onClick={this.props.handleSave}
>
<SaveIcon
className={classNames(classes.leftIcon, classes.iconSmall)}
/>
{this.props.label}
</Button>
</GridItem>
<GridItem xs={12} sm={12} md={3}>
<RoutingButton
variant="contained"
size="large"
className={classes.button}
buttonIcon={close}
routingLink={this.props.routingLink}
label="Cancel"
/>
</GridItem>
</GridContainer>
)
}
}
export default withStyles(ClientContactsStyles)(SaveAndCancelButtons)
The problem here is jest function is not waiting for formik to call its onSubmit() function. To resolve this jest provides 'wait' api. See the below code.
await wait(() => {
expect(mockHandleSubmit).toHaveBeenCalled()
});
Happy Coding :)
I don't think submit is being called because your user is not passing your validation. Rectify that and use below for expectation.
xxxxxx
Given that you have omitted test code in your post that satisfies the schema above is not the solution. The solution is to do expectations against the actions. It is not possible to mock handleSubmit as this is dealt with by the withFormik wrapper.
xxxxxx
it('verify simulate change on all input elements', async () => {
wrapper.find('button').at(0).simulate('click')
await new Promise(resolve=>{
setTimeout(()=>resolve(),0);
})
expect(mockHandleSubmit).toHaveBeenCalled()
})
The problem is that I can't get the value customInput and customSelect and write it to the state? I can’t show all the code I’m trying to connect react dashboard material-ui. If I do the same with normal input and select I get data in state.
Can someone help with this? I can not give a working example, too much code...
import React from "react";
import { connect } from 'react-redux';
// #material-ui/core components
import withStyles from "#material-ui/core/styles/withStyles";
// core components
import GridItem from "components/Grid/GridItem.jsx";
import GridContainer from "components/Grid/GridContainer.jsx";
import CustomInput from "components/CustomInput/CustomInput.jsx";
import Button from "components/CustomButtons/Button.jsx";
import Card from "components/Card/Card.jsx";
import CardHeader from "components/Card/CardHeader.jsx";
import CardBody from "components/Card/CardBody.jsx";
import CardFooter from "components/Card/CardFooter.jsx";
import FormControl from "#material-ui/core/FormControl/FormControl";
import CustomSelect from "../../components/CustomSelect/CustomSelect";
import "../../components/CustomSelect/Select.css";
class NewExercise extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Enter text',
};
}
handleChange = (event) => {
this.setState({
value: event.target.value,
});
};
handleClick = () => {
this.setState({
value: '',
});
console.log(this.state);
};
render() {
console.log('store', this.props.newExercise);
const { classes } = this.props;
return (
<div>
<GridContainer>
<GridItem xs={12} sm={12} md={12} lg={12}>
<form >
<Card>
<CardHeader color="primary">
<h4 className={classes.cardTitleWhite}>Create new exercise</h4>
<p className={classes.cardCategoryWhite}>Please, add a new exercise name and measurement type</p>
</CardHeader>
<CardBody>
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<CustomInput
value={this.state.value}
onChange={this.handleChange}
labelText="Exercise Name"
id="exercise"
formControlProps={{
fullWidth: true
}}
/>
</GridItem>
<GridItem xs={12} sm={12} md={12}>
<FormControl style={{width: "100%"}} className={classes.formControl}>
<div className="materialSelect">
<CustomSelect
labelText="Measurement"
id="custom-select"
formControlProps={{
fullWidth: true
}}
>
<option value="kg">kilograms</option>
<option value="min">minutes</option>
<option value="m">meters</option>
</CustomSelect>
</div>
</FormControl>
</GridItem>
</GridContainer>
</CardBody>
<CardFooter>
<Button color="primary" onClick={this.handleClick}> Create Exercise</Button>
</CardFooter>
</Card>
</form>
</GridItem>
</GridContainer>
</div>
);
}
}
export default connect (
state => ({
newExercise: state
}),
dispatch => ({})
) (withStyles(styles)(NewExercise));
// Custom Input of material-ui dashboard react
import React from "react";
import classNames from "classnames";
import PropTypes from "prop-types";
// #material-ui/core components
import withStyles from "#material-ui/core/styles/withStyles";
import FormControl from "#material-ui/core/FormControl";
import InputLabel from "#material-ui/core/InputLabel";
import Input from "#material-ui/core/Input";
// #material-ui/icons
import Clear from "#material-ui/icons/Clear";
import Check from "#material-ui/icons/Check";
// core components
import customInputStyle from "assets/jss/material-dashboard-
react/components/customInputStyle.jsx";
function CustomInput({ ...props }) {
const {
classes,
formControlProps,
labelText,
id,
labelProps,
inputProps,
error,
success
} = props;
const labelClasses = classNames({
[" " + classes.labelRootError]: error,
[" " + classes.labelRootSuccess]: success && !error
});
const underlineClasses = classNames({
[classes.underlineError]: error,
[classes.underlineSuccess]: success && !error,
[classes.underline]: true
});
const marginTop = classNames({
[classes.marginTop]: labelText === undefined
});
return (
<FormControl
{...formControlProps}
className={formControlProps.className + " " + classes.formControl}
>
{labelText !== undefined ? (
<InputLabel
className={classes.labelRoot + labelClasses}
htmlFor={id}
{...labelProps}
>
{labelText}
</InputLabel>
) : null}
<Input
classes={{
root: marginTop,
disabled: classes.disabled,
underline: underlineClasses
}}
id={id}
{...inputProps}
/>
{error ? (
<Clear className={classes.feedback + " " + classes.labelRootError} />
) : success ? (
<Check className={classes.feedback + " " + classes.labelRootSuccess} />
) : null}
</FormControl>
);
}
CustomInput.propTypes = {
classes: PropTypes.object.isRequired,
labelText: PropTypes.node,
labelProps: PropTypes.object,
id: PropTypes.string,
inputProps: PropTypes.object,
formControlProps: PropTypes.object,
error: PropTypes.bool,
success: PropTypes.bool
};
export default withStyles(customInputStyle)(CustomInput);
check this example:
//custom component
import React from 'react'
import PropTypes from 'prop-types'
import Radio from '#material-ui/core/Radio'
export const BmRadio = (props) => {
return <Radio onClick={this.props.onClick} {...props} />
}
BmRadio.defaultProps = {
color: 'primary'
}
BmRadio.propTypes = {
onClick: PropTypes.func,
color: PropTypes.oneOf(['default', 'primary', 'secondary']),
icon: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
checkedIcon: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
value: PropTypes.string,
disabled: PropTypes.bool,
onChange: PropTypes.func,
type: PropTypes.string
}
//main component
import React, {PureComponent} from 'react'
import {BmRadio} from '#components'
class MainComponent extends PureComponent {
handleOnClick =(event)=>{
console.log(event) //event from Radio button then click in main component
}
render(){
return(
<BmRadio onClick={this.handleOnClick}/>
)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
try event.currentTarget.value. Maybe help if i understand your question correctly
if you want to get value from custom component, you need to pass onChange property in custom component.
const {
classes,
formControlProps,
labelText,
id,
labelProps,
inputProps,
error,
success
onChange //get like props
} = props;
//and in component
<Input
classes={{
root: marginTop,
disabled: classes.disabled,
underline: underlineClasses
}}
id={id}
onChange={onChange}
{...inputProps}
/>
//and in place, where you render custom component create change handler and pass it in this component like onChange={this.handleOnChange}
handleOnChange = (event) => {
this.setState({
value = event.target.value
})
}