Meteor login error cause unmounted component - reactjs

I know this has been asked before, but I'm not understanding what's causing the problem. In this case when a user submits the login request handleSubmit is called and the page runs Meteor.loginWithPassword. The problem occours when there is a server error, ie. the users password is incorrect. this.setState({ loginError: true }) returns a console error:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the LoginPage component.
I realise it's something to do with the component unmounting when the form is submitted but I don't understand how I suppose to fix it.
export default class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
errors: {},
password: '',
loginError: false
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
Meteor.loginWithPassword(email, password, (error, result) => {
if (error) {
this.setState({ loginError: true });
} else {
console.log("success");
}
});
}
render() {
return (
<div>
<form noValidate>
{this.state.loginError
?
<div className="alert alert-danger text-center" role="alert">
Incorrect username or password.
</div>
: null
}
<SingleInput
name={'email'}
inputType={'email'}
controlFunc={this.handleInputChange}
content={this.state.email}
placeholder={'Email'}
bsSize={null}
error={this.state.errors && this.state.errors.email}
/>
<SingleInput
name={'password'}
inputType={'password'}
controlFunc={this.handleInputChange}
content={this.state.password}
placeholder={'Password'}
bsSize={null}
error={this.state.errors && this.state.errors.password}
/>
<button className="btn btn-success btn-block" onClick={this.handleSubmit}>Login</button>
</form>
</div>
);
}
}

The error is likely in the parent component that calls LoginPage
<App>
<HOCWrapper component={LoginPage} {...this.props} />
</App>
When you're calling this.setState(), it's not updating the Higher Order Component (AsyncWrapper), so it doesn't know to re-render to component.
So, it's passing the state to the HOC, not to LoginPage
const HOCWrapper = LoginPage => {
constructor() {
super()
this.state = {
loginError: true
}
}
So you need to update the HOC's state, and pass it back to the props with something like:
const HOCUpdater = Login => {
constructor() {
super()
this.state = {
loginError: []
}
}
return class HOCUpdater extends Component {
componentWillReceiveProps(nextProps){
this.setState(nextProps);
}
render(){
return createElement(LoginPage,{...this.state});
}
}
}

Related

Why is this.state not updated real-time after setState is issued?

So I'm trying to simulate the state by clicking a button. The 'before' status seems to have the correct value, but why is the 'after' not displaying the correct value even if the setState is already hit by the code?
class App extends Component {
constructor(){
super()
this.state = {isLoggedIn: false}
this.OnClick = this.OnClick.bind(this);
}
OnClick(){
this.setState(prev =>
{
return (prev.isLoggedIn = !this.state.isLoggedIn);
})
console.log(`After setState value: ${this.state.isLoggedInstrong text}`) // setState is done, why is this.state displaying incorrect value?
}
render()
{
console.log(`Before setState value: ${this.state.isLoggedIn}`)
return <Login isLoggedIn={this.state.isLoggedIn} OnClick={this.OnClick} />
}
}
import React from "react";
class Login extends React.Component
{
render()
{
const {isLoggedIn, OnClick} = this.props;
return (
<div>
<button onClick={OnClick} >{isLoggedIn ? "Log Out" : "Log In"} </button>
</div>
)
}
}
export default Login;
OUTPUT:
"Before setState value: false"
(Initial display, button value is: Log In)
When button is clicked:
"After setState value: false" <------ why false when setState has been hit already? Not real-time update until Render is called?
"Before setState value: true"
(Button value is now: Log Out)
The main problem I see in your code is you’re trying to mutate the state.
this.setState(prev => {
return (prev.isLoggedIn = !this.state.isLoggedIn);
})
You have to merge to the state not mutate it. You can do it simply by returning an object like this.
this.setState((prev) => {
return { isLoggedIn: !prev.isLoggedIn };
});
This will fix all the weird behaviours in your code.
Full Code
App.js
import { Component } from "react";
import Login from "./Login";
class App extends Component {
constructor() {
super();
this.state = { isLoggedIn: false };
this.OnClick = this.OnClick.bind(this);
}
OnClick() {
this.setState((prev) => {
return { isLoggedIn: !prev.isLoggedIn };
});
console.log(`After setState value: ${this.state.isLoggedIn}`);
}
render() {
console.log(`Before setState value: ${this.state.isLoggedIn}`);
return <Login isLoggedIn={this.state.isLoggedIn} OnClick={this.OnClick} />;
}
}
export default App;
Login.js
import { Component } from "react";
class Login extends Component {
render() {
const { isLoggedIn, OnClick } = this.props;
return (
<div>
<button onClick={OnClick}>{isLoggedIn ? "Log Out" : "Log In"} </button>
</div>
);
}
}
export default Login;
CodeSandbox - https://codesandbox.io/s/setstate-is-not-update-the-state-69141369-efw46
try this
this.setState({
isLoggedIn:!this.state.isLoggedIn
})
or
this.setState(prev => ({
isLoggedIn:!prev.isLoggedIn
}))

calling function in React SetState gives error that userName is unlabelled why?

import React,{Component} from 'react'
class Formhandler extends Component {
constructor(props) {
super(props)
this.state = {
userName:""
}
}
changer=(event)=>{
this.setState(()=>{
userName : event.target.value
})
}
render()
{
return(
<div>
<label>UserName</label>
<input type="text" value={this.state.userName} onChange={this.changer}/>
</div>
)
}
}
export default Formhandler
You are getting the error because of invalid syntax.
Update changer function
changer = (event) => {
this.setState({ userName: event.target.value });
};
You need to return an object inside the setState function but you are not that's the source of issue(syntax error).
use a function inside setState when your new state value would depend on your previous state value, where the function passed inside the setState will receive previous state as argument
changer = (e) => {
this.setState((prevState) => ({
userName : e.target.value
})
);
}
pass an object to update the state, use this when it doesn't depend on your previous state value.
changer = (e) => {
this.setState({ userName: e.target.value });
};
import React from "react";
class Formhandler extends React.Component {
constructor(props) {
super(props);
this.state = {
userName: "",
};
}
changer(event) {
this.setState(() => ({
userName: event.target.value,
}));
}
render() {
return (
<div>
<label>UserName</label>
<input
type="text"
value={this.state.userName}
onChange={this.changer.bind(this)}
/>
</div>
);
}
}
export default Formhandler;
It will work, compare your version and this

How to update component based on container's state change

I have a React container called UserContainer which renders a component called UserComponent.
The code looks approximately like this (I have removed the unnecessary bits):
// **** CONTAINER **** //
class UserContainer extends React.Component<ContainerProps, ContainerState> {
state = { firstName: "placeholder" };
async componentDidMount() {
const response = await this.props.callUserApi();
if (response.ok) {
const content: ContainerState = await response.json();
this.setState({ firstName: content.firstName });
}
}
private isChanged(componentState: ComponentState) {
return this.state.firstName === componentState.firstName;
}
async save(newValues: ComponentState) {
if (!this.isChanged(newValues)) {
console.log("No changes detected.");
return;
}
const response = await this.props.changeFirstName(newValues.firstName);
if (response.ok) {
const content: ContainerState = await response.json();
this.setState({ firstName: content.firstName });
}
}
render() {
return <UserComponent firstName={this.state.firstName} onSave={(newValues: ComponentState) => this.save(newValues)} />;
}
}
export default UserContainer;
// **** COMPONENT **** //
class UserComponent extends React.PureComponent<ComponentProps, ComponentState> {
constructor(props: ComponentProps) {
super(props);
this.state = { firstName: props.firstName }
}
render() {
return (
<div>
<input type="text" value={this.state.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
<button type="button" onClick={() => this.props.onSave(this.state)}>Save</button>
</div>
);
}
}
export default UserComponent;
The problem is that this.state.firstName in the component is always "placeholder". Even after the container gets its values from the API, the state of the component is not changed (however, the props are changed). When adding console.log into the individual methods, the flow of individual steps is following:
Container render()
Component constructor()
Component render()
Container didMount()
Container render()
Component render()
As you can see, the component constructor is called just once, prior to the container receiving its data from the backend API. Is there a way to pass the updated container state into the component in order to display the real data?
There are really FEW cases where updating state by props is necessary, I suggest you to read the full blog post from facebook under paragraph "Preferred Solutions": https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
class UserComponent extends React.PureComponent<ComponentProps, ComponentState> {
constructor(props: ComponentProps) {
super(props);
this.state = { firstName: props.firstName }
}
componentWillReceiveProps(nextProps: ComponentProps){
if(nextProps.firstName != this.props.firstName){
this.state = { firstName: nextProps.firstName }
}
}
render() {
return (
<div>
<input type="text" value={this.state.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
<button type="button" onClick={() => this.props.onSave(this.state)}>Save</button>
</div>
);
}
}
For latest React version please use getDerivedStateFromProps
You are already passing the updated data to the component. Only mistake is, you are assigning it once. So, whenever you get the updated values, it doesn't reflect, since you don't have only assigned it once.
Two ways to go about it.
If there is no manipulation taking place. Change this.state.firstName to this.props.firstName
<input type="text" value={this.props.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
If there is some manipulation taking place, you'll be doing it in the componentWillReceiveProps method and then setting your firstName state. This method will be triggered whenever you'll be updating the states.
Example -
componentWillReceiveProps(nextProps) {
if(this.props.firstName!==nextProps.firstName) {
//do your validation
}
}
EDIT
As dubes rightly pointed out, componentWillReceiveProps method is deprecated. So you'll have to use the static getDerivedStateFromProps and have to return the new resulting state from this method.
Hope this helps :)

React - conditional render fails trying to display component for the second time

I am creating a React Component to manage the user Input.
This Component, UserInput.js, has the following methods,
renderOrigin - displays a Form component,
renderCities - displays the same form component with diferent props,
renderAddCitiesQuestion - renders two buttons (yes or no) which are handled by,
handleContinue - sets state with the answer to 'continue' question
getChildSate - sets the state received by the child components (like the form)
render - Conditional rendering based on the state. State has the boolean properties, 'start' (for the first render), 'rendercities' and 'renderQuestion'.
The flow of the conditional is as follows.
First, state.start is true and we call renderOrigin;
than, state.start becomes false, state.renderCities becomes true, and we call renderCities(); than, state.rendercities becomes false and state.renderQuestion becomes true, which makes us call renderAddCityQuestion(); now there are two possibilites, either the user clicks the No button, and we should render nothing, or he clicks Yes and state.renderCities becomes true ( and state.renderQuestion becomes false) which calls renderCities() (and it is called, i see it via console.log) but that component is NOT rendered, while the question component remains visible.
I can not see to find the mistake.
Here is the entirety of the code.
import React from 'react';
import Form_city from './Form_city';
class UserInput extends React.Component {
constructor(props) {
super(props);
this.getChildState = this.getChildState.bind(this);
this.handleContinue = this.handleContinue.bind(this);
this.renderOrigin = this.renderOrigin.bind(this);
this.renderCities = this.renderCities.bind(this);
this.renderAddCitiesQuestion = this.renderAddCitiesQuestion.bind(this);
this.state = {
origin: null,
cities: [],
renderCities: false,
renderQuestion: false,
start: true
}
}
getChildState(stateName, stateVal) {
console.log('setting state. received stateName, stateVal', stateName, stateVal);
this.setState({
[stateName] : stateVal
});
console.log('set state done: ', this.state);
}
handleContinue(answer) {
this.state.renderQuestion = false;
answer === 'yes' ? this.state.renderCities = true : this.state.renderCities = false;
console.log('state after clicking answer: ', this.state);
this.render();
}
renderOrigin() {
return(
<div>
<Form_city
divName="originForm"
text="Please select an Origin:"
type="origin"
placeHolder="Origin"
getChildState={this.getChildState}
/>
</div>
);
}
renderCities() {
console.log('rendering city form');
return(
<div>
<Form_city
divName="citiesForm"
text="Which city do you want to visit?"
type="cities"
placeholder="Destination"
getChildState={this.getChildState}
/>
</div>
);
}
renderAddCitiesQuestion() {
console.log('rendering question');
return(
<div>
<p>Do you want to visit any other city?</p> <br />
<button type="button" onClick={this.handleContinue.bind(this, 'yes')}>Yes</button>
<button type="button" onClick={this.handleContinue.bind(this, 'no')}>No</button>
</div>
);
}
render() {
console.log('inside render\n, state: ', this.state);
let content = null;
if (this.state.start === true) {
console.log('inside render origin conditional');
content = this.renderOrigin();
} else if (this.state.renderCities === true) {
console.log('inside render cities conditional');
content = this.renderCities();
} else if (this.state.renderQuestion === true) {
console.log('inside render question conditional');
content = this.renderAddCitiesQuestion();
} else {
content = <p>Weird stuff?</p>
}
return(
<div> {content} </div>
);
}
}
export default UserInput;
here is also the Form component for completeness sake.
import React from 'react';
class Form_city extends React.Component {
constructor(props) {
super(props);
this.state = {data: ''};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState( {data: event.target.value} );
}
handleSubmit(event) {
console.log('clicked submit button');
event.preventDefault();
if (this.props.type === 'origin') {
console.log('inside handle submit Origin, passing: ', this.state.data);
this.props.getChildState('start', false);
this.props.getChildState('origin', this.state.data);
this.props.getChildState('renderCities', true);
} else if (this.props.type === 'cities') {
console.log('inside handle submit Cities');
this.props.getChildState('cities', this.state.data);
this.props.getChildState('renderCities', false);
this.props.getChildState('renderQuestion', true);
}
}
render() {
return(
<div className = {this.props.divName}>
<form onSubmit = {this.handleSubmit}>
<label>
{this.props.text} <br />
<input
type="text"
placeholder={this.props.placeholder}
value={this.state.data}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default Form_city;
Your way to update a state is incorrect, you need to use setState if you want to re-render component with a new state:
handleContinue(answer) {
if (answer === 'yes'){
this.setState({
renderQuestion: false,
renderCities: true,
renderCities: false
})
}
}
Pretty good explanation why: https://stackoverflow.com/a/40309023/7132340
And docs.

Reactjs pass variables from one component to another

I have a component like this:
class LoginPage extends React.Component{
constructor(props) {
super(props);
this.state = {submit:''};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e) {
alert('username and password were submitted: ' + this.props.userName + ' and ' + this.props.password);
e.prevenDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<GetUsername/>
<GetPassword/>
<button type="submit">Login</button>
</form>
)
}
}
The GetUsername and GetPassword components get username and password from user input. My question is that is there anyway I can pass the username and password from these 2 components to the handleSubmit method of the LoginPage component above, so the alert pops up can show correctly?
To achieve what you are looking for, you would need the login form to have its own state which would include the username and password. It would have instance methods that would update those fields. It would pass those functions down to the relevant child components and whenever they change they would call this.props.onChange with the appropriate value. The parent component would then update its appropriate state. Here is an example.
class LoginPage extends React.Component{
constructor(props) {
super(props);
this.state = {
submit:'',
password: '',
userName: ''
};
this.handleSubmit = this.handleSubmit.bind(this);
this.onUserNameChange = this.onUserNameChange.bind(this);
this.onPasswordChange = this.onPasswordChange.bind(this);
}
onUserNameChange(userName) {
this.setState({
userName: userName
})
}
onPasswordChange(password) {
this.setState({
password: password
})
}
handleSubmit(e) {
alert('username and password were submitted: ' + this.state.userName + ' and ' + this.state.password);
e.prevenDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<GetUsername onChange={onUserNameChange}/>
<GetPassword onChange={onPasswordChange}/>
<button type="submit">Login</button>
</form>
)
}
}
this may require some tweaking and as stated before you would need to make sure that you are ingesting the this.props.onChange appropriately in your GetUsername and GetPassword components so they call that function and pass the appropriate argument in.
You need to make username and password part of the state of your LoginPage component, and update the state whenever the input changes. You do this by having an inputChanged function defined on LoginPage which is passed as a prop to your GetUsername and GetPassword components.
class LoginPage extends React.Component {
constructor() {
super();
this.state = {
username: '',
password: ''
};
}
handleSubmit(e) {
e.prevenDefault();
alert('username and password were submitted: ' + this.state.userName + ' and ' + this.state.password);
}
handleInputChange(name, value) {
this.setState({
[name]: value
});
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<GetUsername onChange={newValue => this.handleInputChange('username', newValue)} />
<GetPassword onChange={newValue => this.handleInputChange('password', newValue)} />
<button type="submit">Login</button>
</form>
)
}
Then e.g.
const GetUsername = ({ onChange }) => (
<input type="text" name="username" onChange={(e) => onChange(e.target.value)} />
);

Resources