0
votes

I am getting the error message

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.in Login (created by Context.Consumer`)

I am have tried the most common fix for that issue but it didn't work. The fix where you use _isMounted = false; componentDidMount() { this._isMounted = true;} if (this._isMounted) {this.setState({ data: data});}

I am also using Context.

SessionContex.js

import React, { Component, createContext } from "react";

export const SessionContext = createContext();

class SessionContextProvider extends Component {
  _isMounted = false;
  state = {
    is_logged_in: false,
    personid: " ",
    firstname: " ",
    lastname: " "
  };
  loggedIn = (loginvalue, personid, firstname, lastname) => {
    this.setState({
      is_logged_in: loginvalue,
      personid: personid,
      firstname: firstname,
      lastname: lastname
    });
  };

  loggedOut = () => {
    this.setState({
      is_logged_in: false,
      personid: " ",
      firstname: " ",
      lastname: " "
    });
  };
  componentDidMount = () => {
    this._isMounted = true;
    const login = localStorage.getItem("is_logged");
    const id = localStorage.getItem("personid");
    const firstname = localStorage.getItem("firtname");
    const lastname = localStorage.getItem("lastname");
    console.log(login);
    if (this._isMounted) {
      this.setState({
        is_logged_in: login,
        personid: id,
        firstname: firstname,
        lastname: lastname
      });
    }
  };

  componentWillUnmount() {
    this._isMounted = false;
  }
  render() {
    return (
      <SessionContext.Provider
        value={{
          ...this.state,
          loggedIn: this.loggedIn,
          loggedOut: this.loggedOut
        }}
      >
        {this.props.children}
      </SessionContext.Provider>
    );
  }
}

export default SessionContextProvider;

Login.jsx

import React, { Component } from "react";
import { SessionContext } from "../context/SessionContext";
import "../Stylesheets/Login.css";
import "..//Stylesheets/global.css";
import { Redirect } from "react-router-dom";

class Login extends Component {
  _isMounted = false;
  static contextType = SessionContext;

  state = {
    password: " ",
    email: " ",
    couldNotfindLogin: true,
    redirect: false
  };

  handleChange = e => {
    this.setState({
      [e.target.name]: e.target.value,
      [e.target.name]: e.target.value
    });
  };

  handleSubmit = async e => {
    e.preventDefault();
    const data = this.state;
    console.log(data);
    const response = await fetch("http://localhost:3080/users/login", {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json"
      },
      body: JSON.stringify(data)
    });

    const reply = await response;

    if (reply.status === 200) {
      const userData = await response.json();
      this.context.loggedIn(
        userData.isValid,
        userData.personid,
        userData.firstname,
        userData.lastname
      );
      localStorage.setItem("is_logged", userData.isValid);
      localStorage.setItem("personid", userData.personid);
      localStorage.setItem("firstname", userData.firstname);
      localStorage.setItem("lastname", userData.lastname);

      this.setState({
        couldNotfindLogin: true,
        redirect: true
      });
    }

    if (reply.status !== 200) {
      this.context.loggedIn(false);
      console.log(this.context);
    }
    this.setState({
      couldNotfindLogin: false
    });
  };
  componentWillUnmount() {
    this.mounted = false;
  }

  render() {
    let { couldNotfindLogin } = this.state;
    if (this.state.redirect === true) {
      return <Redirect to="/" />;
    }

    return (
      <>
        <section className="container">
          <div className="row">
            <div className="col-sm-12 col-md-12 col-lg-12 login-section">
              <div className="form login-box">
                <form className="form-login Login" onSubmit={this.handleSubmit}>
                  <label htmlFor="Email">
                    <p className="login-header">
                      Login to see your past, present, and future self.
                    </p>
                    <input
                      className="login-input"
                      id="Email"
                      type="text"
                      name="email"
                      onChange={this.handleChange}
                      placeholder="Email Address"
                      required
                    />
                    {!couldNotfindLogin ? (
                      <p className="FindYou">We could not find your account </p>
                    ) : (
                      " "
                    )}
                  </label>
                  <label htmlFor="Password">
                    <input
                      className="login-input"
                      id="Password"
                      type="password"
                      name="password"
                      onChange={this.handleChange}
                      placeholder="Password"
                      required
                    />
                  </label>
                  <button className="login-button" type="submit">
                    Login
                  </button>
                </form>
              </div>
            </div>
          </div>
        </section>
      </>
    );
  }
}
1

1 Answers

0
votes

I found a few mistakes that are holding you back. I'll try to brief as to not make this a long-winded answer:

  • Allow the Provider to handle all things related to high-level state and localStorage. Only use local state that's revelant to the component. Also, since setState() is asynchronous, avoid setting it immediately following another asynchronous function(s). While you do have an _isMounted class property, it isn't being checked against when the async function (fetch request) resolves, therefore if you're redirecting the user to another route while React attempts to update the component state, then you'll get these warnings as described above. Put simply, utilize the _isMounted in the async function right before using setState or simply avoid using setState (see example below).
  • The handleChange class field only needs a single [e.target.name]: e.target.value statement. Having two is unnecessary. You can use object destructuring to simplify this down to: [name]: value (see example below).
  • Any component provided to a Route has access to some route props. Among the props is a history object that contains several methods -- among them is a push method. You can use this push method to redirect a user to another url: this.props.history.push("url"). Again this is provided, by default, to a component that resides in a react-router-dom Route.
  • async/await will only work on promises. Therefore, you'll want to await the initial fetch response and then await the fetch response.json() promise. The first promise, returns a Stream object, while the second promise returns a JSON object (this is by design, other libraries, like axios, skip this second promise). Therefore, const reply = await response; expression isn't necessary as it's not waiting for promise a resolve, but instead it's just setting the response's Stream object to a reply variable.
  • Lastly, only use let for variables if you plan on updating or mutating this variable dynamically within the same execution cycle; otherwise, just use const. This becomes especially important because state and props should always be immutable for React to know when or if they have been updated (via another component or by setState). Simply put, avoid using let for state/prop expressions like: let { password } = this.state.

Working example (I'm mimicking the fetch response promise, but you can play around with it by checking out the src/api/index.js file -- for example you can trigger an error response by submitting a password as invalid):

Edit polished-sun-hqlt5