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.
Related
I have a checkbox list of domain tlds, such as com, net, io, etc. I also have a search text input, where I can drill down the list of 500 or so domains to a smaller amount. For example, if I start to type co in to my search text input, I will get back results that match co, such as co, com, com.au, etc. I am using Laravel and Vue,js 3 to achieve this with a watcher. It works beautifully. How can an achieve the same within a Pinia store?
Here is my code currently:
watch: {
'filters.searchedTlds': function(after, before) {
this.fetchsearchedTlds();
}
},
This is inside my vue component.
Next is the code to fetch searched tlds:
fetchsearchedTlds() {
self = this;
axios.get('/fetch-checked-tlds', { params: { searchedTlds: self.filters.searchedTlds } })
.then(function (response) {
self.filters.tlds = response.data.tlds;
console.log(response.data.tlds);
})
.catch(function (error) {
console.log(error);
})
.then(function () {
// always executed
});
},
And finally, the code inside my Laravel controller:
public function fetchCheckedTlds(Request $request)
{
$data['tlds'] = Tld::where('tld', 'LIKE','%'.$request->input('searchedTlds').'%')->pluck('tld');
return response()->json($data);
}
I am converting my code to use a Pinia store and I am stuck on how to convert my vue component watcher to Pinia?
Many thanks in advance.
To watch a pinia status, you may watch a computed attribute based on pinia or use watch getter
Your pinia may look like the one below.
~/store/filters.js
export const useFilters = defineStore('filters', {
state: () => {
return {
_filters: {},
};
},
getters: {
filters: state => state._filters,
},
...
}
In where you want to watch
<script setup>
import { computed, watch } from 'vue';
import { useFilters } from '~/store/filters.js';
const filters = useFilters();
// watch a computed attributes instead
const searchedTlds = computed(() => {
return filters.filters?.searchedTlds || '';
});
watch(
searchedTlds,
(newValue, oldValue) {
fetchsearchedTlds();
}
);
// or use watch getter
watch(
() => {
return filters.filters?.searchedTlds || '';
},
(newValue, oldValue) {
fetchsearchedTlds();
}
);
</script>
The first parameter of watch() can be a single ref or a getter function, or an array of getter functions, for more details, please view the Watch Source Types.
I have a function that returns a BehaviorSubject but when I try to use the data I get back from the function I need to use it once all the data is back, is there a way to know when the BehaviorSubject is done pulling all the data?
I tried using .finally but it never gets called. Here is the code I'm using.
getData() {
let guideList = '';
this.getChildren(event.node)
.subscribe(
function(data) {
console.log('here');
guideList = data.join(',');
},
function(err) {
console.log('error');
},
function() {
console.log('done');
console.log(guideList);
}
);
}
getChildren(node: TreeNode) {
const nodeIds$ = new BehaviorSubject([]);
//doForAll is a promise
node.doForAll((data) => {
nodeIds$.next(nodeIds$.getValue().concat(data.id));
});
return nodeIds$;
}
Attached is a screen shot of the console.log
Easiest way is to just collect all the data in the array and only call next once the data is all collected. Even better: don't use a subject at all. It is very rare that one ever needs to create a subject. Often people use Subjects when instead they should be using a more streamlined observable factory method or operator:
getChildren(node: TreeNode) {
return Observable.defer(() => {
const result = [];
return node.doForAll(d => result.push(d.id)).then(() => result);
});
}
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.
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!
deftjs looks really promising as it adds exactly the necessary things I missed in the MVC implementation of ExtJs.
What I actually miss is a functionality that makes routing possible/ easy. Extjs has a Ext.ux.Router functionality but I formerly used code like this with help of this lib:
initRoutes: function () {
var me = this;
Log.debug('Books.controller.App: initRoutes');
//use PATH.JS library until ExtJs supports routing as Sencha Touch 2.0 does. (see utils\Path)
Path.map("#/home").to(function () {
me.getController('Home').index();
});
Path.map("#/trackingsheet").to(function () {
me.getController('TrackingSheet').index();
});
Path.root('#/home');
Path.listen();
}
As the procedure of creating the crucial parts in deftjs is now exactly the other way around (view creates the controller) I certainly cannot refer to a controller's method and instantiate the view and make it the visible one. I have a pretty simple card layout here - what means only one view can be visible at a time, it is not necessary to go any deeper like this (e.g. make a task pane visible or the like).
What is the preferred way to do it?
I can think of making the Viewport a view factory having some methods like the controller before.
Thanks,
da5id
I solved this problem by using Ext.util.History class in a history context class that can raise an event when the hash changes:
Ext.define('myApp.context.HistoryContext', {
mixins: {
observable: 'Ext.util.Observable'
},
constructor: function(config) {
var me = this;
if (config == null) {
config = {};
}
this.initConfig(config);
Ext.util.History.add('home');
//init Ext.util.History; if there is a hash in the url,
//controller will fire the event
Ext.util.History.init(function(){
var hash = document.location.hash;
me.fireEvent('tokenChange', hash.replace('#', ''));
});
//add change handler for Ext.util.History; when a change in the token occurs,
//this will fire controller's event to load the appropriate content
Ext.util.History.on('change', function(token){
me.fireEvent('tokenChange', token);
});
this.mixins.observable.constructor.call(this);
this.addEvents('tokenChange');
return this.callParent(arguments);
}
});
Then you can inject this context in to your controller, and observe the token change, and implement the action in dispatch method:
Ext.define('myApp.controller.HomeController', {
extend: 'Deft.mvc.ViewController',
inject: [
'historyContext'
],
control: {
appContainer: {},
home: {
click: 'addHistory'
},
about: {
click: 'addHistory'
}
},
observe: {
historyContext: {
tokenChange: "dispatch"
}
},
init: function() {
return this.callParent(arguments);
},
switchView: function(view) {
//change this to get the cards for your card layout
this.getAppContainer().add(Ext.ComponentMgr.create({
xtype : view,
flex : 1
}));
},
addHistory: function(btn) {
var token = btn.itemId;
Ext.util.History.add(token);
},
dispatch: function(token) {
// switch on token to determine which content to load
switch(token) {
case 'home':
this.switchView('view-home-Index');
break;
case 'about':
this.switchView('view-about-Index');
break;
default:
break;
}
}
});
This should be ok for the first level routing (#home, #about), but you need to implement your own mechanism to fetch the token for the second and third level routes. (#home:tab1:subtab1) You can possibly create a service class that can handle fetching the hash and inject the service to each controllers to dispatch.
For further discussion in this topic, go to https://github.com/deftjs/DeftJS/issues/44