Update component state using props with another stateless component - reactjs

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;

Related

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} />
);
}

React unable to set two way binding

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;
}

React Redux - Uncaught TypeError: Cannot read property 'setState' of undefined

New to this.
I have looked for answers here and here.
am using Redux as well. As per good practice I have a container "AddressContainer" and its component "Address".
The AddressContainer is as follows -
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { Field, change } from 'redux-form'
import { Col, Panel, Row } from 'react-bootstrap'
import Select from 'react-select'
import Address from './address'
import { ensureStateData, getSuburbs } from './actions'
import { CLIENT_FORM_NAME } from '../clients/client/client'
export class AddressContainer extends Component {
static contextTypes = {
_reduxForm: PropTypes.object.isRequired,
}
constructor(props, context) {
super(props, context)
this.state = {
selectedSuburb: null,
}
}
componentDidMount() {
this.props.ensureStateData()
}
// Manage asyncSelect for new data request - for suburbs.
handleSuburbSearch = (query) => {
const { addressData } = this.props
const companyStateId = addressData.companyStateId
if (!query || query.trim().length < 2) {
return Promise.resolve({ options: [] })
}
const queryString = {
query: query,
companyStateId: companyStateId,
}
return getSuburbs(queryString)
.then(data => {
return { options: data }
})
}
render() {
const {
initialValues,
addressData,
updatePostcodeValue,
} = this.props
//const { value } = this.state
const sectionPrefix = this.context._reduxForm.sectionPrefix
if (addressData.isLoading || !addressData.states.length) {
return (
<p>Loading</p>
)
}
if (addressData.error) {
return (
<p>Error loading data</p>
)
}
const companyStateId = addressData.companyStateId
// initialValues = {
// ...initialValues.Address=null,
// state: addressData.states.find(option => option.stateId === companyStateId),
// }
return (
<Address
initialValues={initialValues}
addressData={addressData}
handleSuburbSearch={this.handleSuburbSearch}
/>
)
}
}
const mapStateToProps = (state) => ({
initialValues: state.address,
companyStateId: state.companyStateId,
addressData: state.addressData,
})
const mapDispatchToProps = (dispatch) => ({
ensureStateData: () => dispatch(ensureStateData()),
getSuburbs: (values) => dispatch(getSuburbs(values)),
updatePostcodeValue: (postcode, sectionPrefix) => dispatch(change(CLIENT_FORM_NAME, `${sectionPrefix ? (sectionPrefix + '.') : ''}postcode`, postcode))
})
export default connect(mapStateToProps, mapDispatchToProps)(AddressContainer)
The Address component is as follows -
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { Field, reduxForm, change } from 'redux-form'
import { Col, Panel, Row } from 'react-bootstrap'
import Select from 'react-select'
import FormField from '../formComponents/formField'
import TextField from '../formComponents/textField'
import StaticText from '../formComponents/staticText'
export const ADDRESS_FORM_NAME = "Address"
export const Address = (props) => {
const { addressData, handleSuburbSearch } = props
const { reset } = props
return (
<Panel header={<h3>Client - Address Details</h3>}>
<Row>
<Field component={TextField}
name="address1"
id="address1"
type="text"
label="Address Line 1"
placeholder="Enter street 1st line..."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
<Field component={TextField}
name="address2"
id="address2"
type="text"
label="Address Line 2"
placeholder="Enter street 2nd line..."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
<Row>
<Field
component={props => {
const { input, id, placeholder, type } = props
const { fieldCols, labelCols, controlCols, label, inputClass } = props
// just the props we want the inner Select textbox to have
const { name, onChange } = input
const onStateChange = (state) => {
console.log('onStateChange', state)
onChange(state)
}
return (
<FormField
id={id}
label={label}
fieldCols={fieldCols}
labelCols={labelCols}
controlCols={controlCols}
inputClass={inputClass}
>
<Select
name={name}
onChange={onStateChange}
placeholder="Select state"
valueKey="id"
options={addressData.states}
labelKey="stateLabel"
optionRenderer={option => `${option.stateShortName} (${option.stateName})`}
value={input.value}
selectValue={Array.isArray(input.value) ? input.value : undefined}
/>
</FormField>
)
}}
name="state"
id="state"
label="State."
fieldCols={6}
labelCols={3}
controlCols={6}
/>
</Row>
<Row>
<Field
component={props => {
const { input, id, placeholder, type } = props
const { fieldCols, labelCols, controlCols, label, inputClass } = props
const { name, value, onChange, onBlur, onFocus } = input
const inputProps = {
name,
value,
onChange,
onBlur,
onFocus,
}
const onSuburbChange = (value) => {
console.log('onSuburbChange: ', value)
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
return (
<FormField
id={id}
label={label}
fieldCols={fieldCols}
labelCols={labelCols}
controlCols={controlCols}
inputClass={inputClass}
>
<Select.Async
{...inputProps}
onChange={onSuburbChange}
valueKey="id"
labelKey="suburbName"
loadOptions={handleSuburbSearch}
backspaceRemoves={true}
/>
</FormField>
)
}}
name="suburb"
id="AddressLocation"
label="Suburb."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
<Row>
<Field component={StaticText}
name="postcode"
id="postcode"
label="Postcode."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
</Panel>
)
}
Address.propTypes = {
handleSuburbSearch: PropTypes.func.isRequired,
}
const AddressForm = reduxForm({
form: ADDRESS_FORM_NAME,
})(Address)
export default AddressForm
The problem is with the following function in the address component below and with setState which it says is undefined -
const onSuburbChange = (value) => {
console.log('onSuburbChange: ', value)
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
You will note there is a console.log for "value". This produces the result:
onSuburbChange: Object {id: 6810, suburbName: "Eaglehawk", postcode: "3556", state: "VIC"}
I am using React-Select as the async dropdown. This all works. If I select an option I get dropdown options but select one and it gives me the error.
I am using react state here for selectSuburb options as I dont need to update redux with this - just react state.
It seems all right but I still get the error. Why am I getting this error and how do I fix it?
This specific error is caused by the fact that the <Address /> component is a stateless functional component and cannot have a this.state object or a setState function in it. However, more generally it looks like you are expecting state and functions from the <AddressContainer /> component to be available to the child <Address /> component, but that cannot happen. In this case, you are wanting to modify the state of the parent by calling setState on the child.
A child React component (in this case <Address />) will only have state/functions/properties from its parent that are explicitly passed down as props to that component. If you want to change the local state of a component it must happen on the component with the local state. If you want to have a child component trigger some type of function call on the parent, then that function must be passed down as a prop to the child and the child can call it.
If I understand your code correctly, you want 3 things to happen when the Suburbs FormField is changed, in this order.
The selectedSuburb state on <AddressContainer /> is updated.
The onChange of the Redux-Form <Field /> in <Address /> is triggered.
The updatePostCode action is fired off.
If that is correct, then you will need to move your onSuburbChange to the <AddressContainer /> and pass it to <Address /> as a prop. However, you cannot call the onChange of the Redux-Form <Field /> inside <AddressContainer />. Therefore, you can make that function expect to receive a callback function that will be fired off after the state updates. Then you can define the callback in the child component but the state-changing function in the parent. As long as you pass down needed props on the parent, such as updatePostCode and sectionPrefix, you'll be golden. Here's what it would look like:
AddressContainer
export class AddressContainer extends Component {
/* Everything else in this component */
onSuburbChange = (value, callback) => {
this.setState({ selectedSuburb: value }, callback);
}
render() {
/* Other stuff inside render */
return (
<Address
initialValues={initialValues}
addressData={addressData}
handleSuburbSearch={this.handleSuburbSearch}
onSuburbChange={this.onSuburbChange}
updatePostcodeValue={this.props.updatePostcodeValue}
sectionPrefix={sectionPrefix}
/>
);
}
}
Address
export const Address = (addressProps) => {
return (
/* All other JSX */
<Field
component={props => {
const { input } = props;
const handleSuburbChange = (value) => {
addressProps.onSuburbChange(value, () => {
input.onChange(value);
addressProps.updatePostcodeValue(value ? value.postcode : null, addressProps.sectionPrefix)
});
}
return (
<Select.Async
onChange={handleSuburbChange}
/>
)
}}
);
}
As you can see, there is going to be a naming conflict between the different props variables in the <Address /> component, so I call the main props addressProps to avoid this.

Resources