Issue
I want to be able to receive mqtt messages and display them on a web app. I'm working with AWS Amplify's PubSub and the function calls happen outside of the class component. I can't directly access any of the instance functions/properties from the function outside of it but I need some way to trigger a setState change so that the web app can be re-rendered, right?
I've tried just directly calling the function from the React class, but there's still no re-rendering happening. I've tried making an outside variable, storing the message received in it and then accessing the variable from the React class but still no trigger. Then I researched and found that I could force a re-render every couple of seconds but read that doing such a thing should be avoided. I should be using ComponentDidMount and setState functions but not really understanding how to get this to work.
Again all I'm really trying to do is get the message and update the web app to display the new message. Sounds pretty simple but I'm stuck.
import...
var itemsArr = [];
function subscribe() {
// SETUP STUFF
Amplify.configure({
...
});
Amplify.addPluggable(new AWSIoTProvider({
...
}));
// ACTUAL SUBSCRIBE FUNCTION
Amplify.PubSub.subscribe('item/list').subscribe({
next: data => {
// LOG DATA
console.log('Message received', data);
// GET NAMES OF ITEMS
var lineItems = data.value.payload.lineItems;
lineItems.forEach(item => itemsArr.push(item.name));
console.log('Items Ordered', itemsArr);
// THIS FUNCTION CALL TRIGGERS AN ERROR
// CANT ACCESS THIS INSTANCE FUNCTION
this.update(itemsArr);
},
error: error => console.error(error),
close: () => console.log('Done'),
});
}
// REACT COMPONENT
class App extends Component {
constructor(props){
super(props);
this.state = {
items:null,
};
}
// THOUGHT I COULD USE THIS TO UPDATE STATE
// TO TRIGGER A RE-RENDER
update(stuff){
this.setState({items: stuff});
}
render() {
// THINK SUBSCRIBE METHOD CALL SHOULDN'T BE HERE
// ON RE-RENDER, KEEPS SUBSCRIBING AND GETTING
// SAME MESSAGE REPEATEDLY
subscribe();
console.log('items down here', itemsArr);
return (
<div className="App">
<h1>Test</h1>
<p>Check the console..</p>
<p>{itemsArr}</p>
</div>
);
}
}
export default App;
Ideally, I'd like columns of the list of items to be displayed as messages come in but I'm currently getting an error - "TypeError: Cannot read property 'update' of undefined" because the subscribe function outside of the class doesn't have access to the update function inside the class.
Solution
Put subscribe method inside the App component so you can call it. You can call subscribe method in componentDidMount lifecycle to execute it (to get the items) after App component renders the first time. And then, update method will run this.setState() (to store your items in the state) causing App component to re-render. Because of this re-render, your this.state.items will contain something and it will be displayed in your paragraph.
class App extends Component {
constructor(props){
super(props);
this.state = {
items: [],
};
this.subscribe = this.subscribe.bind(this);
this.update = this.update.bind(this);
}
update(stuff){
this.setState({ items: stuff });
}
subscribe() {
// SETUP STUFF
Amplify.configure({
...
});
Amplify.addPluggable(new AWSIoTProvider({
...
}));
// ACTUAL SUBSCRIBE FUNCTION
Amplify.PubSub.subscribe('item/list').subscribe({
next: data => {
// LOG DATA
console.log('Message received', data);
// GET NAMES OF ITEMS
let itemsArr = [];
var lineItems = data.value.payload.lineItems;
lineItems.forEach(item => itemsArr.push(item.name));
console.log('Items Ordered' + [...itemsArr]);
this.update(itemsArr);
},
error: error => console.error(error),
close: () => console.log('Done'),
});
}
componentDidMount() {
this.subscribe();
}
render() {
console.log('items down here ' + [...this.state.items]);
return (
<div className="App">
<h1>Test</h1>
<p>Check the console..</p>
<p>{this.state.items !== undefined ? [...this.state.items] : "Still empty"}</p>
</div>
);
}
}
export default App;
Answered By - Eirin Gonzales
Answer Checked By - - Gilberto Lyons (ReactFix Admin)