I have React components that consumes external webservice to reach data for rendering.
I would like the data being loaded BEFORE the rendering because I want it to be indexed by search engines.
Here is my component :
class AboutPage extends React.Component {
async componentWillMount() {
let response = await EventWS.doSearch();
this.setState({
eventList : response.data
})
}
render() {
/* ... */
}
}
(I tried to use async/await because I thought it could help, but no).
When trying to load the page with server side rendering I got the warning :
Warning: setState(...): Can only update a mounting component. This usually means you called setState() outside componentWillMount() on the server. This is a no-op. Please check the code for the FluxContainer(AboutPage) component.
Indicating that the setState is done AFTER the componentWillMount has ended.
In my specific case, what is best way to acheving this ? Is there a easy way to do an ajax call synchronously ? Is it recommended to do so ?
Thank you.
EDIT :
I have found a library that allow to make synchronous call :
https://github.com/ForbesLindesay/sync-request
But it states that it is not well suited for production. So I'am a bit disapointed.
I dont have a lot of experience with Flux but it seems like you should do this in the componentDidMount method.
You can use Axios promise based get in componentDidMount to implement it, for an example you can refer Handling Events In React and below sample code:
constructor(props) {
super(props);
this.state = {eventList: []};
this.Axios = axios.create();
}
componentDidMount() {
let _this = this;
this.Axios.get('/your/rest/url')
.then(function (response) {
console.log(response);
_this.setState({eventList: response.data});
}).catch(function (error) {
console.log(error);
});
}
Or if you are already using Axios then make sure EventWS.doSearch() return Promise to you and call it in componentDidMount, as follows:
constructor(props) {
super(props);
this.state = {eventList: []};
}
componentDidMount() {
let _this = this;
EventWS.doSearch().then(function (response) {
console.log(response);
_this.setState({eventList: response.data});
}).catch(function (error) {
console.log(error);
});
}
Related
I am working on a React web app (in Typescript) in which I want to load a tensorflow.js model and then apply it each time after the component updates. I have been able to load and apply my model in a small demo app without React, but now encounter some problems with the async load function:
My idea was to load the model in the constructor of the component, but an async function cannot be used in a constructor. So I tried to make a sort of wrapper function:
export default class MyGreatComponent extends React.Component<{pixels}> {
model;
constructor(props) {
super(props);
this.model = this.loadmodel('http://localhost:3333/model.json');
}
async loadmodel(p: any) {
try {
const model = await tf.loadModel(p);
return model;
} catch (error) {
console.log("error while loading model: ");
console.log(error);
return error;
}
}
...
}
And then, when the component updates and I want to apply the model:
componentDidUpdate() {
... // get data etc
const t4d = tf.tensor4d(imgarray_n, [1, width, height, 3]);
var prediction : any = this.model.predict(t4d);
}
However, this gives:
TypeError: this.model.predict is not a function.
So I also tried a wrapper function that uses a promise (as I saw in this answer):
loadmodel(p: any){
tf.loadModel(p).then(model => {
return model;
});
}
But the promise seems to never resolve and gives me:
TypeError: Cannot read property 'predict' of undefined
I am not sure about what I am doing wrong, or what it is that I do not understand. I hope you guys can point out what's going wrong here. Thnx!
I figured out how silly my wrapper idea was and now load the model not in the constructor but in ComponentDidMount and that did the trick!
async componentDidMount() {
this.model = await tf.loadModel('http://localhost:3333/model.json');
}
I'm building a blog application that has an articles index page, and from there you can click on an article to see the article or edit the article.
If you're going from the index page to the edit page, it works just fine because I already have all the articles in state. But if I refresh after I've gone to the edit-article page, I no longer have all the articles in state.
This is a problem because I'm making an asynchronous recieveSingleArticle call in the componentDidMount of my edit-article page, then I setState so my form is prepopulated. There's a double render which causes an "Uncaught TypeError: Cannot read property 'title' of undefined" error, presumably during the first render before the article has been received into state.
class ArticleEdit extends React.Component {
constructor(props) {
super(props);
this.state = {title: "", body: "", imageFile: ""};
this.handleChange = this.handleChange.bind(this);
this.handlePublish = this.handlePublish.bind(this);
this.handleFile = this.handleFile.bind(this);
this.handleCancel = this.handleCancel.bind(this);
}
componentDidMount() {
const { article, requestSingleArticle } = this.props;
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}
...
I tried wrapping my async calls inside of an "if (this.props.article)" but that didn't work. Is there a best way of dealing with this type of problem? Any advice greatly appreciated!
UPDATE:
Another solution that works is to have a componentDidUpdate in addition to componentDidMount. check in componentDidMount if this.props.article exists and if so, setState. And in componentDidUpdate, wrap the setState in the following conditional:
if (!prevProps.article && this.props.article)
Just check if the article is present in the props before calling async action
componentDidMount() {
const { article, requestSingleArticle } = this.props;
if (!(article && requestSingleArticle)) return; // this line
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}
Since you are not getting any render from this method , it means that the props are not yet obtained in the life cycle method componnetDidMount. So instead you can use componentWillReceiveProps like this
componentWillReceiveProps(nextProp) {
// this line here will check the article props' status so that
// we will not use setState each time we get a prop
if (this.props.article === nextProp.article) return;
// rest is just the same code from above
const { article, requestSingleArticle } = nextProp;
if (!(article && requestSingleArticle)) return; // this line
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}
I am working in a small application for a class I am taking and I have an issue when I am using the fetch API
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
map: "",
markers: [],
Data: []
};
}
componentDidMount() {
fetch(
`https://api.foursquare.com/v2/venues/explore?near=ashkelon&v=20180729&client_id=MVLZGLPIAITJITM0OOFYER3C2ZRT5ERGGEWCC0T1YWV3HFZA&client_secret=1TBLTY0TSM1T320FEO3BJBGTMYVQPCMBOGO5GEBC0ZB1E5LK`
)
.then(function(response) {
return response.json();
})
.then(
function(data) {
this.setState({ Data: data });
}.bind(this)
)
.catch(function(e) {
console.log("There is an issue with getting the information", e);
});
}
}
window.initMap = this.initMap;
loadJS("https://maps.googleapis.com/maps/api/js?key=AIzaSyDySeqpuOjQlckWJUMlbSW_w5CydOVTWJI&callback=initMap");
UPDATE :
this will not provide with an error and the state is set, but what now happens is that my state is empty when i log the state in the initMap method.
At this point i see that the state is set for "that".
But if its set for "that" how can i use "this" state in the rest of my application i need this information to create markers on the google maps API
thanks in advance.
The problem is that this is undefined in your anonymous function. By assigning const that = this, you make the context from componentDidMount() available in all of the anonymous functions. Another solution is to bind() all functions with the correct context. For example
...
.then((function(data) {
this.setState({Data: data.response.groups[0]})
console.log(this.state.Data);
}).bind(this))
...
Now you can remove the declaration for that.
If you don't really care about IE11 support (and does not use Babel) please consider using arrow functions (it's syntax sugar for functions that have the same this as the this around them)
Note that string literals like you used, have similar compatibility table as arrow functions so you lose nothing and gain much cleaner code!
The code with arrow functions looks like this:
componentDidMount() {
/* ... */
fetch(`URL`)
.then(response => response.json())
.then(data => this.setState({Data: data.response.groups[0]}))
.catch(e => console.log('There is an issue with getting the information' , e))
/* ... */
}
I am trying to accept incoming calls with the twilio.Js Library. (https://www.twilio.com/docs/api/client)
I am listening for the incoming calls like this:
componentDidMount() {
Twilio.Device.incoming((conn) => {
console.log('Incoming connection from ' + conn.parameters.From);
});
}
I know that I can just call conn.accept(); in the Twilio.Device.incoming function, however for my component to work, I would need to be able to accept the call outside of the componentDidMount() function.
So preferably I would have a function like this:
acceptCall() {
// Do something to accept call
}
Is there any expert here, that could quickly help me?
Ok I figured it out myself.
What I need to do is the following:
constructor(props){
super(props);
this.state = {
inboundConn: null
};
}
// Initialize after component creation
componentDidMount() {
Twilio.Device.incoming((conn) => {
this.setState({
inboundConn: conn
});
});
}
acceptCall() {
this.state.inboundConn.accept();
}
After reading official react.js documentation I understand how it should work in a good way, like
I have list of items in initial component state
adding new item through setState will update state and trigger update of UI
What should I do if I use external object as model like some global array which should be available for some not react.js parts of code OR could be modified with web sockets somewhere in future? Is calling ReactDOM.render after each action a good way? AFAIK it should work ok from performance point of view.
You still use setState:
let React = require('React');
let externalThing = require('tools/vendor/whoever/external-lib');
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
}
getInitialState() {
// This assumes your external thing is written by someone who was
// smart enough to not allow direct manipulation (because JS has
// no way to monitor primitives for changes), and made sure
// to offer API functions that allow for event handling etc.
externalThing.registerChangeListener(() => this.updateBasedOnChanges(externalThing));
return { data: externalThing.data }
}
updateBasedOnChanges(externalThing) {
// note that setState does NOT automatically trigger render(),
// because React is smarter than that. It will only trigger
// render() if it sees that this new 'data' is different
// (either by being a different thing entirely, or having
// different content)
this.setState({
data: externalThing.data
});
}
render() {
// ...
}
}
If the external thing you're using is terribly written and you have to manipulate its data directly, your first step is to write an API for it so you don't directly manipulate that data.
let externalData = require('externaldata') // example: this is a shared array
let ExternalDataAPI = new ExternalDataAPI(externalData);
...
And then you make sure that API has all the update and event hooks:
class ExternalDataAPI {
constructor(data) {
this.data = data;
this.listeners = [];
}
addListener(fn) {
this.listeners.push(fn);
}
update(...) {
// do something with data
this.listeners.forEach(fn => fn());
}
...
}
Alternatively, there are frameworks that already do this for you (flux, etc) but they also somewhat dictate how many more things "should be done" so that might be overkill for your need.
Since your question is about organizing your code in a manageable way, I would first of all suggest pairing ReactJS with a Flux-type framework, like Redux or Relay.
If you want to skip that for now, then you can organize your project using some react components at the top of the hierarchy for storing and retrieving data. For example, in such a component, in its componentWillMount method, you can start a setTimeout that periodically checks your global array and calls setState when appropriate. The render method should then contain child components that receive this state as props.
Below is an example. Obviously, the timers can be replaced by whichever method you use to subscribe to your data changes.
// your global object
var globalState = {name: "Sherlock Holmes"}
function onData(callback) {
setInterval(function(){
callback(globalState)
}, 1500)
}
var Child = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
var Root = React.createClass({
getInitialState: function() {
return {}
},
componentWillMount: function() {
var that = this;
this.props.onData(function(data){
that.setState({external: data})
})
},
render: function() {
if (this.state.external)
return <Child name={this.state.external.name}/>
else
return <div>loading...</div>;
}
});
ReactDOM
.render(<Root onData={onData} />, document.getElementById('container'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>