React unable to set two way binding - reactjs

Here are my two components. I just need to update my state in the login component. I am not sure what I am doing wrong here. I am trying to pass the data on change to the login component. The data is getting captured in e.target.value for each character, but then it resets the state.
I have tried to move the userObj inside the state as well,but does not work
import React, { Component } from 'react';
import FormHeader from './FormHeader'
class NonLoggenInForm extends Component {
render() {
return (
<div className="marginTop1 formPanel">
<FormHeader label={this.props.label}/>
{this.props.content.map((key)=>{
return <input type={key.type}
value = {key.value}
placeholder = {key.name}
required = {key.required}
onChange = {e=>this.props.onChange(e)}
className = "formInput"
name = {key.name}
key = {key.id}
/>;
})}
<button onClick={this.props.onSubmit}> Sign in</button>
</div>
);
}
}
export default NonLoggenInForm;
import React, { Component } from 'react';
import Logo from '../shared/Logo';
import NonLoggenInForm from '../shared/NonLoggenInForm';
class Login extends Component {
changeHandler = (e) => {
console.log(e.target.value);
this.setState({
[e.target.name] : e.target.value
});
}
loginHandler = (e) => {
e.preventDefault();
console.log(this.state);
}
render() {
let userObj = [
{
name : 'userId',
type: 'text',
required: true,
value : '',
id : 1
},
{
name : 'password',
type : 'password',
required : true,
value : '',
id : 2
}
];
return (
<div className="nonLoggedInPages">
<Logo/>
<NonLoggenInForm content={userObj} label="Sign in" onSubmit={this.loginHandler} onChange={this.changeHandler}/>
</div>
);
}
}
export default Login;

Moved the user Obj to state again and changed the onChange function as below
changeHandler = (e) => {
let objIndex = this.state.userObj.findIndex((ele)=>{
return ele.name === e.target.name;
});
let upadtedObject = [...this.state.userObj];
upadtedObject[objIndex].value = e.target.value;
this.setState({
userObj : upadtedObject
});
e.target.value = this.state.userObj[objIndex].value;
}

Related

Update component state using props with another stateless component

I want to update the state of my stateful component 'LoginForm' inside of my stateless component 'InputRetro' I don't know the proper way of achieving it. Please help me.
'LoginForm' component codes:
import React, { Component } from 'react';
import './LoginForm.css';
import InputRetro from './InputRetro';
class LoginForm extends Component {
state = {
loginData : {
username : null,
password : null,
},
inputs : [
{ id : 'username', label : 'Username', type : 'text' },
{ id : 'password', label : 'Password', type : 'password' },
],
}
inputsJSX = this.state.inputs.map(input => {
return (
<InputRetro id={input.id} label={input.label} type={input.type} key={input.id} />
);
});
handleLogin = e => {
e.preventDefault();
// blank function.
}
render() {
return (
<form className='login-form' onSubmit={this.handleLogin}>
{ this.inputsJSX }
</form>
);
}
}
export default LoginForm;
'InputRetro' component codes:
import React from 'react';
import './InputRetro.css';
let InputRetro = props => {
const { id, label, type, placeholder } = props;
return (
<div className='InputRetro'>
<label>{label}</label>
<input id={id} type={type} placeholder={placeholder} />
</div>
);
}
export default InputRetro;
I want to update 'LoginForm' component's state values inside of 'InputRetro' component using an onChange or onKeyUp the state values I want to change are state.loginData.username and state.loginData.password when the input tag value of 'InputRetro' component is changed.
more details:
state.loginData.username value will store the value of 'InputRetro' component's input tag with an id of username with an onChange or onKeyUp event. Same in state.loginData.password.
You can pass a CallBack function from the parent component(LoginForm) to the child component(Input Retro) and then trigger that action in the child component on the button click.
LoginForm.js
import React, { Component } from 'react';
import './LoginForm.css';
import InputRetro from './InputRetro';
class LoginForm extends Component {
state = {
loginData : {
username : null,
password : null,
},
inputs : [
{ id : 'username', label : 'Username', type : 'text' },
{ id : 'password', label : 'Password', type : 'password' },
],
}
const ChangeState = (childData, flag) => {
flag === 1 ? state.loginData.username = childData : state.loginData.password = childData
}
inputsJSX = this.state.inputs.map(input => {
return (
<InputRetro id={input.id} label={input.label} type={input.type} callBackHandler={ChangeState} key={input.id} />
);
});
handleLogin = e => {
e.preventDefault();
// blank function.
}
render() {
return (
<form className='login-form' onSubmit={this.handleLogin}>
{ this.inputsJSX }
</form>
);
}
}
export default LoginForm;
In the child component you can call the callback function at whatever moment you like.
InputRetro.js
import React from 'react';
import './InputRetro.css';
let InputRetro = props => {
const { id, label, type, placeholder, callBackHandler } = props;
const handleClick = (event) => {
label === 'email' ? callBackHandler(event.target.value, 1) : callBackHandler(event.target.value, 2)
}
return (
<div className='InputRetro'>
<label>{label}</label>
<input id={id} type={type} placeholder={placeholder} onChange={event => handleClick(event)} />
</div>
);
}
export default InputRetro;

How can I convert a Class Component which extends another Class component in a Functional Component in ReactJS?

How can I convert a Class Component which extends another Class component in a Functional Component in ReactJS?
input.jsx [Functional Component]
const Input = ({ name, label, error, ...rest }) => {
return (
<div className="mb-3">
<label htmlFor={name} className="form-label">
{label}
</label>
<input
autoFocus
{...rest}
id={name}
name={name}
className="form-control"
/>
{error && <div className="alert alert-danger">{error}</div>}
</div>
)
}
export default Input
form.jsx [Class Component]
import React, { Component } from "react"
import Input from "./input"
import Joi from "joi"
class Form extends Component {
state = {
data: {},
errors: {}
}
validate = () => {
const options = { abortEarly: false }
const schemaJoi = Joi.object(this.schema)
const { error } = schemaJoi.validate(this.state.data, options)
if (!error) return null
const errors = {}
error.details.map(item => (errors[item.path[0]] = item.message))
return errors
}
validateProperty = ({ name, value }) => {
const obj = { [name]: value }
const schema = {
[name]: this.schema[name]
}
const schemaJoi = Joi.object(schema)
const { error } = schemaJoi.validate(obj)
return error ? error.details[0].message : null
}
handleSubmit = e => {
e.preventDefault()
const errors = this.validate()
console.log(errors)
this.setState({ errors: errors || {} })
if (errors) return
this.doSubmit()
}
handleChange = ({ currentTarget: input }) => {
const errors = { ...this.state.errors }
const errorMessage = this.validateProperty(input)
if (errorMessage) errors[input.name] = errorMessage
else delete errors[input.name]
const data = { ...this.state.data }
data[input.name] = input.value
this.setState({ data, errors })
}
renderButton = label => {
return (
<button disabled={this.validate()} className="btn btn-primary">
{label}
</button>
)
}
renderInput = (name, label, type = "text") => {
const { data, errors } = this.state
return (
<Input
name={name}
label={label}
error={errors[name]}
type={type}
value={data[name]}
onChange={this.handleChange}
/>
)
}
}
export default Form
loginForm.jsx [Class Component which extends the other]
import Joi from "joi"
import Form from "./common/form"
class LoginForm extends Form {
state = {
data: { username: "", password: "" },
errors: {}
}
schema = {
username: Joi.string().required().label("Username"),
password: Joi.string().required().label("Password")
}
doSubmit = () => {
console.log("Submitted")
}
render() {
return (
<div>
<h1>Login</h1>
<form onSubmit={this.handleSubmit}>
{this.renderInput("username", "Username")}
{this.renderInput("password", "Password", "password")}
{this.renderButton("Login")}
</form>
</div>
)
}
}
export default LoginForm
I already know how to convert a simple Class Component to a Stateless Functional Component but what I don't know is how to convert a Class Component which extends another Class Component.
Please, may you explain me how?

React snapshot test with Jest, how can a child component calls a function from parent component?

I have a basic app for the sack of my training composed of tow Components App and User and a snapshot test file for the User.
The test passes for now but I want to test the methods that update the state of the parent but I don't know how to proceed, Please Help.
App component
import React from 'react'
import './App.css'
import data from './data/users-data.json'
import User from './components/User/User'
export default class App extends React.Component {
constructor() {
super()
this.state = {users: data}
this.clickFollowHandler = this.clickFollowHandler.bind(this)
this.clickStarHandler = this.clickStarHandler.bind(this)
}
clickFollowHandler(id) {
this.setState(prevState => {
const updatedUsers = prevState.users.map(user => {
if (user.id === id) {
user.isFollowed === 'active' ? user.isFollowed = 'idle' : user.isFollowed = 'active'
}
return user
})
return {
users: updatedUsers
}
})
}
clickStarHandler(id) {
this.setState(prevState => {
const updatedUsers = prevState.users.map(user => {
if (user.id === id) {
user.isStared === 'active' ? user.isStared = 'idle' : user.isStared = 'active'
}
return user
})
return {
users: updatedUsers
}
})
}
render() {
return (
<div>
{this.state.users.map(u => {
return (
<User
key={u.id}
id={u.id}
name={u.name}
date={u.date}
readingTime={u.readingTime}
isStared={u.isStared}
isFollowed={u.isFollowed}
image={u.image}
handleFollowClick={this.clickFollowHandler}
handleStarClick={this.clickStarHandler}
/>
)
})}
</div>
)
}
}
User component
import React from 'react'
import classes from './User.module.css'
import myImage from '../../assets/images/avatar.png'
import PropTypes from 'prop-types'
const User = props => {
return(
<div className={classes.User} key={props.id}>
<div className={classes.name}>name: {props.name}</div>
<button onClick={() => props.handleFollowClick(props.id)}>
{props.isFollowed === 'active' ? 'Unfollow' : 'Follow'}
</button>
<input
className={classes.hvrIconPop}
checked={props.isStared === 'active' ? true : false}
onChange={() => props.handleStarClick(props.id)}
type='checkbox'
/>
<div>date: {props.date}</div>
<div>reading time: {props.readingTime}</div>
<img src={myImage} alt={props.name} />
</div>
)
}
User.propTypes = {
handleFollowClick: PropTypes.func.isRequired,
handleStarClick: PropTypes.func.isRequired,
}
export default User
User.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import User from './User';
const users = [{
"id": "5d552d0058f193f2795fc814",
"isFollowed": "active",
"isStared": "idle",
"image": "./assets/images/avata.png",
"readingTime": 20,
"name": "Walton Morton",
"date": "Aug 9"
}];
it('renders correctly when there are no users', () => {
const tree = renderer.create(<User
users={[]}
handleFollowClick={() => 'test'}
handleStarClick={() => {}} />).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders correctly when there is one user', () => {
const tree = renderer.create(<User users={users}
handleFollowClick={() => 'test'}
handleStarClick={() => {}}
/>).toJSON();
expect(tree).toMatchSnapshot();
});
You pass your User component a mock function (jest.fn()) through its handleFollowClick and handleStarClick props, then simulate whatever is supposed to trigger the parent action (a click event on the <button /> or a change event on the <input />) and test whether the corresponding mock function was called.
I personally always use Enzyme for this sort of thing, but here's how I'd assume it works using react-test-renderer based on this answer:
const mockFollowClick = jest.fn();
const mockStarClick = jest.fn();
const tree = renderer.create(<User
{...users[0]}
handleFollowClick={mockFollowClick}
handleStarClick={mockStarClick}
/>)
const button = tree.root.findByType('button');
const input = tree.root.findByType('input');
button.props.onClick();
expect(mockFollowClick).toHaveBeenCalled();
input.props.onChange();
expect(mockStarClick).toHaveBeenCalled();
You can even check if it was called with the correct user id:
button.props.onClick();
expect(mockFollowClick).toHaveBeenCalledWith("5d552d0058f193f2795fc814");

Unable to update parent prop in react

I can insert the input value say "1,2,3" and when backspace it removes all but in the console "1" is still shown i.e., House{props{house{property{rent:1}}}}
I am providing the code here which has 3 files.
(1) house.js
import ValInput from "main/components/val-input";
class House extends Component {
state = {
rent:"",
};
componentDidMount() {
if (this.props.house.rent) {
const { rent} = this.props.house;
this.setState({ rent });
}
}
onChange = (e) => {
const rent = parseInt(e.target.value.replace(string);
this.setState({
rent,
});
};
render(){
const {house} = this.props;
const {rent} = this.state;
...
<ValInput
type="text"
value={ rent }
onChange={e => {
this.onChange(e);
}}
/>
}
(2) val-input\index.js
import React from "react";
import Input from "main/components/input";
const ValInput = props => (
<Input
{...props}
type={props.type ? props.type : "text"}
/>
);
export default valInput;
(3) components/input/index.js
import React from "react";
const noOp = () => {};
const Input = ({
onBlur = xP,
...otherProps
}) => (
<input
onBlur={e => {
e.target.placeholder = placeholder;
onBlur(e);
}}
{...otherProps}
/>
);
export default Input;
The expected result should be, after emptying the value say with backspace, and visit the page next time, the input field should be empty and should not show old value.
Check this CodeSandbox out I replicated your code and if I understood the problem right then fixed it
https://reactjs.org/docs/cross-origin-errors.html
For updating #NaderZouaoui, has given me an example how to do Call back :
1. Child file :
onChange={e => {
this.onChange(e);
}}
onChange = e => {
this.setState({
rent
});
this.props.callback(rent);
};
2. Parent file :
state = {
rent: ""
};
handleChangeRent = newRent => {
this.setState({ rent: newRent });
};
render(){
return(
<House house={{ rent }} callback={this.handleChangeRent} />
);
}

FormValidation with Button State

In my React app, I have a component call <ComingSoonForm/> Inside that, I have a TextInputField. If the Email valid, the Notify-Button is able. If the Email is invalid, the Notify-Button is disabled.
Here is my Component File:
import React, { Component } from "react";
import { TextInputField, toaster, Button, } from "evergreen-ui";
import Box from 'ui-box';
import { validateEmail } from "../FormValidation/FormValidator";
class ComingSoonForm extends Component {
constructor(props) {
super(props);
this.state = {
emailErr: {
status: true,
value: ""
},
email: "",
isDisabled: true,
};
this.handleSubmit = this.handleSubmit.bind(this);
this.checkFormStatus = this.checkFormStatus.bind(this);
}
handleEmailInput = e => {
const email = e.target.value;
this.setState({ email: email});
console.log(this.state.email);
this.checkFormStatus();
};
handleSubmit() {
if (this.checkFormStatus()) {
alert("Form is OK")
}
}
checkFormStatus() {
// form validation middleware
const { email } = this.state;
const emailErr = validateEmail(email);
if (!emailErr.status) {
this.setState({isDisabled:false})
return true;
} else {
this.setState({
emailErr,
});
return false;
}
}
render() {
return (
<div>
<Box className="welcomePageWelcomeInnerLoginButton">
<TextInputField
marginTop={15}
width={200}
onChange={this.handleEmailInput}
value={this.state.email}
type="email"
placeholder="Your email-address"
inputHeight={40}
/>
</Box>
<Button height="40" appearance="primary" marginBottom={5} className="welcomePageWelcomeInnerLoginButtonWidth" disabled={this.state.isDisabled} onClick={this.handleSubmit}>Notify Me</Button>
</div>
);
}
}
export default ComingSoonForm;
But this case doesn't work correctly. So when the command console.log(this.state.email) in the handleEmailInput Function run, I get the following data in the console:
I type one letter (t) and I get:
//ComingSoonForm.js:25
I type a second letter (t) and I get:
t //ComingSoonForm.js:25
t //FormValidator.js:10
Why do I have to enter two letters in order for one to appear in the console?
setState is asynchronous, you can pass a callback method as a second parameter like this:
handleEmailInput = e => {
const email = e.target.value;
this.setState({ email: email }, () => console.log(this.state.email));
this.checkFormStatus();
};

Resources