Mir Stephen
Mir Stephen

Reputation: 1927

Converting class based component to function based component fails

I am switching from class-based component to function-based components, but my code works in class-based component and doesn't work in the function-based component.

Here is the code:

import { connect } from "react-redux";
import * as actions from "../redux/actions/authActions";

class Login extends Component {
  constructor(props) {
    super(props);
    this.state = {
      email: "",
      password: "",
    };
  }

  authenticate() {
    this.props.auth(this.state.email, this.state.password).then((response) => {
      if (this.props.authenticated) {
        alert("User Is Authenticated");
      } else {
        alert("User Isn't Authenticated");
      }
    });
  }

  render() {
    return (
      <View style={{ flex: 1 }}>
        <TextInput
          autoCapitalize="none"
          keyboardType="email-address"
          style={// styles here}
          placeholder="Enter email"
          value={this.state.email}
          onChangeText={(email) => this.setState({ email })}
        />
        <TextInput
          autoCapitalize="none"
          secureTextEntry
          style={// styles here}
          placeholder="Enter password"
          value={this.state.password}
          onChangeText={(password) => this.setState({ password })}
        />
        <TouchableOpacity onPress={() => this.authenticate()}>
          <Text style={{ marginTop: 20, color: "black", textAlign: "center" }}>
            Login
          </Text>
        </TouchableOpacity>
      </View>
    );
  }
}

const mapStateToProps = (state) => ({
  isLoggedIn: state.auth.isLoggedIn,
  isLoading: state.auth.isLoading,
  userData: state.auth.userData,
  error: state.auth.error,
  authenticated: state.auth.isAuthenticated,
  mainState: state,
});

const mapDispatchToProps = (dispatch) => ({
  auth: (email, password) => dispatch(actions.loginUser({ email, password })),
});

export default connect(mapStateToProps, mapDispatchToProps)(Login);

Converted code to function based component

import { connect } from "react-redux";
import * as actions from "../redux/actions/authActions";

function Login({ auth, authenticated }) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const authenticate = () => {
    auth(email, password).then((response) => {
      if (authenticated) {
        alert("User Is Authenticated");
      } else {
        alert("User Isn't Authenticated");
      }
    });
  };

  return (
    <View style={{ flex: 1 }}>
      <TextInput
        autoCapitalize="none"
        keyboardType="email-address"
        style={ // styles here}
        placeholder="Enter email"
        value={email}
        onChangeText={(text) => setEmail(text)}
      />
      <TextInput
        autoCapitalize="none"
        secureTextEntry
        style={ // styles here}
        placeholder="Enter password"
        value={password}
        onChangeText={(text) => setPassword(text)}
      />
      <TouchableOpacity onPress={() => authenticate()}>
        <Text style={{ marginTop: 20, color: "black", textAlign: "center" }}>
          Login
        </Text>
      </TouchableOpacity>
    </View>
  );
}

const mapStateToProps = (state) => ({
  isLoggedIn: state.auth.isLoggedIn,
  isLoading: state.auth.isLoading,
  userData: state.auth.userData,
  error: state.auth.error,
  authenticated: state.auth.isAuthenticated,
  mainState: state,
});

const mapDispatchToProps = (dispatch) => ({
  auth: (email, password) => dispatch(actions.loginUser({ email, password })),
});

export default connect(mapStateToProps, mapDispatchToProps)(Login);

Here at the Login function. App state isn't updated at the first time, but it gets updated in subsequent attempts.

Thank you for reading and helping.

Upvotes: 1

Views: 40

Answers (1)

Drew Reese
Drew Reese

Reputation: 202686

You've closed over stale state from the render cycle that authenticate was invoked in. React state updates are asynchronous, so if you want to handle state after an update you need to do so in the next render cycle, likely in an useEffect hook (synonymous to the class-based component's componentDidUpdate method). When the authenticated redux state value updates then the component is rerendered.

useEffect(() => {
  if (authenticated) {
    alert("User Is Authenticated");
  } else {
    alert("User Isn't Authenticated");
  }
}, [authenticated]);

authenticate() {
  auth(email, password)
    .then((response) => {
      console.log(response);      
    });
}

Update

This useEffect callback will display one alert or the other, each render. You can add state to make it wait until you've submitted an auth request AND have been authenticated.

const [isAuthenticating, setIsAuthenticating] = useState(false);
const [finishedAuth, setFinishedAuth] = useState(false);

useEffect(() => {
  if (isAuthenticating && finishedAuth) {
    alert(authenticated
      ? "User Is Authenticated"
      : "User Isn't Authenticated"
    );
  }
}, [isAuthenticating, finishedAuth, authenticated]);

authenticate() {
  setIsAuthenticating(true);
  auth(email, password)
    .then((response) => {
      console.log(response);
    })
    .finally(() => setFinishedAuth(true));
}

These two additional state might be great candidates to instead store in your redux state, BTW.

Upvotes: 2

Related Questions