How to setState inside an api function - reactjs

I have this code inside a class method:
ref
.orderByValue()
.equalTo(email)
.once("value", snapshot => {
if (snapshot.exists()) {
console.log(this);
this.setState({ signedUp: true });
} else {
ref.update({ [newKey]: email });
}
});
It's going to update my Firebase database on submit, unless the user has already signed up. A typeError says this.setState is not a function. But when I console log this, the console prints out the class. Logging this.state also prints out the state.
How do I fix this? I want to update my state from inside this function.

The best solution is to use functional component.
However if you want to use class component, this is probably a binding issue.
So if this is triggered inside function named func, you should bind this function inside the constructor.
...
constructor(props) {
...
this.func = this.func.bind(this);
...
}
func() {
...
ref
.orderByValue()
.equalTo(email)
.once("value", snapshot => {
if (snapshot.exists()) {
console.log(this);
this.setState({ signedUp: true });
} else {
ref.update({ [newKey]: email });
}
});
...
}
If you don't want to bind this function, you should use arrow function.
func = () => {
...
}

Related

How to properly get rid of UNSAFE_componentWillMount

For a React app that I inherited from another developer, one of the pages includes:
import { getLogUser } from "../../appRedux/actions/authAction";
constructor(props) {
super(props);
this.state = {
user: null,
};
}
UNSAFE_componentWillMount() {
let user = getLogUser();
this.setState({ user });
// user state is used inside the render part
}
componentDidMount = () => {
let { username } = getLogUser();
// ... username is used inside some logic within the componentDidMount method.
I would like to get rid of the UNSAFE_componentWillMount method.
Can I remove the UNSAFE_componentWillMount part if I use user: getLogUser() inside the constructor?
If that is indeed the correct way to do it, shouldn't I then also
replace let { username } = getLogUser(); inside
componentDidMount with let { username } = this.state.user?
To start, let me explain what is UNSAFE_componentWillMount first
By defination
UNSAFE_componentWillMount() is invoked just before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering.
So it means UNSAFE_componentWillMount() will be called before render() (the component has not been on UI yet). This is totally opposite of componentDidMount() which is called after render()
To go deeper into why React's team wanted to make it UNSAFE as for a deprecated function, you can check this RFC.
Following up on your questions
Can I remove the UNSAFE_componentWillMount part if I use user: getLogUser() inside the constructor?
The benefit to having your function calls in the constructor is similar to UNSAFE_componentWillMount which makes sure your data available before rendering trigger.
So I'd say yes for your case, you can do it as long as it's not an asynchronous function (like async/await)
constructor(props) {
super(props);
this.state = {
user: await getLogUser(), //YOU CANNOT DO THIS WAY
};
}
This is the correct way
constructor(props) {
super(props);
this.state = {
user: getLogUser(), //no asynchronous call
};
}
So what if getLogUser() is asynchronous? componentDidMount comes in handy. It will be triggered after first rendering but you can wait for your data as much as you want and beyond that, it won't block your UI's interactions (or you can show a loading UI instead)
componentDidMount = async () => {
const user = await getLogUser()
setState({ user })
}
render() {
//show loading if `user` data is not populated yet
const { user } = this.state
if(!user) {
return <div>Loading</div>
}
}
If that is indeed the correct way to do it, shouldn't I then also replace let { username } = getLogUser(); inside componentDidMount with let { username } = this.state.user?
Yes, indeed. You can do it if you already populate user state in constructor, but you need to ensure your function will be executed in a small amount of time. If your function call takes too long, that will cause UI problems due to the blocked rendering.
//trigger before first rendering
constructor(props) {
super(props);
this.state = {
user: getLogUser(), //no asynchronous call
};
}
//trigger after first rendering
componentDidMount = () => {
const { username } = this.state.user;
}

React setState {} not setting variable

I have the following code for a main view and user login:
export class MainView extends React.Component {
constructor(){
super();
this.state = {
array: [],
user: null,
userData: {},
};
}
setUserData(user) {
if (user) {
this.setState({ userData: user.user });
console.log(user.user);
console.log (userData); /* errors in console */
} else {
console.log('userData not set');
}
}
onLoggedIn(authData) {
this.setState({ user: authData.user.Username });
/* setting localstorage key:item */
// console.log(authData);
localStorage.setItem('token', authData.token);
localStorage.setItem('user', authData.user.Username);
this.getArrayObjects(authData.token);
this.setUserData(authData);
}
using Passport to get the auth data and token. I don't get why it will log user.user in the setUserData function but will log an "undefined" error for the userData variable in the same function. Any ideas?
You are trying to access an undefined variable.
userData is present inside the state.
So you should access it with this.state.userData.
But even it you write console.log(userData);, it will print {} because in React, setState is asynchronous. Meaning, the state is not updated immediately.
If you want to check whether state has been update or not, check it like this.
this.setState({ userData: user.user }, () => {
console.log(this.state.userData);
});
The, second parameter to setState is a function that is called once state is successfully updated. So, inside there you can see the value in console.
You can't access the state directly as a var. You need to access the state, and the the properties:
console.log(state.userData);
Also, in your code, when you print state.userData, probably you will see the old value, since the setState is an async function.

Firebase authentication in ReactJS

I'm trying to authenticate the user using the google. Everything is going fine. But when I try to console.log(this) inside of the onAuthStateChanged() is printing something like
Object { next: componentDidMount(user), error: noop(), complete: noop()
}
​
complete: function noop()​
error: function noop()​
next: function componentDidMount(user)​
<prototype>: Object { … }
But if I console.log(this) outside of the onAuthStateChanged() its working fine with the information of the current class.
What I want is, I want to update the state information with the user details by using this this.setState() method. But this.setState() method is not working inside of the onAuthStateChanged().
How can I do it?
Here is my code
componentDidMount = ()=>{
console.log(this)
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
console.log(this)
console.log("userSignedIn")
} else {
console.log(" No user is signed in.");
}
});
};
The problem is that this has a different meaning inside the callback, due to how you declare it.
The simplest fix is to use a fat arrow to declare the callback, same as you do elsewhere:
componentDidMount = ()=>{
console.log(this)
firebase.auth().onAuthStateChanged((user) => { // 👈
if (user) {
console.log(this)
console.log("userSignedIn")
} else {
console.log(" No user is signed in.");
}
});
This is an incredibly common problem, so I recommend reading more on it here: How to access the correct `this` inside a callback?

State not changing after calling this.setState

I am getting the data from my form component and trying to set the state of my app component with this data.
However, the state.data is an empty object and is not updating the data. I console log the model data before setting it to check if it exists. Their is data within the model.
import React, { Component, Fragment } from "react";
import Form from "../components/Form";
import product from "./product.json";
class App extends Component {
constructor() {
super();
this.state = {
data: {}
};
}
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
});
console.log("Form: ", this.state);
}
render() {
const fields = product.fields;
return (
<Fragment>
<div>Header</div>
<Form
model={fields}
onSubmit={(model) => {this.onSubmit(model);}}
/>
<div>Footer</div>
</Fragment>
);
}
}
export default App;
setState() is an async call in React. So you won't likely get the updated state value in the next line. To check the updated value on successful state update, you could check in the callback handler.
Change this
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
});
console.log("Form: ", this.state);
}
to
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
}, () => {
console.log("Form: ", this.state);
});
}
As per the react docs, setState is an asynchronous call. You can ensure your state has updated to perform a particular action in two ways as shown below:
You can pass the setState a function which will have your current state and props and you the value you return will be your next state of the component.
Keep in mind following:
state is a reference to the component state at the time the change is
being applied. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
state and props.
Following is an example:
this.setState((state, props) => {
//do something
return {counter: state.counter + props.step};
});
You can pass a callback to the setState function as mentioned in Dinesh's
answer. The callback will be executed once the state has been updated successfully hence ensuring you will have the updated state in the call back.
Following is an example:
this.setState({ ...new state }, () => {
// do something
});
Hope it helps.
I just want to add, that if you will do like this its not going to work:
this.setState({things} , console.log(this.state))
You have to pass a refarence to the call back and not the exscutable code itself. If you won't do so, the function will envoke before the state is updated,even you will see the log.

ReactNative: this.setState Error: null is not an object

React script
class TransactionsList extends Component {
constructor(props) {
super(props);
this.state = {
activeAccountId: "",
accessToken: "",
TransactionsData: "",
};
}
replaceRoute(route, passProps) {
this.props.replaceRoute(route, passProps);
}
async _getToken() {
try {
let accessToken = await AsyncStorage.getItem('AUTH_TOKEN');
if(!accessToken) {
this.replaceRoute('login');
} else {
this.setState({accessToken: accessToken})
}
} catch(error) {
Alert.alert('Print Errorr', error.message)
this.replaceRoute('login');
}
}
componentWillMount(){
this._getToken()
let token = 'Token '+this.state.accessToken
this.load_data(token)
}
render() {
return (
<Container>
// other code
</Container>
)
}
}
Got error in setState in getToken below is catch(error) block output
Print Error null is not an object(evaluating
prevComponentInstance._currentElement)
But same above code works in other screens.
It is not advisable to make api calls in componentWillMount because it is possible that the component will not have been mounted when the api call has finished and you call setState.
Instead, you should make api calls in componentDidMount. According to the documentation:
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. Setting state in this method will
trigger a re-rendering.
And, you also need to bind _getToken as #Jazib mentioned.
You need to bind _getToken method using something like this:
this._getToken().bind(this)
Or you can do this in the constructor for better code (I prefer this one):
constructor(props) {
super(props);
this.state = {
activeAccountId: "",
accessToken: "",
TransactionsData: "",
};
this._getToken() = this._getToken().bind(this)
}
Hope this helps
I know I am replying a bit late but a better way is to use an arrow function instead of using bind on a named function. So you could write your _getToken method like this:
const _getToken = async () => {
// your logic here
}
The arrow function here implicitly assigns the current instance of the component to this keyword whereas in the named function you have to give the this keyword the context by using bind method as mentioned by others.
Also, componentWillMount is now deprecated and its better if you call your method in componentDidMount

Resources