Reactjs: Loading view based on response - javascript

Looking for a way for React to process some json, and load views based on the response.
For example:
1- React has a form, response goes out to external API
2- API processes the input, returns a success code unless there was validation issues, and send a response back to the React app
3- React gets the json response, loads a "Success" view, or reloads the form and outputs the erros
Is there a simple way for React to handle this? Thanks in advance!

Very simple...
Basically, you need to track when you initiate request (sending data) and when request is completed (receiving response).
Based on data returned, you decide what to render...
Take a look at this example (working fiddle)
// In this example, we are using JSONPlaceholer service do real
// request and receive response;
const root = 'http://jsonplaceholder.typicode.com';
const Success = () => (<div>Success!</div>);
const Error = () => (<div>Error! Fix your data!</div>);
const Form = React.createClass({
getInitialState() {
return {
processing: false,
result: undefined,
};
},
submit(event) {
this.setState({ processing: true });
event.preventDefault();
fetch(`${root}/posts`, {
method: 'POST',
data: {
// your data here...
}
})
.then(response => {
// We simulate succesful/failed response here.
// In real world you would do something like this..
// const result = response.ok ? 'success' : 'error';
const processing = false;
const result = Math.random() > 0.5 ? 'success' : 'error';
this.setState({ result, processing });
});
},
render() {
const { result, processing } = this.state;
if (result === 'success')
return <Success />;
return (
<form>
Form content here<br/>
<button onClick={this.submit}>
{ processing ? 'Sending data...' : 'Submit' }
</button>
{ result === 'error' && <Error /> }
</form>
);
},
});
render(<Form />, document.getElementById('root'));

The easy way would be to trigger the new state with setState() from the API callback function such as in the example below, although I recommend using a library such as Redux for state management.
var MainComp = React.createClass({
getInitialState: function() {
return {someProp: ""}
},
callAPI: function() {
var root = 'http://jsonplaceholder.typicode.com';
$.ajax({
url: root + '/posts/1',
method: 'GET'
}).then(function(data) {
this.setState({someProp: data.body})
}.bind(this));
},
render: function(){
return (
<div>
<h2>{this.state.someProp}</h2>
<button onClick={this.callAPI}>Async</button>
</div>
)
}
});
React.render(<MainComp/>, document.getElementById("app"));
Please note this is a naive example, you should still cover up error cases and build a logic to trigger different views based on state.

Related

ReactJS componentDidUpdate not called even after props change

I'm working with draft-js for react where I make an api call (IN A FUNCTIONAL PARENT COMPONENT) and get contents to be put up in the editor. I have this code to insert the fetched data into the editor after receiving it in props:
Parent Component:
var draftContent = "";
var isContentFetched = false;
const CommentSection = () => {
let response = await fetch(url, {
method: "get",
headers: headers,
});
let query = await response.json(); // read response body and parse as JSON
draftContent = element.comment;
isContentFetched = true;
return(<RichTextEditor
isDraftFetched={isDraftFetched}
isContentFetched={isContentFetched}
placeholder=""
rows={6}
boxWidth="100%"
boxHeight="155px"
/>)
})
Editor Component:
componentDidUpdate(props) {
if(this.props.isContentFetched == true && draftAlreadyFilled== false) {
this._insertText(this.props.draftContent);
draftAlreadyFilled = true;
}
}
_insertText(htmlContent) {
this.setState({
editorState: EditorState.createWithContent(
ContentState.createFromBlockArray(
convertFromHTML(htmlContent)
)
),
})
}
Problem is, the text loaded and sent through props does not fill up in the editor. Instead once I am in the page and I click on refresh, then it loads fine.
PS: 1. I have checked related questions and those solutions did not work. 2. It is really necessary that I make the API call in the parent component.

react-http-request does not change loading state after second request

I am now using react-http-request in my React.js component to send request and process the response. The URL parameter passed is relevant to the component state such that when the state changes, the component will be re-rendered and change the component display.
This works on the first request. However, I found that the component does not return a {load: true} after the second request, and I wonder how to solve this.
I tried to call the onRequest method and set the loading state for the component, but I cannot change the loading state after the request is finished (as render function cannot change the state).
react-http-request: https://github.com/mbasso/react-http-request
My Code is like below:
var FilmList = React.createClass({
getInitialState: function(){
return {
queryType: this.props.queryType
}
},
// ... details emitted.
render: function(){
return (<Request
url={config.url.api + "/" + this.state.queryType}
method="get"
accept="application/json"
query={{ several parameter }}
>
{
({error, result, loading}) => {
if (loading || error) {
return <Loading />
}
else {
// process the result here.
}
}
}
</Request>)
}
So, my initial recommendation would be that you use some state management library (redux, mobx, etc) but it is not necessary to get a working example of your code, so:
import fetch from 'whatwg-fetch'; // gives compatibility with older browsers
var FilmList = React.createClass({
getInitialState: function(){
return {
queryType: this.props.queryType,
content: null
}
},
componentWillMount: function() {
this.fetchContent();
},
fetchContent: function() {
const uri = config.url.api + "/" + this.state.queryType;
// You can use w/e you want here (request.js, etc), but fetch is the latest standard in the js world
fetch(uri, {
method: 'GET',
// More properties as you see fit
})
.then(response => response.json()) // might need to do this ;)
.then(response => {
this.setState({
content: response
})
})
},
// ...
render: function(){
const content = this.state.content? (
// render your content based on this.state.content
): (
<Loading />
)
return content;
}
});
Haven't tested this code, but there are some nice benefits to it:
The http request is not dependant on React, which should (in theory) be for UI components.
The fetching mechanism is decoupled, and can be re-used at any point in the component lifecycle
In my opinion easier to read, divided into smaller logical chunks
I would recommend reading the React Component Lifecycle.
In this case, I read the source code of the react-http-request, and found that there is a weakness that after accepting and sending the second request, the component failed to update the state of "loading" returns.
// starts from Line 49
value: function componentWillReceiveProps(nextProps) {
if (JSON.stringify(this.props) === JSON.stringify(nextProps)) {
return;
}
this.request.abort();
this.performRequest(nextProps);
}
Manually changed the state of loading here can help reset the loading after each request received.
I changed the source code of this lib, and sent the pull request to the repo. It's now merged into master and ejected a new release.
See: https://github.com/mbasso/react-http-request/pull/3
Thus, this problem can be solved by keeping the lib update to the release (currently it is 1.0.3).

React component get request being made in one or clicks late

This one is kind of hard to explain, but basically when a click on a component, I make a get request for some data for another component. This however is only made after a couple of clicks.
I should probably also admit that I am not 100% sure if the place I am making the request is even correct, so if that's the case please let me know how I can get that fixed. Here's the code:
var ChangeLogData = React.createClass({
getInitialState: function () {
return {
content: {},
}
},
render: function () {
var _this=this;
$.get(this.props.source, function (data) {
var log = $.parseJSON(data);
_this.state.content = log;
}.bind(this));
return (
<div>
{_this.state.content[0]}
</div>
);
}
});
window.ChangeLog = React.createClass({
render: function () {
return (
<div>
<ChangeLogData name={this.props.params.name}
source={currentUrl + "/changelog/" +
this.props.params.name}/>
</div>
);
}
});
Edit: I should also probably add that it seems that most people recommend doing http requests on componentWillMount, but if I do that, the request only works once.
Edit 2: Here is the code of where the event is being called:
var AboutItem = React.createClass({
render: function () {
return (
<ListGroup>
{this.props.list.map(function (listValue,key) {
var link = currentUrl + "/changelog/" + listValue.split(' ')[0];
return <ListGroupItem key={key} className="module"
bsStyle="warning">
{listValue}
</ListGroupItem>
})}
</ListGroup>
);
}
});
I guess the idea is, the user will click on an item (that is dynamically generated), and when the item is clicked, it will send to the ChangeLog component the data in which it has to do the get request. So where exactly would I put my event handler?
I think the problem is that it's not being called correctly, as jquery is async...
var jqxhr = $.get( "example.php", function() {
alert( "success" );
})
.done(function() {
// PUT YOUR CALLBACK CODE HERE WITH THE RESULTS
})
.fail(function() {
alert( "error" );
})
.always(function() {
alert( "finished" );
});
And update the state in the .done()
You should not be making requests in your render method. You should also not be directly modifying state through this.state but instead use this.setState(). You also don't seem to be adding any onClick handlers.
You can do something like the following to trigger requests onClick:
var ChangeLogData = React.createClass({
getInitialState: function () {
return {
content: {},
}
},
handleClick: function() {
$.get(this.props.source, function (data) {
var log = $.parseJSON(data);
this.setState( { content: log } );
}.bind(this));
}
render: function () {
return (
<div onClick = { this._handleClick } >
{_this.state.content[0]}
</div>
);
}
If you want to do it on component mount you can put into componentDidMount() and call setState when you retrieve your data. This will cause the component to re-render with content
First: the get request is async, so by the time you get the response back the DOM is already rendered.
Second: Never update state inside the render method, if it works and you don't get an error message you most likely will create an infinite loop, render => updateState => render => udpateState...
you have multiple options, you can have the get request inside the function called after onClick (not shown in your code), and then update state and pass data as props. In this case you would be making a new get request every single time there's a click event. If you dont need a new get request on every click look into react lifecycle methods, in particular componentDidMount, which is basically executed after the react component is mounted, then you can do a get request there and update the state
componentDidMount: function() {
$.get(this.props.source, function (data) {
var log = $.parseJSON(data);
this.setState( { content: log } );
}.bind(this));
},
I can't see from your code what component should be clicked in order to trigger the request, but as far as could see, you should take the request out of the render() method. This method is called every time state/props change, so it might make your code make the request multiple times.
Another thing is that you should always mutate your state by calling this.setState() method, so in your case it would be _this.setState({ content: log }).
Now, if you change the name prop every time another component is clicked, you should do something like:
var ChangeLogData = React.createClass({
getInitialState: function () {
return {
content: {},
}
},
componentWillMount: function () {
this.getLog(this.props.source);
},
componentWillReceiveProps: function (nextProps) {
this.getLog(nextProps.source);
},
getLog: function (source) {
var _this = this;
$.get(source, function (data) {
var log = $.parseJSON(data);
_this.setState({
content: log
});
}.bind(this));
}
render: function () {
return (
<div>
{this.state.content[0]}
</div>
);
}
First you can extract the process to create the request call from the render() method into its own method, in this case, getLog(). Then, with two new methods from the React lifecycle, you can call getLog() when the component will be mounted and whenever new props come in from the parents components.

How do I pull items from MongoDB and update them when a new one is entered?

I am trying to build a simple blog with React, Express, MongoDB and Node. But I am still confused on (1) how to correctly make the ajax request to my database and how do I set state and (2) how to properly update the state.
I've tried setting getInitialState by making an AJAX request, but it won't work. Also I don't know if that is best practice. Also, once someone adds a new post, where am I supposed to place the POST and then how do I properly update state?
var React = require('react');
var List = React.createClass({
render: function() {
return (
<div>
<h2>{this.props.postbody}</h2>
</div>
)
}
})
// I'll break this up into smaller components later, but for now I just want
// to know where to put my database entries into the posts array.
// The only field in MongoDB right now is postbody.
var Home = React.createClass({
getInitialState: function() {
return {
posts: []
}
},
handleClick: function() {
$.ajax({
type: 'GET',
url: '/api/blogPosts',
success: function(data) {
this.setState = data;
console.log(this.setState);
}
})
},
render: function() {
return (
<div>
{this.state.posts.map(function(post) {
return (
<List postbody={post.postbody}></List>
)
})}
</div>
)
}
})
setState is a function, not a property to be set on this. You should do this.setState(data)

React: initial render fires before state is set (via ajax)

I've set things up so the HomePage component renders UserShow for the current logged in user. For example, if a user with an ID of 2 is logged in and visits the HomePage page, it will render their UserShow.
The "normal" UserShow works correctly. For example if you type in /users/18, it will properly render. However it's not working when HomePage renders it.
I'm new to React (especially its lifecycle methods), so my debugging has been to throw alerts in at various steps. I'd say the most important findings to share are:
currentUserID( ) is functioning and returns the correct ID
Hard-coding the value of state.userID within componentDidMount causes things to work correctly
These two points lead me to believe that Render is being called before it can update state.userID with its (correct) return value. Even more specific is that it's rendering before the .success portion of the this.currentUserID() ajax call returns. If this is so, what's the best way to go about not doing an initial render until an ajax call like this completes?
My code is in a state of spaghetti - it's my first time doing front-end routing with JavaScript. I'm also managing sessions via using the user's email as the token in localStorage - I'm new to sessions in JS as well. Please bear with me.
HomePage component:
var HomePage = React.createClass({
getInitialState: function(){
return{
didFetchData: false,
userID: null,
}
},
componentWillMount: function(){
newState = this.currentUserID()
this.setState({userID: newState})
// this.setState({userID: 2}) //hard-coding the value works
},
currentUserID: function(){
if(App.checkLoggedIn()){
var email = this.currentUserEmail()
this.fetchUserID(email)
}else{
alert('theres not a logged in user')
}
},
currentUserEmail: function(){
return localStorage.getItem('email')
},
fetchUserID: function(email){ //queries a Rails DB using the user's email to return their ID
$.ajax({
type: "GET",
url: "/users/email",
data: {email: email},
dataType: 'json',
success: function(data){
this.setState({didFetchData: 'true', userID: data.user_id})
}.bind(this),
error: function(data){
alert('error! couldnt fetch user id')
}
})
},
render: function(){
userID = this.state.userID
return(
<div>
<UserShow params={{id: userID}} />
</div>
)
}
})
UserShow component:
var UserShow = React.createClass({
getInitialState: function(){
return{
didFetchData: false,
userName: [],
userItems: [],
headerImage: "../users.png"
}
},
componentDidMount: function(){
this.fetchData()
},
fetchData: function(){
var params = this.props.params.id
$.ajax({
type: "GET",
url: "/users/" + params,
data: "data",
dataType: 'json',
success: function(data){
this.setState({didFetchData: 'true', userName: data.user_name, userItems: data.items, headerImage: data.photo_url})
}.bind(this),
error: function(data){
alert('error! couldnt load user into user show')
}
})
},
render: function(){
var userItem = this.state.userItems.map(function(item){
return <UserItemCard name={item.name} key={item.id} id={item.id} description={item.description} photo_url={item.photo_url} />
})
return(
<div>
<Header img_src={this.state.headerImage} />
<section className="body-wrapper">
{userItem}
</section>
</div>
)
}
})
So what you want to do is to avoid rendering anything until your ajax-request returns your result.
You can do a check in the render method if the state is how you want it. If it's not, then return null, or a loader or some other markup. When the componentDidMount then sets the state, it will trigger a re-render, since the userID then is set, it will return the userShow component
Example:
render(){
if(this.state.userID === null){
return null; //Or some other replacement component or markup
}
return (
<div>
<UserShow params={{id: userID}} />
</div>
);
}
Fetching the data in the userShow component could be done like this:
componentWillReceiveProps(nextProps){
//fetch data her, you'll find your prop params in nextProps.params
}
You can also avoid doing this here, by kicking the data-fetch in the render-method.
Your initial data set what your doing in componentWillMount will not help to set data because you want to fetch data from ajax. Debug fetchUserID and
currentUserID function whether your getting correct email from localstorage and user id from server. Others is fine.

Categories

Resources