setState() causes state variable to be undefined - reactjs

I am, for the most part, following this tutorial.
My Django API's set up well. I have this service function:
export default class GoalService{
getGoals() {
const url = `${API_URL}/api/goals`;
return axios.get(url).then(response => response.data);
}
}
Which is called by the componentDidMount method in my GoalList:
class GoalTable extends Component {
constructor(props) {
super(props);
this.state = {
goals: [],
now: now.getDate(),
}
}
componentDidMount() {
var self = this;
goalService.getGoals().then(function (result) {
console.log(result);
self.setState({ goals: result.data })
});
}
render() { ... }
(This is step 8 of the above-linked tutorial).
Now, when I try to use { this.state.goals.map(...) }, I get the error TypeError: this.state.goals is undefined. Looking at other threads, a lot of people seem to have had this problem—but it comes about because they've used setState() outside of the request being made and, since setState() is asynchronous, the state is set to something blank. I'm using it inside of a call to then, so I don't think that's the issue.
I tried adding a second argument to then (in case this operation wasn't successful), but, the getGoals() call is successful, and successfully prints out the JSON sent back by Django's API. Similarly, I can see that the request went as expected in the Network tab of the developer tools.
What could be going wrong here? Why isn't the state properly updating w/ the returned JSON?

As mentioned in the comments, the tutorial has a typo, which means that the code tries to access response.data.data instead of response.data.
The fix would be to remove this extra level of drilling down into the object:
componentDidMount() {
var self = this;
goalService.getGoals().then(function (result) {
self.setState({ goals: result }) // no .data
});
}
Also, note that you could make this code simpler by using arrow functions (which automatically bind the this from the place that they're defined) and the object initialization shorthand:
componentDidMount() {
// { goals } is the same as { goals: goals }
goalService.getGoals().then(goals => this.setState({ goals }));
}

Related

Spread Operator not copying results in React

I am trying to update setState in a for loop, but for some reason state isn't being copied it's just being replaced. There should be 2 clients, instead I am getting one. Can anyone tell me why this is happening? The console.log is returning both clients.
const handleViewClients = () => {
for (let i = 0; i < clients.length; i++) {
console.log(clients[i].clientid);
fetch("http://localhost:3005/all-clients/" + clients[i].clientid)
.then((response) => response.json())
.then((result) => {
console.log(result);
setBarbersClient({
...barbersClient,
client: result,
});
});
}
};
I have also tried this... The console.log is returning what I need
Promise.all(
clients.map((client) =>
fetch("http://localhost:3005/all-clients/" + client.clientid)
)
)
.then((resp) => resp.json())
.then((result) => {
console.log(result.username)
setBarbersClient({
...barbersClient,
client: result,
});
});
Here is the route from the server side
app.get("/all-clients/:clientid", (req, res) => {
db.NewClientsx.findOne({
where: {
id: req.params.clientid,
},
}).then((response) => {
res.json(response);
});
});
There some fundamental concepts of sync vs. async code that you aren't accounting for here. State changing (and fetching) is asynchronous, so it won't run until after this synchronous loop has finished being executed (during which the state value will remain unchanged). Also, it's a bad idea to change state in a loop, for this reason and others.
Fetch all the clients, then do one state change at the end with all the fetched data. You can utilise things like Promise.all and Promise.spread to achieve this. Here's an example of doing multiple fetches then dealing with the results in one batch: How can I fetch an array of URLs with Promise.all?
You're making two distinct mistakes of which either is enough to cause the behaviour you're seeing.
1. You're overwriting the client property.
Every time you call the setter function you're overwriting the previous value of the client property. You'll need some data structure that supports multiple values like a map:
setBarbersClient({
...barbersClient,
clients: {
...barbersClient.clients,
[result.id]: result
},
});
You will need to change your render logic somewhat to accomodate the new data structure.
2. You're using a stale reference.
When you access barbersClient its setter may have already been called with a different value and your reference to it still refers to the value of the previous run of the render function. You can make sure your reference is fresh by using a set state action callback.
setBarbersClient(previousValue => {
...previousValue,
clients: {
...previousValue.clients,
[result.id]: result
},
});
previousValue will never be stale inside the set state action function body.

How to wait for getDownloadURL to finish in my mapping function before updating my object array in react state?

getImages() {
const entries_copy = this.state.entries;
entries_copy.map(entry => {
storage.refFromURL(entry.sign_in_photo).getDownloadURL()
.then((url) => {
entry["inPhotoURL"] = url;
storage.refFromURL(entry.sign_out_photo).getDownloadURL()
.then((url) => {
entry["outPhotoURL"] = url;
});
}).catch((error) => {
// Handle any errors
});
});
this.setState({entries: entries_copy});
}
I'm trying to retrieve the download url for images and store them in my entry object inside my entries object array but the problem I'm facing right now is that the setState is called before the urls are retrieved and I have no idea how to wait for it to complete before setting the state. I have searched for similar problems but most of them are solved by executing it inside then() but for mine, I can't execute it inside then() because I have to wait for all the entries to be updated. I have only recently started using React for this project so I'm sorry if the answer is obvious.
This is because the code in asynchronous.
You should call setState inside the .then() function.
I would recommend you to read about Promises in Javascript. They are an important aspect of the language to master.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
In addition to the answer of #TomSlutsky, note that you need to correctly chain your promises and you should not forget to "always return results, otherwise callbacks won't catch the result of a previous promise".
So you need to do as follows:
storage.refFromURL(entry.sign_in_photo).getDownloadURL()
.then((url) => {
entry["inPhotoURL"] = url;
return storage.refFromURL(entry.sign_out_photo).getDownloadURL()
})
.then((url) => {
entry["outPhotoURL"] = url;
this.setState(...);
})
.catch((error) => {
// Handle any errors
});
Note also how the catch() method is called at the end of the chain, see the doc for more details (and possible other options).

Building Credential object needed for Firebase Reauthentication

When trying to build the credential object to reauthenticate a user with Firebase and ReactJS, I am getting cannot read property 'credential' of undefined where undefined is referring to this.app.auth.EmailAuthProvider which should be firebase.auth.EmailAuthProvider.
I have read that it is a static method and cannot be called on an instance, but I am not sure what exactly that means or how to correct my implementation to get the credential needed. I am calling the method in a class based component, but I am still unaware of how all of this ties in to calling a static method.
The method that I am calling is 'this.app.auth.EmailAuthProvider.credential()'
reAuthUser = (data) => {
// console.log('email: ', data.userEmail, 'password: ', data.password);
const userCredential = this.app.auth.EmailAuthProvider.credential(
data.email,
data.password
)
// this.currentUser.reauthenticateWithCredential(data.userEmail, data.password)
// .then(function() {
// alert('user has been reauthenticated')
// }).catch(function(error) {
// console.log('reauth error: ', error)
// })
};
This is in ReactJS, in a class component. this.app is a reference to Firebase and it is called in the constructor:
constructor(props) {
super(props);
this.app = Firebase;
this.currentUser = this.app.auth().currentUser;
};
I know similar questions have been asked and answers have been approved, but they don't make much sense to me at this point.
Assuming that this.app.auth is an instance of the firebase.auth.Auth class, EmailAuthProvider won't be present on that object, as it is not part of the prototype for the firebase.auth.Auth class.
EmailAuthProvider is instead part of the firebase.auth namespace which means it can only be accessed using firebase.auth.EmailAuthProvider (note how auth is not called as a function).
If using imports, you could also use
import { auth as FirebaseAuth } from 'firebase';
FirebaseAuth.EmailAuthProvider.credential(...)

Can't use "this" in stomp client subscribe - React

I have my Spring-Boot service setup so I can send messages through websocket to my browser and it works.
//#MessageMapping
#RequestMapping(value = "/notify")
#SubscribeMapping("/notification")
#SendTo("/topic/notification")
public String sendNotification() throws Exception {
sendMessage();
return "Request to update Tanks has been sent!";
}
public void sendMessage() {
this.messagingTemplate.convertAndSend("/topic/notification", "IT WORKS");
}
Here's the console log from chrome:
<<< MESSAGE
destination:/topic/notification
content-type:text/plain;charset=UTF-8
subscription:sub-1519225601109-13
message-id:f2qodiqn-8
content-length:8
IT WORKS
I want to be able to receive a message from the service and update the state in react, so, that it refetches from the backend. This is what my client looks like:
var socket = new SockJS("http://localhost:6667/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification){
console.log(notification.body);
//this.showNotification(JSON.parse(notification.body).content);
//this.showNotification(notification.body);
})
}, function(err) {
console.log('err', err);
});
And the fetch in componentDidMount()
fetch(`http://localhost:6666/front/objects`)
.then(result=>result.json())
.then(fuelTanks=>this.setState({fuelTanks}))
.catch(function(err) {
console.log('Could not fetch: ' + err.message);
}
)
I can't use this.showNotification(notification.body), hence I can't set the state to be able to refetch my objects. I tried making methods outside the class but then I can't use anything from the main class.
Is there a way to make react run componentDidMount again, or better, just access the fetch method in my class when I get a message from spring through the websocket?
Like this:
componentDidMount(){
var socket = new SockJS("http://192.168.1.139:8610/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification){
refetchTanks(); // call fetch tanks -> can't use "this"
})
}, function(err) {
console.log('err', err);
});
Thanks!
I know, it is a bit old question, but since it pops every time when you search for stomp issue, i thought of answering it. The way to access this in callbacks is to bind callbacks with this first, then the whole of object can be accessed in the callback.
Example:
connectCallBack(){
this.setState({loading:false})
}
errorCallback=()=>{
}
componentDidMount() {
axios.post('http://localhost:8080/subscribe', null, { params: {
deviceId
}})
.then(response => response.status)
.catch(err => console.warn(err));
const socket = new SockJS('http://localhost:8080/test');
const stompClient = Stomp.over(socket);
//stompClient.connect();
stompClient.connect( {}, this.connectCallBack, this.errorCallback);
If see above code both callbacks can access this.
I tried everything to be able to use my class methods and the state in stompClient's .subscribe method. I was able to connect and reconnect if the service died, nevertheless it wasn't working.
I decided to use react-stomp, which worked. I could use a class method in onMessage=.... This is what my code looks like:
<SockJsClient
url = 'http://localhost:8610/refresh/'
topics={['/topic/notification']}
onConnect={console.log("Connection established!")}
onDisconnect={console.log("Disconnected!")}
onMessage={() => this.update()} <------ this method performs a new GET
request
debug= {true}
/>
I also had to send the message in a specific way on the server side, since I was getting a JSON error when sending a string.
this.messagingTemplate.send("/topic/notification", "{"text":"text"}");
<<< MESSAGE
destination:/topic/notification
content-type:text/plain;charset=UTF-8
subscription:sub-0
message-id:aaylfxl4-1
content-length:49
{
"text": "text"
}
It currently works, but I am curious if there are other, better solutions to this issue.
EDIT: a much better solution here! Use the code from the first post and create a variable before connect to be able to access this like this var self = this;, then just access is as self.update() after subscribe!

Angular 2 - undefinded when sharing variable with API data between component

Basically what i try to do is to hit my API once and save the result inside global variable in my Service, and then share and modify this value in my parent and child component with two helpers functions.
repairs.service.ts
public myItems:any[];
public GetRepairs = ():Observable<any> => {
this.headers = new Headers();
this.headers.set('Authorization', 'Bearer' + ' ' + JSON.parse(window.localStorage.getItem('token')));
return this._http.get(this.actionUrl +'repairs'{headers:this.headers})
.map((res) => {return res.json();
}).map((item) => {
let result:Array<any> = [];
if (item.items) {
item.items.forEach((item) => {
result.push(item);
});
}
this.myItems = result;
return this.myItems;
});
};
public GetItems() {
return this.myItems;
};
public UpdateItems(data:any[]) {
this.myItems = data;
};
And then in my main component i do
repairs.component.ts
export class RepairsComponent implements OnInit {
public myItems:any[];
constructor(private _userService:UserService,
private _RepairsService:RepairsService,
public _GlobalService:GlobalService) {
}
ngOnInit() {
this._userService.userAuthenticate();
this.getAllItems();
}
private getAllItems():void {
this._RepairsService
.GetRepairs()
.subscribe((data) => {
this._RepairsService.UpdateItems(data);
},
error => console.log(error),
() => {
this.myItems = this._RepairsService.GetItems();
});
}
}
This work just fine but when i try to invoke GetItems() in child component i get undefinded. I try to do it inside constructor and ngOnInit with the same result.
child.component.ts
export class ChildComponent {
private items:any[] = [];
constructor(private _RepairsService:RepairsService,
private _Configuration:Configuration) {
this.items = this._RepairsService.GetItems();
// undefinded
}
ngOnInit() {
this.items = this._RepairsService.GetItems();
// undefinded
}
}
From what i can see in the limited amount of code you shared, it would seem you are trying to get the items before the http get call finishes and saves the data. I think a better design pattern would be to make the GetItems() function also an observable or promise, and check if the data is there, if not call the http get call, and once that completes send the data back to the different components that need it.
As #MSwehli mentioned with async code execution you can't rely on the order of code lines. In this code:
ngOnInit() {
this.items = this._RepairsService.GetItems();
// undefinded
}
the async code in GetItems(); is scheduled for later execution into the event queue and then continued with the sync code. The scheduled code will be executed eventually but it's not determined when. It depends on the response of the server in this example.
If you return a Promise you can use .then(...) the chain the execution so that your code is only executed when the async execution is completed.
There are two errors/inconsistencies in your code:
userAuthenticate() call followed with getAllItems() call. These calls are async, user is not yet authenticated by the time getAllItems() is called, getAllItems will fail.
Solution here is to chain calls using rxjs flatMap:
//assuming userAuthenticate returns Observable
userService.userAuthenticate().flatMap(()=>{
return repairsService.GetRepairs();
}).subscribe(..process repairs..);
getAllItems() is called nearly at the same time as GetItems(). In most cases it fails also, because previous http request is not completed when GetItems() is called.
In my opinion early initialization is not necessary here, use service directly:
//ChildComponent
ngOnInit() {
this._RepairsService.GetRepairs().subscribe(..do anything with list of repairs i.e. assign to bindable property..);
}
You could add console.log statements in each part of the code to see the order of events in your app.

Resources