Any way to bind react variables between functions? - javascript

I want to avoid typing the same lines of code. Currently, I have an app that is supposed to make an API call, like so.
render: function(){
var processappkey = localStorage.getItem('yourid');
var weather = new XMLHttpRequest();
var deesfault = "Houston, Texas";
weather.open("GET", "http://apidatafromsomewebsiteq="+deesfault+"&units=imperial&appid="+processappkey, false);
weather.send(null);
var r = JSON.parse(weather.response);
var check = r.main.temp;
var theunicorn = r.weather[0].icon;
return (<p>{theunicorn}</p>)
}
I would like to split this up to something like this:
somecontainer: function(){
var processappkey = localStorage.getItem('yourid');
var weather = new XMLHttpRequest();
var deesfault = "Houston, Texas";
weather.open("GET", "http://apidatafromsomewebsiteq="+deesfault+"&units=imperial&appid="+processappkey, false);
weather.send(null);
var r = JSON.parse(weather.response);
var check = r.main.temp;
var theunicorn = r.weather[0].icon;
},
render: function() {
{this.somecontainer()}
return (
<p>{theunicorn}</p>
)
}
I will be calling the API from different areas in my app. Not to mention including a setInverval, which will have me repeating the code again.
As a matter of fact, while I am at it I would also like to know how to go about something like this.
render: function() {
this.somecontainer();
setInterval(function() {
this.somecontainer();
}, 5000);
}
However, that is a different question, and I'll be happy for insight on the first issue.

Good question, pretty easy answer. Just have a function that goes and gets the data you want, and returns the result via a callback function. This utility function would sit in another file somewhere and you can import it and call it from any component. Then, take the data that the function returns and put it in your components state.
You should almost certainly not be calling an API in the render method. React can run the render() method a lot depending on your app. If you want it to fire when the component first loads, use componentDidMount (this will only fire on the client, handy if you're using server-side rendering).
let counter = 0;
// separate utility
function goGetAUnicorn(callback) {
// replicate async for demonstration...
setTimeout(() => {
callback(`I am unicorn picture #${counter++}`);
}, 100)
}
class Unicorn extends React.Component {
constructor(props) {
super(props);
this.state = {
unicornPicture: '',
};
}
componentDidMount() {
// runs once, client side only
goGetAUnicorn(unicornPicture => {
this.setState({unicornPicture});
});
// to simulate reusing the same function elsewhere at some other time
setInterval(() => {
goGetAUnicorn(unicornPicture => {
this.setState({unicornPicture});
});
}, 1000)
}
render() {
return (
<div>Here is your unicorn: {this.state.unicornPicture}</div>
);
}
}
ReactDOM.render(<Unicorn />, document.getElementById('app'));
I'm using setTimeout just to indicate that you must wait for the response before carrying on. I'd actually use a promise, not a callback, but they both work.
Here's a jsbin to play with: https://jsbin.com/lohojo/1/edit?html,js,output

Related

Vuejs3/Vuex4 conditional render on promise fulfillment

I have custom objects for holding child objects full of data. The child objects are initiated with null values for all their properties, so the objects can be referenced and their properties filled from remote sources. This creates a lazy-loading setup.
This code is going to be extremely trimmed down, but everything relevant should be here:
class Collection extends Object {
constructor(){
this.loaded = false;
var allLoaders = [];
var loaderPropmises = [];
var resolver;
const $this = this;
var trackLoaders = function(){
$this.loaded = false;
loaderPromises.push(Promise.all(allLoaders).then(() => {
//... irrelevant logic in here to ensure only the latest promise sets loaded to true
$this.loaded = true; //This is getting called where I expect
resolver();
}));
}
//hook for outside things to watch the promise if they want
this.loader = new Promise((resolve) => {
//this only gets resolved once, which is fine
resolver = resolve;
});
//... bunch of code around adding child objects, but the important part:
this.add(child){
this[child.id] = child;
this.allLoaders.push(child.loader);
trackLoaders();
}
}
}
The child then looks like:
class Child extends Object {
constructor(){
this.loaded = false;
var resolver;
const $this = this;
this.loader = new Promise((resolve) => {
resolver = resolve;
}).then((){
$this.loaded = true;
});
this.populate(data){
//bunch of stuff to set data to properties on this object
resolver();
}
}
}
In Vuex 4 I have these Collections as properties on an "AppData" object in the store:
const store = createStore({
state: function(){
AppData: {}
},
mutations: {
setupCollection(state, name){
if (!Object.hasOwnProperty.call(state.AppData, name){
state.AppData[name] = new Collection();
}
}
},
actions: {
//this is called on each row of data returned from an Axios call
add (context, {name, data}){
context.state.AppData[name][data.id].populate(data);
}
}
});
The idea is that whenever a Child is added to a Collection, the collection loaded property will be false until all the Child loader promises resolve. This all executes perfectly... Except that the loaded bools aren't reactive.
Right now, I have a Promise.all in each component's Created function that flags the component as "loaded" once all the objects needed for the component have had their "loader" promises resolved. This absolutely works, but isn't ideal as different data will be available at different times, and there are sometimes hundreds or more of these classes on screen at once. What I'm trying to accomplish is:
<div v-if="!myCollection.loaded">
Loading...
</div>
<div v-else>
Show the data I want here {{myCollection.property}}
</div>
So I have two thoughts on overcoming this, either of which would be great:
VueJS3 no longer has a need for Vue.set(), because Proxies. How would I make the loaded bools here reactive then? Or more specifically, what am I doing that prevents this from working?
Alternatively, is there a practical way to use the loader promise directly in a template?
It looks like Vue's ref is what I needed:
this.loaded = ref(false);
This works, at least on the Child class. I have some sort of circular referencing issue going on and haven't been able to test on the Collection class yes, but it should work the same.

Accessing Svelte component properties in a callback?

Imagine that you have a lot of properties in a component:
let a = 'foo';
let b = 'bar';
// ...
let z = 'baz';
You then want to do something like update all of them from an external callback, like in another library (i.e. something that isn't and can't be a Svelte component itself).
A simple use case is just an AJAX method to load in a bunch of data (assume this ajax function works and you can pass it a callback):
onMount(async function() {
ajax('/data', function(data) {
a = data.a;
b = data.b;
// ...
z = data.z;
});
});
This works, but it's incredibly boilerplaty. What I'd really like is a way to loop through all the properties so they can be assigned to programmatically, especially without prior knowledge on the outside library/callback's part.
Is there no way to get access to a Svelte component and its properties so you can loop through them and assign them from an outside function?
Vue has a simple solution to this, because you can pass the component around, and still check and assign to its properties:
var vm = this;
ajax('/data', function(data) {
for (var key in data) {
if (vm.hasOwnProperty(key)) {
vm[key] = data[key];
}
});
});
I have seen some solutions to this, but they're all outdated - none of them work with Svelte 3.
Apologies if this has been asked before. I've spent days trying to figure this out to avoid all that extra boilerplate and the closest I could find is Access Component Object in External Callback? which does not have an answer right now.
If possible, you could put the ajax call in the parent component and have the data returned from it stored in a temporary object, that you then pass on to the component using the spread operator.
<Component { ...dataObject }></Component>
let dataObject = {};
onMount(async function() {
ajax('/data', function(data) {
dataObject = data;
});
});
You can reduce the boilerplate by using destructuring:
onMount(async function() {
ajax('/data', data => {
({ a, b, ..., z } = data);
});
});
But if you have a very large number of variables, you might be better off just putting them in an object in the first place:
let stuff;
onMount(async function() {
ajax('/data', data => {
stuff = data;
});
});

How to pass a ReactJS function as a callback to a external JS function?

I am implementing a credit card payment form in ReactJS using a 3rd party bank JS which works using iframes.
The logic is I load bank's javascript in my page, something like this:
<script src="https://my-bank.com/super-secure-script.js"></script>
Then I collect user's CC data in my ReactJS component, then call a function like this:
window.Bank.SendPayment(CC, this.paymentCompletedCallback);
...
paymentCompletedCallback = (result) => {
// process payment result here, inside my component
}
The problem is: the code inside super-secure-script.js cannot find the callback this.paymentCompletedCallback, because it is inside inside my component.
The question is: how can I pass to an external script a reference to a react object function?
You can attach the function to the window object:
const Component = () => {
const func = () => {
console.log('do something');
};
window.func = func;
};
this.paymentCompletedCallback.bind(this) may solve the problem.
Here is another example of when binding is necessary, hope that makes it more clear to you:
var Button = function(content) {
this.content = content;
};
Button.prototype.click = function() {
console.log(this.content + ' clicked');
};
var myButton = new Button('OK');
myButton.click();
var looseClick = myButton.click;
looseClick(); // not bound, 'this' is not myButton - it is the global object
var boundClick = myButton.click.bind(myButton);
boundClick(); // bound, 'this' is myButton

React Native loading screen (conditional render)

I am trying to render a view in a React Native Android app when a HTTP call has completed.
My render method:
render: function() {
if (this.state.obj.name) {
return this.renderPage();
} else {
return (<Loading />);
}
}
< Loading /> contains simple View and Text elements which display "Loading".
I call the network request in componentDidMount():
componentDidMount: function() {
var self = this;
SomeService.getObjById(1).then(function(data) {
var obj = JSON.parse(data._bodyText);
self.setState({
obj: obj
});
});
},
However, when I update the state and instantiate 'obj' (which now has a name prop) render is recalled, it returns render page but the view does not update.
My renderPage method works when it is light like:
renderPage: function() {
return (
<View><Text>Hello</Text></View>
)
}
But when it has more than say 6 lines of code it seems it doesn't update.
Any idea why event though the conditional returns different components when the call is finished, the actual view does not update?
I solved this by making the request before the change of view.
This still didnt work but I added a setTimout(function(){}, 0) around the navigator.push() and this worked, as it pushed the 'push' to the end of the call stack.
Not amazing, but works

Flux/Alt setTimeout not updating store

I'm trying to create a basic "Toast" like service in my React app using Alt.
I've got most of the logic working, I can add new items to the array which appear on my view when triggering the add(options) action, however I'm trying to also allow a timeout to be sent and remove a toast item after it's up:
onAdd(options) {
this.toasts.push(options);
const key = this.toasts.length - 1;
if (options.timeout) {
options.timeout = window.setTimeout(() => {
this.toasts.splice(key, 1);
}, options.timeout);
}
}
On add, the toast appears on my page, and the timeout also gets triggered (say after a couple of seconds), however manipulating this.toasts inside of this setTimeout does not seem to have any effect.
Obviously this is missing the core functionality, but everything works apart from the setTimeout section.
It seems that the timeout is setting the state internally and is not broadcasting a change event. It might be as simple as calling forceUpdate(). But the pattern I use is to call setState() which is what I think you might want in this case.
Here is an example updating state and broadcasting the change event.
import alt from '../alt'
import React from 'react/addons'
import ToastActions from '../actions/ToastActions'
class ToastStore {
constructor() {
this.toasts = [];
this.bindAction(ToastActions.ADD, this.add);
this.bindAction(ToastActions.REMOVE, this.remove);
}
add(options) {
this.toasts.push(options);
this.setState({toasts: this.toasts});
if (options.timeout) {
// queue the removal of this options
ToastActions.remove.defer(options);
}
}
remove(options) {
const removeOptions = () => {
const toasts = this.toasts.filter(t => t !== options);
this.setState({toasts: toasts});
};
if (options.timeout) {
setTimeout(removeOptions, options.timeout);
} else {
removeOptions();
}
}
}
module.exports = alt.createStore(ToastStore, 'ToastStore');

Categories