JS Promise is stuck to my code - javascript

Its been 48 hours and still couldn't figured it out.
I'm dealing with a promise using a Clarifai api for react native. Here is my code:
function updater(){
return ClarifaiApp.models.predict("coiner app", "https://www.coinsonline.com/wp-content/uploads/2015/11/Half-Dimes-and-Dimes.jpg").then(
function(response){
par1 = response['outputs'][0]['data']['concepts'][0];
return (par1.name);
});
}
export default class CameraScreen extends React.Component {
render(){
return(
function updater2(){
updater().then(function(getAlll){
<Text> {getAlll} </Text> //error: trying to add an object - invalid
});
);
}
}
Now, the thing is that I want to get the value of 'par1'. The problem is that whenever I try to get the value of getAlll in console which is par1, I can get the string I wanted but when I try to add it as {variable} inside text, it gives an error that I'm trying to an object inside .

I think the problem is you're trying to render a Promise inside render. I think the message may be a little misleading. Here is a codepen that explains one way to handle renders that depend on async data.
https://codepen.io/sscaff1/pen/bMVvgN
export default class CameraScreen extends React.Component {
state = { getAll: null }
componentDidMount() {
updater().then(getAll => this.setState({ getAll }));
}
render(){
const { getAll } = this.state;
return getAll ? <Text>{getAll}</Text> : null // you can also put a loader here
}
}

Related

setState with arrow function does not work

Could you please have a look on the following code. I need to get some value from another class. This works asynchronous, so I provided a function handleGameDeserialization.
The function gets the right value (as I tested with the alert), however the setState function has no impact. Could that be a "this-context" issue?
export default class GameplayScreen extends React.Component {
constructor(props) {
super(props);
this.fbGame = new FBGame();
global.currentScreenIndex = 'Gameplay';
this.state = {
currentGame: 'N/A'
}
// this.handleGameDeserialization = this.handleGameDeserialization.bind(this);
if (this.props.route.params != null) {
this.gameKey = this.props.route.params.gameKey;
this.game = this.fbGame.deserializeGame(this.gameKey, this.handleGameDeserialization);
}
}
handleGameDeserialization = (game) => {
// alert('yeah'+game); // here comes the expected output
this.setState({
currentGame: game
});
}
render() {
return (
<View>
<Text>{this.state.currentGame}</Text>
</View>
/*<Board game={this.state.game}/>*/
)
}
}
I call that function when the component GameplayScreen is navigated to. As you can see above, there is a class FBGame, which does the deserialization (read the game from firebase database)
export default class FBGame {
...
deserializeGame(key, handleGameDeserialization) {
var gameRef = firebase.database().ref("games/"+key).child("serialized");
gameRef.on("value", snapshot => {
//console.log('deserialized: ' + );
handleGameDeserialization(snapshot.val().serialized);
});
}
...
}
edit:
When I use componentDidMount like below, it works fine. But this seems to be an anti-pattern. I still don't understand, why it doesn't work, when callded in the constructor and how I am supposed to solve this.
componentDidMount() {
this.game = this.fbGame.deserializeGame(this.gameKey, this.handleGameDeserialization);
}
For things like subscriptions that will update the state and other side-effects, you should put the logic out in componentDidMount() which will fire immediately after the component is mounted and won’t give you any trouble if you update the state inside of it.
You can't but things that call this.setState in the constructor.

Component not rerendering on state change?

Whenever setState() is called, the component doesn't seem to rerender. As you can see by my comments, the state does in fact change and render seems to be called again, but if I don't add that if statement and simply add a paragraph tag that displays the data it will give me an error. I'm sure I'm missing something simple, but any help is appreciated.
import React from "react";
import axios from "axios";
import { constants } from "../constants/constants";
const { baseURL, apiKey, userName } = constants;
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: []
};
}
componentDidMount() {
let getUserInfo = axios.create({
baseURL,
url: `?
method=user.getinfo&user=${userName}&api_key=${apiKey}&format=json`
});
getUserInfo().then(response => {
let data = response.data;
console.log(data.user.playcount); //logs second, displays correct
this.setState(state => ({
user: data
}));
});
}
render() {
console.log(this.state); //logs first and third, doesn't work on first but does on third
let toReturn;
if (this.state.user.length > 0) {
toReturn = <p>{this.state.user.user.playcount}</p>;
} else {
toReturn = <p>didn't work</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;
React LifeCycle function sequence is Constructor and then it calls render method.
In constructor method it initialises the state which is currently empty user array.
Now it calls render() method as this.state.user is an empty array, referencing something out of it gives an error
this.state.user.user.playcount
this will generate an error if you dont have if condition.
After the first render it will call componentDidMount, now you fetch something update state. As setState occurred, render will be called again Now you have something in this.state.user then displaying will happen.
this.state.user.length > 0 is true
Look at this: https://reactjs.org/docs/react-component.html and https://reactjs.org/docs/conditional-rendering.html
You can right in single tag using conditional render like this
<p>{this.state.user.length ? this.state.user.user.playcount : 'loading'}
Hope this helps.
I think your problem might have something to do with the changing shape of the user value. You initialise the value to an empty array, but then—after the fetch is done—you assume it's an object (by using user.user).
Maybe you could simplify the code a bit to look more like the one below?
/* imports */
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null // Make it explicit there's no value at the beginning.
};
}
componentDidMount() {
let getUserInfo = axios.create(/* ... */);
getUserInfo().then(response => {
let data = response.data;
this.setState({ // No need to for a setter function as you dno't rely on the previous state's value.
user: data.user // Assign the user object as the new value.
});
});
}
render() {
let toReturn;
// Since it's now a `null`, you can use a simple existence check.
if (this.state.user) {
// User is now an object, so you can safely refer to its properties.
toReturn = <p>{this.state.user.playcount}</p>;
} else {
toReturn = <p>No data yet.</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;

Fetch data only once per React component

I have a simple component that fetches data and only then displays it:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
stuff: null
};
}
componentDidMount() {
// load stuff
fetch( { path: '/load/stuff' } ).then( stuff => {
this.setState({
loaded: true,
stuff: stuff
});
} );
}
render() {
if ( !this.state.loaded ) {
// not loaded yet
return false;
}
// display component based on loaded stuff
return (
<SomeControl>
{ this.state.stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
);
}
}
Each instance of MyComponent loads the same data from the same URL and I need to somehow store it to avoid duplicate requests to the server.
For example, if I have 10 MyComponent on page - there should be just one request (1 fetch).
My question is what's the correct way to store such data? Should I use static variable? Or I need to use two different components?
Thanks for advice!
For people trying to figure it out using functional component.
If you only want to fetch the data on mount then you can add an empty array as attribute to useEffect
So it would be :
useEffect( () => { yourFetch and set }, []) //Empty array for deps.
You should rather consider using state management library like redux, where you can store all the application state and the components who need data can subscribe to. You can call fetch just one time maybe in the root component of the app and all 10 instances of your component can subscribe to state.
If you want to avoid using redux or some kind of state management library, you can import a file which does the fetching for you. Something along these lines. Essentially the cache is stored within the fetcher.js file. When you import the file, it's not actually imported as separate code every time, so the cache variable is consistent between imports. On the first request, the cache is set to the Promise; on followup requests the Promise is just returned.
// fetcher.js
let cache = null;
export default function makeRequest() {
if (!cache) {
cache = fetch({
path: '/load/stuff'
});
}
return cache;
}
// index.js
import fetcher from './fetcher.js';
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
stuff: null
};
}
componentDidMount() {
// load stuff
fetcher().then( stuff => {
this.setState({
loaded: true,
stuff: stuff
});
} );
}
render() {
if ( !this.state.loaded ) {
// not loaded yet
return false;
}
// display component based on loaded stuff
return (
<SomeControl>
{ this.state.stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
);
}
}
You can use something like the following code to join active requests into one promise:
const f = (cache) => (o) => {
const cached = cache.get(o.path);
if (cached) {
return cached;
}
const p = fetch(o.path).then((result) => {
cache.delete(o.path);
return result;
});
cache.set(o.path, p);
return p;
};
export default f(new Map());//use Map as caching
If you want to simulate the single fetch call with using react only. Then You can use Provider Consumer API from react context API. There you can make only one api call in provider and can use the data in your components.
const YourContext = React.createContext({});//instead of blacnk object you can have array also depending on your data type of response
const { Provider, Consumer } = YourContext
class ProviderComponent extends React.Component {
componentDidMount() {
//make your api call here and and set the value in state
fetch("your/url").then((res) => {
this.setState({
value: res,
})
})
}
render() {
<Provider value={this.state.value}>
{this.props.children}
</Provider>
}
}
export {
Provider,
Consumer,
}
At some top level you can wrap your Page component inside Provider. Like this
<Provider>
<YourParentComponent />
</Provider>
In your components where you want to use your data. You can something like this kind of setup
import { Consumer } from "path to the file having definition of provider and consumer"
<Consumer>
{stuff => <SomeControl>
{ stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
}
</Consumer>
The more convenient way is to use some kind of state manager like redux or mobx. You can explore those options also. You can read about Contexts here
link to context react website
Note: This is psuedo code. for exact implementation , refer the link
mentioned above
If your use case suggests that you may have 10 of these components on the page, then I think your second option is the answer - two components. One component for fetching data and rendering children based on the data, and the second component to receive data and render it.
This is the basis for “smart” and “dumb” components. Smart components know how to fetch data and perform operations with those data, while dumb components simply render data given to them. It seems to me that the component you’ve specified above is too smart for its own good.

Error during conversion of a string to JSON object in ReactJS application

As part of my ongoing effort to learn ReactJS I'm developing a simple page that will render a list of trends as follows:
On clicking the button "Get Trends" the list of trends is retrieved from a back-end server over websockets and displayed. It works as expected. Below is the corresponding source code:
import React, { Component } from 'react';
import './App.css';
class TrendRow extends Component {
render() {
return (
<tr>
<td>{this.props.onerow}</td>
</tr>
);
}
}
class TrendTable extends Component {
render() {
var rows = [];
for (var i=1; i<3; i++) {
rows.push(<TrendRow key={i} onerow={this.props.trends[i]}/>);
}
return (
<table>
<thead>
<tr>
<th>List of Trends</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}
class App extends Component {
constructor() {
super();
this.state = {
ws: new WebSocket('ws://localhost:8025/websockets/TwitterWSService'),
getTrendsqueryString: 'GETTRENDS',
listoftrends: ['{"trend":"#Default","id":"0"}']
};
this.handleOnClick = this.handleOnClick.bind(this);
}
handleOnClick = (event) => {
this.state.ws.send(this.state.getTrendsqueryString);
this.state.ws.onmessage = (event) => {
this.setState(prevState => ({listoftrends: prevState.listoftrends.concat(event.data)}));
}
}
render() {
return (
<div>
<button onClick={this.handleOnClick}>Get Trends</button>
<TrendTable trends={this.state.listoftrends} />
</div>
);
}
}
export default App;
Now when I try to convert the JSON string displayed into a JSON object using "JSON.parse" I get different types of errors based on where I parse it.
If I parse as shown below,
class TrendRow extends Component {
render() {
var jsonobject = JSON.parse(this.props.onerow);
return (
<tr>
<td>{jsonobject}</td>
</tr>
);
}
}
I get the below error:
"SyntaxError: Unexpected token u in JSON at position 0
...
var jsonobject = JSON.parse(this.props.onerow);
..."
A quick google search for the error message returned the following discussion but it's not very clear how to apply the solution to my use-case:
uncaught syntaxerror unexpected token U JSON
I understand that the error is due to value 'this.props.onerow' being undefined during the initial render and JSON.parse() trying to parse this object. Even initializing the "listoftrends" object with a default string does not solve the error.
If on the other hand I JSON.parse() as shown below,
handleOnClick = (event) => {
this.state.ws.send(this.state.getTrendsqueryString);
this.state.ws.onmessage = (event) => {
var jsonobject = JSON.parse(event.data);
this.setState(prevState => ({listoftrends: prevState.listoftrends.concat(jsonobject)}));
}
}
I get the error message:
Objects are not valid as a React child...
Google search leads me down another rabbit hole! Could someone please provide any other solutions for me to try?
Looking at your code (and your note that you are just learning) let me add few comments how you can possibly improve it.
Using an arrow function with a class property ensures that the method is always invoked with the component as the value for this, meaning that the manual binding here is redundant. So you can get rid of the line below.
this.handleOnClick = this.handleOnClick.bind(this);
Also get rid of the ugly for loop inside the TrendTable and replace it with map function.
class TrendTable extends Component {
render() {
return (
<table>
<tbody>{this.props.trends.map(trend =>
<TrendRow key={i} onerow={trend}/>)}
</tbody>
</table>
);
}
}
You can read more about what alternatives you have if you are going to avoid regular for loop.
For prepopulating your trends array the better approach is to use componentDidMount react lifecycle method. To dive deeper check this article.
And I think it's better to create update button (instead of get trends) which should completely rewrite your trends list with fresh portion from backend side in case you need to (but sure this point is up to you).
Now you don't have to use constructor method inside component if you just only need to initialize default state, so you can use
state = {....}; just inside your component w/o usage of constructor. But make sure that you are using stage-2 preset.
So taking into account the comments above, here is the App component:
class App extends Component {
state = {
ws: new WebSocket('ws://localhost:8025/websockets/TwitterWSService'),
getTrendsqueryString: 'GETTRENDS',
listoftrends: [] // you can add default trend record if you need it
};
};
componentDidMount() {
this.fetchTrends();
}
fetchTrends = (completeUpdate = false) => {
this.state.ws.send(this.state.getTrendsqueryString);
this.state.ws.onmessage = (event) => {
this.setState(prevState => (
{ listoftrends: !completeUpdate ? prevState.listoftrends.concat(event.data) : event.data }
));
}
};
updateTrends = () => {
this.fetchTrends(true); //in that case you'd like to completely update the list
}
}
render() {
return (
<div>
<button onClick={this.updateTrends}>Update trends</button>
<TrendTable trends={this.state.listoftrends} />
</div>
);
}
}
And regarding your question itself. As many of other guys already mentioned, yes it's not possible to use an object inside of JSX, so you have to transform it first (for instance to array).
E.g.
var array = Object.values(jsonobject).map(value => ...);
// and then in JSX
<div>{array}</div>
Hopefully it all makes sense.
jsonobject is an object.
React don't know how to render it.
You can convert it to an Array and then render it it.
var jsonArray = Object.keys(jsonobject).map(function(k) {
return jsonobject[k];
});
JSX :
<td>{jsonobject}</td>
A simple solution to make your first approach work would be to circumvent the Error by not attempting to parse JSON during the first render:
var jsonobject = this.props.onerow ? JSON.parse(this.props.onerow) : {};
As others have said though, you cannot render objects directly. For testing purposes, replace <td>{jsonobject}</td> with <td>{jsonobject.id}</td>.
If you log the initial row, AND results of your JSON parsing, e.g.
console.log(this.props.onerow);
console.log(JSON.parse(this.props.onerow));
What is the console's output? Your first error is probably because your 'JSON' response from the backend isn't properly structured, or because initially if the value is something like an empty string then you can't use JSON.parse on it as it will cause an error.
Your second error, Objects are not valid as a React child, is what happens when you try to render an object inside a JSX element, for example:
render(){
let someObj = {potato:1, hello:'hi there'};
return(<div>Here's the object: {someObj}</div>); <-- ERROR! can't put an object here
}
So I'm guessing at some point you're trying to render that JSON object inside a JSX element.
I have provided the updated code after incorporating the suggestions provided by users. This may not be perfect but accomplishes what I wanted.
import React, { Component } from 'react';
import './App.css';
class TrendRow extends Component {
render() {
var jsonArray = Object.keys(this.props.onerow).map((k) => {
return this.props.onerow[k];
});
return (
<tr>
<td>{jsonArray[0]}</td>
</tr>
);
}
}
class TrendTable extends Component {
render() {
var rows = this.props.trends.map((trend, index) => {
return (index<2) ? <TrendRow key={index} onerow={trend} /> : [];
}
);
return (
<table>
<thead>
<tr>
<th>List of Trends</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}
class App extends Component {
constructor() {
super();
this.state = {
ws: {},
getTrendsqueryString: 'GET',
listoftrends: []
};
}
componentDidMount = () => {
this.setState({ws: new WebSocket('ws://localhost:8025/websockets/TwitterWSService')});
}
handleOnClick = (event) => {
this.state.ws.send(this.state.getTrendsqueryString);
this.state.ws.onmessage = (event) => {
var jsonobject = JSON.parse(event.data);
this.setState(prevState => ({listoftrends: prevState.listoftrends.concat(jsonobject)}));
}
this.state.ws.onclose = (event) => {
this.setState({ws: new WebSocket('ws://localhost:8025/websockets/TwitterWSService')});
}
}
render() {
return (
<div>
<button onClick={this.handleOnClick}>Get Trends</button>
<TrendTable trends={this.state.listoftrends} />
</div>
);
}
}
export default App;

React-Redux - passing down props before async data arrives

I receive the "Uncaught TypeError: Cannot read property 'data' of null" error in the console when I'm loading up my page.
In the constructor I call an external api for data with axios using redux-promise, then I pass props down to a stateless component (TranslationDetailBox).
The data arrives, and the child component will receive the props shortly (page looks ok), but first the error message appears.
this.props.translation is empty before the api call, and rendering happens before the state is updated with the external data. I believe this is the issue, but I have no clue on how to solve it.
class TranslationDetail extends Component {
constructor(props){
super(props);
this.props.fetchTrans(this.props.params.id);
}
render() {
return (
<div className="container">
<TranslationDetailBox text={this.props.translation.data.tech}/>
<TranslationDetailBox text={this.props.translation.data.en}/>
</div>
);
}
}
function mapState(state) {
const { translation } = state;
return { translation };
}
...
I find this to be a good use case for conditional rendering.
Your render could check to see whether the data has loaded or not and if not, render something indicating it is still loading.
A simple implementation could be:
render() {
return (
!this.props.data ?
<div>Loading...</div>
:
<div>{this.props.data}</div>
)
}
You could set defaultProps:
TranslationDetail.defaultProps = {
translation: {
data: {}
}
};

Categories