I have GUser class, in my project to store data from GitHub-api and process them :
import axios from "axios";
export default class GUser {
constructor(userName) {
this.userName=userName;
this.getUserData(this,userName);
}
getUserData(that, userName) {
if(that.data===undefined)
{
axios.get('https://api.github.com/users/'.concat(userName))
.then(function (response) {
that.data = response.data;
console.log(that);
return that.data;
})
.catch(function (error) {
console.log(userName+ " got error " +error);
});
}
else{return that.data;}
}
}
I'm trying to sync pages, so in my TestRender I wrote :
import React from 'react';
import GUser from "../model/GUser";
class TestRender extends React.Component{
userName='maifeeulasad';
user=new GUser(this.userName);
componentWillMount() {
try {
console.log(this.user.getUserData());
}
catch (e) {console.log(e);}
}
componentDidMount() {
try {
console.log(this.user.getUserData());
}
catch (e) {console.log(e);}
}
render() {
return(
<div>
</div>
);
}
}
export default TestRender;
My target is to console log the data, as soon as it gets received.
How can I do this ?
Previously I used componentWillMount only, when I loaded data from the same script. It's currently giving me TypeError: Cannot read property 'data' of undefined, maybe because of I haven't specified data as state in GUser, but it gets created through time right ?
Looks like you are using object's attribute to store the state's component, that isn't good, because React will not to re-render the component when these values are updated.Every instance of TestRender should have the same value of userName and user as attribute? These values are always the same? If not, use useState for that.
Also, componentDidMount and componentWillMount will be called only when the component is being mounted, and at this moment you didn't finished the fetch, so you don't have yet the value to print. It's an async problem.
To handle with async challenges, I recommend to use useEffect.
Related
All articles I have read on promises show examples with console.log - I am using AWS Athena and want to display the result on the webpage in my React export. The react export does not allow the use of .then. So I need to resolve the promise to an external variable.
client is a aws athena client which returns a promise I need to resolve.
async function getResult(){
try {
return await client.send(command);
} catch (error) {
return error.message;
}
}
export default getResult()
I want to display the result in App.js
render()
{
return (
{ athena }
)
It displays in the console but not on the webpage as the page is loaded before the variable is resolved.
More complete example of App.js
import athena from './athena';
class App extends Component {
render()
{
let athena_result = athena.then(function(result) {
console.log(result)
}
)
return ( athena_result )
Causes Error
Error: Objects are not valid as a React child (found: [object Promise])
The render method of all React components is to be considered a pure, synchronous function. In other words, there should be no side effects, and no asynchronous logic. The error Error: Objects are not valid as a React child (found: [object Promise]) is the component attempting to render the Promise object.
Use the React component lifecycle for issuing side-effects. componentDidMount for any effects when the component mounts.
class App extends Component {
state = {
athena: null,
}
componentDidMount() {
athena.then(result => this.setState({ athena: result }));
}
render() {
const { athena } = this.state;
return athena;
}
}
If you needed to issue side-effects later after the component is mounted, then componentDidUpdate is the lifecycle method to use.
Class components are still valid and there's no plan to remove them any time soon, but function components are really the way going forward. Here's an example function component version of the code above.
const App = () => {
const [athenaVal, setAthenaVAl] = React.useState(null);
React.useEffect(() => {
athena.then(result => setAthenaVAl(result));
}, []); // <-- empty dependency array -> on mount/initial render only
return athenaVal;
}
The code is a little simpler. You can read more about React hooks if you like.
You can use a state, and just set the state to the response's value when it's done:
const Component = () => {
const [athena, setAthena] = useState(""); // Create a state with an empty string as initial value
// Send a request and on success, set the state to the response's body, and on fall set the state to the error message
useEffect(() => client.send(command).then((response) => setAthena(response.data)).catch((error) => setAthena(error.message)), []);
return <>{athena}</>;
};
I am using react-localize-redux for my multilingual application and MySQL to fetch data. One of my actions needs locale data to pass it to backend as an argument so that backend responds with proper data. But by the time locale is available, action gets called and application crashes, how can I resolve the issue?
Here is code:
import React, { Component } from 'react'
import RestaurantCard from './RestaurantCard';
import {Row} from 'react-bootstrap';
import { connect } from 'react-redux';
import {getAllRestaurants} from "../actions/restaurantActions";
import { withLocalize } from 'react-localize-redux';
class RestaurantCardColumns extends Component {
constructor(props){
super(props);
}
componentDidMount(){
this.props.getAllRestaurants(this.props.activeLanguage);
}
render() {
if(this.props.loading || this.props.restaurants === null){
return <p>Loading...</p>
} else {
return (
<Row>
<RestaurantCard data = {this.props.restaurants[0]}/>
<RestaurantCard data = {this.props.restaurants[1]}/>
<RestaurantCard data = {this.props.restaurants[2]}/>
<RestaurantCard data = {this.props.restaurants[3]}/>
</Row>)
}
}
}
const mapStateToProps = (state) =>{
return {
auth: state.auth,
errors: state.errors,
restaurants: state.restaurData.restaurants,
loading: state.restaurData.loading
}
}
export default connect(mapStateToProps, {getAllRestaurants})(withLocalize(RestaurantCardColumns));
My problem is in this particular line:
this.props.getAllRestaurants(this.props.activeLanguage);
When I debug I can see that activeLanguage is available in render() lifecycle.
How can I await for that property before calling getAllRestaurants
Check for availability of this.props.activeLanguage before fetching data. Trigger fetching data once activeLanguage is available. And finally ensure that fetching happening only once (if you need)
class RestaurantCardColumns extends Component {
constructor(props){
super(props);
this.didFetch = false; // store outside of state to not trigger rendering
}
componentDidMount(){
this.fetchAllRestaurants();
}
componentDidUpdate(prevProps) {
if (prevProps.activeLanguage !== this.props.activeLanguage) {
this.fetchAllRestaurants();
}
}
fetchAllRestaurants() {
if (!!this.props.activeLanguage && !this.didFetch) {
this.props.getAllRestaurants(this.props.activeLanguage);
this.didFetch = true;
}
}
Be aware that this approach is entirely relied on the component's existence, i.e. if the component is not in virtual DOM, the API call will not happen. You should consider trigger the call using a redux's middleware, like redux-thunk or redux-saga as other people in here suggest.
Use a store enhancer middleware like Thunk. You seem to be making an async request,and store enhancers enable you to make async calls and retrieve data from backend. Middlewares like Thunk stops default action dispatch, perform async requests ad call the dispatch to pass the action along with the updated payload to the reducer. Using proper async - await in the componentDidMount will handle this as well, but store enhancers actually handle that for you.
Here's an example:
async componentDidMount() {
await this.props.getAllRestaurants(this.props.activeLanguage);
}
ComponentDidMount should be an async function, and you should await for
getAllRestaurants to complete.
In addition to that, you should have a local state variable (e.g. IsLoading), that indicates that data is not ready. After the 'await
getAllRestaurants' statement, you set isLoading to falase.
Render will check this local state in order to display a spinner or the data itself, or an error message, if getAllRestaurants fails (in addition to checking isLoading, you should check the redux store, where you will store not only the data, but also a variable indicating whether getAllRestaurants succeeded or failed).
I'm using Meteor, React, react-meteor-data, and React Router. I am linking to a page and adding an /:id, which I am then trying to use to query the database and build out the component with the returned data.
The problem is the initial query returns an empty array. I can then see in my render method and componentWillReceiveProps that it returns the data I expect a moment later. The problem is that my component does not re-render. I am using withTracker because I want the component to update and re-render every time the database changes in the targeted Collection.
Here is the React code on the client-side:
export default withTracker((props) => {
const handle = Meteor.subscribe('game', props.match.params.id);
return {
listLoading: !handle.ready(),
game: ActiveGames.find({ _id: props.match.params.id}).fetch(),
};
})(Game);
And here is the publication in 'imports/api/activeGames.js':
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const ActiveGames = new Mongo.Collection('activeGames');
if (Meteor.isServer) {
Meteor.publish('game', function (id) {
check(id, String);
return ActiveGames.find({ _id: id });
});
Meteor.publish('activeGames', function activeGamesPublication() {
return ActiveGames.find();
});
}
Here is a screenshot of the output I'm getting, with console logs to track the pertinent life cycle methods.
I believe it's a simple matter of doing something with the listLoading prop.
For instance, your Game component could be:
class Game extends Component {
constructor(props){
super(props);
}
renderGames = () => {
return this.props.games.map(g => {
return (
<li>{g.title}</li>
)
})
}
render() {
return (
<ul>
{this.props.listLoading ? <span>Loading...</span> : this.renderGames()}
</ul>
);
}
}
export default withTracker((props) => {
const handle = Meteor.subscribe('game', props.match.params.id);
return {
listLoading: !handle.ready(),
game: ActiveGames.find({ _id: props.match.params.id}).fetch(),
};
})(Game);
This is how I handle the time between subscribing and receiving data.
Hope it helps, though I'm sure you found a solution by now ;)
I am actually at a loss to figure out why this isn't working as I have spent a lot more hours than usual on how to get it fixed. The problem is I am using axios to make a REST call to get the data to be rendered. Inside the block to handle the response, even though I am able to retrieve the data the 'this' object somehow fails to refer to the correct object and I get an error. I dono why this is happening but any help on it will be highly appreciated.
Posting my code snippet below. I have tried saving the context of this outside the axios call scope and used the new variable but that too does not help. Here is the error I get in my console
TypeError: _this2.setState is not a function
import React, {Component} from 'react';
import axios from 'axios';
import './RouteList.css';
class RouteList extends Component{
constructor(){
super();
this.setState = {
allRoutes: {},
selectedRoutes: {}
};
}
componentDidMount(){
const that = this;
//Retrieve the SF-Muni route list
axios.get('http://webservices.nextbus.com/service/publicJSONFeed?command=routeList&a=sf-muni')
.then(response => {
console.log(response);
that.setState({ allRoutes: response.data.routes });
})
.catch((error) => {
console.log(error);
});
}
render(){
return (
<div className="transit-routes">
{/*TODO-Code to render data.*/}
</div>
);
}
}
export default RouteList;`
The problem is that you are overwriting the setState method in the constructor, try to set the initial state like this:
this.state = {
allRoutes: {},
selectedRoutes: {}
};
Also, when using an arrow function, there's no need to save the parent scope, the function will run under the same scope as the outer function.
From the docs, I get that I need to use a reactive data container around my component to retrieve the currently logged in user (if one exists), but how do I get the data from that container?
import { Meteor } from 'meteor/meteor';
import React, { Component } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
export default FooContainer = createContainer(() => {
return {
user: Meteor.user()
};
}, class FooComponent extends Component {
render() {
return (<div>{ /* this.data.user ??? */ }</div>);
}
});
How do I get the data returned from the container function inside my render method?
I see you returned a user key in your container, therefore your data will be accessible via your this.props.user prop in your component.
Just make sure you add something like this to your container:
export default FooContainer = createContainer(() => {
const subscription = Meteor.subscribe("userData");
subscription.ready() ? Session.set("dataReady", true) : Session.set("dataReady", false);
return {
user: Meteor.user()
};
}
and in your render method:
render() {
if(Session.get("dataReady")){
return (<div>{ /* this.data.user ??? */ }</div>);
}
}
Trust me, it will save you a lot of headache and a lot of errors in the future. This will make sure your data is 100% ready before it's called and rendered in your component.
Oh, also assuming you have autopublish removed, publish the particular user's data in order to subscribe to it like I showed above:
Meteor.publish("userData", function(){
return Meteor.users.find({_id: this.userId});
});
Just a heads up.