Relay requests via setVariables - graphql

when a request is made via setVariables is there a way to take account of the local state in-between async requests i.e. to implement loading indicator ?
an illustration making requests to https://www.graphqlHub.com/graphql
_onChange = (ev) => {
this.setState({
loading:true
})
let gifType = ev.target.value;
this.props.relay.setVariables({
gifType
});
this.setState({
loading:false
})
}
this won't track the loading state and loading will pass on to false immediately while the async change to the view will have lag.
if we move loading into setVariables is there any way to track the response ? in the root container there is the ability to track response via
renderLoading={function() {
return <div>Loading...</div>;
}}
is there any similar method for Relay.createContainer
is it bad practice to use setVariables to navigate through data sets ?
full code
class GiphItems extends React.Component {
constructor(){
super();
this.state = {
loading: false
}
}
render() {
const random = this.props.store.random
return <div>
<select onChange={this._onChange.bind(this)} value={this.props.relay.variables.gifType}>
<option value="sexy">Sexy</option>
<option value="cats">Cats</option>
<option value="goal">Goal</option>
<option value="lol">LOL</option>
</select>
{this.state.loading ? 'LOADING' : <a href={random.url}><img src={random.images.original.url} className="img-responsive"/></a>}
</div>;
}
_onChange = (ev) => {
this.setState({
loading:true
})
let gifType = ev.target.value;
this.props.relay.setVariables({
gifType
});
this.setState({
loading:false
})
}
}
GiphItems = Relay.createContainer(GiphItems, {
initialVariables: {
gifType: "sexy"
},
fragments: {
store: () => Relay.QL`
fragment on GiphyAPI {
random(tag: $gifType ) {
id
url
images {
original {
url
}
}
}
}
`,
},
});

setVariables method also accepts a callback as the 2nd argument which responds to the events involved with the data fulfillment and it receives a 'readyState' object that you can inspect:
this.props.relay.setVariables({
gifType,
}, (readyState)=> {
if (!readyState.done) {
this.setState({
loading: true
})
} else if(readyState.done) {
this.setState({
loading: false
})
}
})

Great question. What you have is a viable option in terms of dealing with loading screens for a particular component. But that can become a burden to implement for every loading scenario for each individual component.
Here's what you can do instead if to provide a more generic solution: You can set up a global event system in your React app that will broadcast a global state to each component based on whether or not a call is being made. And for each component that you need this for, you can subscribe to this global event from componentDidMount() and unsubscribe with componentWillUnmount(). As soon as your component sees a change in this global state, that component should call setState(), which will determine whether or not that component should display a loading scene or not.
This is a good resource to learn how to communicate between components to set up a global event system:
https://facebook.github.io/react/tips/communicate-between-components.html
You can also use Facebook's Flux to implement this as well:
https://facebook.github.io/flux/
Hope this helps!

Related

Vue js function countSubcategories() returns [object Promise]

countSubcategories() function returns [object Promise] where it should return row counts of mapped subcategories.
This code is in vue.js & Laravel, Any suggestions on this?
<div v-for="(cat,index) in cats.data" :key="cat.id">
{{ countSubcategories(cat.id) }} // Here subcategories row counts should be displayed.
</div>
<script>
export default {
data() {
return {
cats: {},
childcounts: ""
};
},
created() {
this.getCategories();
},
methods: {
countSubcategories(id) {
return axios
.get("/api/user-permission-child-count/" + `${id}`)
.then(response => {
this.childcounts = response.data;
return response.data;
});
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => (this.cats = response.data));
}
}
};
</script>
As Aron stated in the previous answer as you are calling direct from the template the information is not ready when the template is rendered.
As far as I understood you need to run getCategories first so then you can fetch the rest of your data, right?
If that's the case I have a suggestion:
Send an array of cat ids to your back-end and there you could send back the list of subcategories you need, this and this one are good resources so read.
And instead of having 2 getCategories and countSubcategories you could "merge" then like this:
fetchCategoriesAndSubcategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => {
this.cats = response.data;
let catIds = this.cats.map(cat => (cat.id));
return this.countSubcategories(catIds) // dont forget to change your REST endpoint to manage receiving an array of ids
})
.then(response => {
this.childcounts = response.data
});
}
Promises allow you to return promises within and chain .then methods
So in your created() you could just call this.fetchCategoriesAndSubcategories passing the data you need. Also you can update your template by adding a v-if so it doesn't throw an error while the promise didn't finish loading. something like this:
<div v-if="childCounts" v-for="(subcategorie, index) in childCounts" :key="subcategorie.id">
{{ subcategorie }} // Here subcategories row counts should be displayed.
</div>
Hello!
Based on the provided information, it could be 2 things. First of all, you may try replacing:
return response.data;
with:
console.log(this.childcounts)
and look in the console if you have the correct information logged. If not, it may be the way you send the information from Laravel.
PS: More information may be needed to solve this. When are you triggering the 'countSubcategories' method?
I would do all the intial login in the component itself, and not call a function in template like that. It can drastically affect the performance of the app, since the function would be called on change detection. But first, you are getting [object Promise], since that is exactly what you return, a Promise.
So as already mentioned, I would do the login in the component and then display a property in template. So I suggest the following:
methods: {
countSubcategories(id) {
return axios.get("..." + id);
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
// or use async await pattern
axios.get("...").then(response => {
this.cats = response.data;
// gather all nested requests and perform in parallel
const reqs = this.cats.map(y => this.countSubcategories(y.id));
axios.all(reqs).then(y => {
// merge data
this.cats = this.cats.map((item, i) => {
return {...item, count: y[i].data}
})
});
});
}
}
Now you can display {{cat.count}} in template.
Here's a sample SANDBOX with similar setup.
This is happen 'cause you're trying to render a information who doesn't comeback yet...
Try to change this method inside created, make it async and don't call directly your method on HTML. Them you can render your variable this.childcounts.

Set State in Ajax Call Back throws error: Warning: setState(...): Can only update a mounted or mounting

I've got a fairly simple react container component that attempts to call set state in an ajax callback called from componentDidMount. The full error is:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the UserListContainer component.
the order of operations from my console.log are:
render
child-render
componentDidMount
ajax-data
[Big ol Error Message]
I started out using async/await but when I received the error I went back to callbacks with the same result. This is the relevant code:
export class UserListContainer extends React.Component<any, any>
{
constructor() {
super();
this.state = {
users: [], request: {}
};
}
//componentDidMount = async () => {
componentWillMount = () => {
console.log('componentWillMount');
//var response: Models.IUserQueryResponse = await Api.UserList.get(this.state.request);
Api.UserList.get(this.state.request).then((response) => {
console.log('ajax-data');
if (response.isOk) {
this.setState({ users: response.data, request: response.state });
}
});
}
render() {
console.log('render');
return <UserList
request={this.state.request}
users={this.state.users}
onEditClick={this.edit}
onRefresh={this.refresh}
/>;
}
Any help would be appreciated.
you cannot set state in componentWillMount because your component could be in a transitioning state.. also it will not trigger a re-rendering. Either use componentWillReceiveProps or componentDidUpdate.
Now that aside your issue is that you are calling setState in the callback from an API request. and the issue with that is you probably have unmounted that component and dont want to setState anymore.
you can fix this with a simple flag
constructor() {
super();
this.state = {
users: [], request: {}
};
this.isMounted = false;
}
componentDidMount(){
this.isMounted = true
}
componentWillUnmount(){
this.isMounted = false;
}
then in your api request you would do this.
Api.UserList.get(this.state.request).then((response) => {
console.log('ajax-data');
if (response.isOk && this.isMounted) {
this.setState({ users: response.data, request: response.state });
}
});
I think is better to use componentWillMount() instead of componentDidMount() cause you want to load the list and then set the state, not after the component was mounted.

How to show and hide activityindicator from actions in react native redux?

I want to show progress/activityindicator whenever I make an api call from my app, But I can't find the correct solution for this. I can show the activityindicator but I can't hide it from view. Here is my code:
StatusModal.js
constructor(props) {
super(props)
// set state with passed in props
this.state = {
message: props.error,
hide: props.hide,
animating: props.animating
}
}
render() {
if(this.state.animating){
return(
<ActivityIndicator
animating={true}
size="small"
/>
)
}else{
return(
<View>
</View>
)
}
}
and here is how I change the animating state
//show activity
Actions.statusModal({animating: true})
//hide activity
Actions.statusModal({animating: false})
and here is my scene structure:
<Scene key="modal" component={Modal} >
<Scene key="root">
<Scene key='login' component={Login} title='Login Page' hideNavBar={true} />
</Scene>
<Scene key="statusModal" component={StatusModal} />
</Scene>
How can I hide the activity indicator from actions?
It's pretty common thing for applications to handle loading.
The simplest way to handle it is create a separate reducer for this.
E.g.:
function appStateReducer(state = { loading: false }, action) {
switch(action.type) {
case "SET_LOADING":
return { loading: action.payload };
default:
return { loading: false };
}
}
...
const rootReducer = combineReducer(
...otherReducers,
app: appStateReducer
);
...
Later you can use it in your components.
...
const mapStateToProps = (state) => ({
loading: state.app.loading,
});
#connect(mapStateToProps)
class MyScene extends Component {
...
render() {
const { loading } = this.props;
if (loading) {
return (
);
}
return ;
}
Dispatch action SET_LOADING in the start of query with true and dispatch SET_LOADING with false in the end or in case of error.
But one single state for handling loading isn't enough for big application. For example: you need to handle parallel queries to API and to show loader for every single query. Then you'll need such fields in other reducers.
By the way, you'll definitely meet the issue with async flow. I would recommend such middlewares as redux-thunk, redux-saga and redux-observable.
My favourite one is redux-saga. It's a very powerful way to control your async flow and all other side effects in your application.
I hope it helps.

How to load AJAX in react

Im trying to get my json result into my react code
The code looks like the following
_getComments() {
const commentList = "AJAX JSON GOES HERE"
return commentList.map((comment) => {
return (
<Comment
author={comment.author}
body={comment.body}
avatarUrl={comment.avatarUrl}
key={comment.id} />);
});
}
How do i fetch AJAX into this?
First, to fetch the data using AJAX, you have a few options:
The Fetch API, which will work out of the box in some browsers (you can use a polyfill to get it working in other browsers as well). See this answer for an example implementation.
A library for data fetching (which generally work in all modern browsers). Facebook recommends the following:
superagent
reqwest
react-ajax
axios
request
Next, you need to use it somewhere in your React component. Where and how you do this will depend on your specific application and component, but generally I think there's two scenarios to consider:
Fetching initial data (e.g. a list of users).
Fetching data in response to some user interaction (e.g. clicking a
button to add more users).
Fetching initial data should be done in the life-cycle method componentDidMount(). From the React Docs:
var UserGist = React.createClass({
getInitialState: function() {
return {
username: '',
lastGistUrl: ''
};
},
componentDidMount: function() {
this.serverRequest = $.get(this.props.source, function (result) {
var lastGist = result[0];
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}.bind(this));
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
return (
<div>
{this.state.username}'s last gist is
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});
ReactDOM.render(
<UserGist source="https://api.github.com/users/octocat/gists" />,
mountNode
);
Here they use jQuery to fetch the data. While that works just fine, it's probably not a good idea to use such a big library (in terms of size) to perform such a small task.
Fetching data in response to e.g. an action can be done like this:
var UserGist = React.createClass({
getInitialState: function() {
return {
users: []
};
},
componentWillUnmount: function() {
this.serverRequest && this.serverRequest.abort();
},
fetchNewUser: function () {
this.serverRequest = $.get(this.props.source, function (result) {
var lastGist = result[0];
var users = this.state.users
users.push(lastGist.owner.login)
this.setState({ users });
}.bind(this));
},
render: function() {
return (
<div>
{this.state.users.map(user => <div>{user}</div>)}
<button onClick={this.fetchNewUser}>Get new user</button>
</div>
);
}
});
ReactDOM.render(
<UserGist source="https://api.github.com/users/octocat/gists" />,
mountNode
);
Lets take a look on the fetch API : https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
Lets say we want to fetch a simple list into our component.
export default MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
lst: []
};
this.fetchData = this.fetchData.bind(this);
}
fetchData() {
fetch('url')
.then((res) => {
return res.json();
})
.then((res) => {
this.setState({ lst: res });
});
}
}
We are fetching the data from the server, and we get the result from the service, we convert is to json, and then we set the result which will be the array in the state.
You can use jQuery.get or jQuery.ajax in componentDidMount:
import React from 'react';
export default React.createClass({
...
componentDidMount() {
$.get('your/url/here').done((loadedData) => {
this.setState({data: loadedData});
});
...
}
First I'd like to use fetchAPI now install of ajax like zepto's ajax,the render of reactjs is asyn,you can init a state in the constructor,then change the state by the data from the result of fetch.

How do I keep context in react without stringing .bind(this)?

I'm using react to retrieve data from parse, manipulate it in my own function, and then update a component in the render.
The problem is that I can't update the state within my own, convoluted function unless I attach a string of bind(this). The entire component looks like this:
React.Component({
getInitialState: function () {
return{
isloading:true
}
},
componentDidMount: function(){
this.myStupidFunction()
},
myStupidFunction : function(){
(
(
(nested parse queries that eventually ...
return an object and set isloading:false).bind(this))
.bind(this))
.bind(this)
},
render: function (){
if (this.state.isloading) {
return(
<Text "...isloading"/>
)
} else {
return(
...actually return important stuff...
)
}
}
})
What is the smarter way to do this? Do I need to really .bind(this) for every nested function?
There are a few ways to maintain the context of your component.
Use ES6 Arrows
If you use ES6 arrows to define your functions. Arrow functions force the inner context of this to be the same as the outer context, regardless of how the function is called.
parse.find({
success: results => {
// this is correct
console.log(this);
}
});
I think this is the most elegant solution, but not all browsers support arrow functions yet.
Use Component Methods
React automatically binds this into each of the top level methods on your component. They are always guaranteed to have the correct context.
onSuccess: function() {
// this is correct
console.log(this);
},
componentWillMount: function() {
parse.find({
success: this.onSuccess
});
}
This is also fairly elegant, in my opinion. It lets React deal with the messiness of context whilst you just write code. However, it can mean that you end up with far too many methods at the top level of your component, so use it sparingly.
As an Argument
Some functions, such as map allow you to optionally pass a context to use as this as a final argument. This allows you to maintain the correct context without .bind(this).
data.map(function() {
console.log(this);
// this is correct
}, this);
This only works for some methods, so it's not really a universal solution.
Alias this
Create a reference to this and use that instead.
var __this__ = this;
parse.find({
success: results => {
// __this__ is correct
console.log(__this__);
}
});
This hack has been around forever in Javascript, but I don't think it's a great way to solve the problem.
Use ES7 Function Bind
For those who like to Javascript on the edge, you could also achieve this using the ES7 function bind syntax proposal — currently implemented in Babel.
parse.find({
success: this::function(results) {
// this is correct
console.log(this);
}
});
This requires using experimental proposal stage features of ES7. You may not want to start using it yet, but it's definitely interesting to be aware of. The value on the left hand side will be bound into the function on the right, as this.
Use a closure at the beginning of the function to capture this. It will be usable in any nested structure. The conventional names for such a closure are self _this and that. I prefer self.
myStupidFunction : function(){
var self = this;
someAsyncCall(1,2, function(result) {
//some nested stuff
anotherAsyncCall(1,2 function(innerResult) {
self.setState(innerResult);
});
});
}
one solution could be using local variable
myStupidFunction:function(){
var that=this
ParseReact.Mutation.Create('Place', {
name: 'New Place',
user: Parse.User.current()
})
.dispatch()
.then(function() {
that.refreshQueries();
});
}
Using ES7 Property Initalizer Syntax, currently implemented in Babel.
The key is the methodName = () => { //method return }
You can read more here.
import React from 'react';
export default class Note extends React.Component {
constructor(props) {
super(props);
this.state = {
editing : false
}
}
render() {
const editing = this.state.editing;
return (
<div>{ editing ? this.renderEdit() : this.renderTask() }</div>
)
}
renderEdit = () => {
return (
<input type="text"
className="edit-input"
autoFocus={true}
defaultValue={this.props.task}
onBlur={this.finishEdit}
onKeyPress={this.checkEnter} />
)
}
renderTask = () => {
const onDelete = this.props.onDelete;
return (
<div onClick={this.edit}>
<span className="task-body">{this.props.task}</span>
{ onDelete ? this.renderDelete() : null }
</div>
)
}
renderDelete = () => {
return (
<button className="delete-btn" onClick={this.props.onDelete}>x</button>
)
}
edit = () => {
this.setState({
editing : true
})
}
checkEnter = (e) => {
if(e.key === "Enter") {
this.finishEdit(e);
}
}
finishEdit = (e) => {
this.props.onEdit(e.target.value);
this.setState({
editing : false
})
}
}
// Note: Sample class from project above.

Resources