In this js file I have defined this const Device that is the name of the mobile im using. The thing is when I call Device from another js it returns it empty. Why?
import DeviceInfo from 'react-native-device-info';
var Device = ''
DeviceInfo.getDeviceName().then(deviceName => {
Device = deviceName + ' ('+DeviceInfo.getSystemVersion()+')'
});
export default Device;
The reason why your current approach doesn't work is because DeviceInfo.getDeviceName is an asynchronous call that returns a Promise which resolves with the deviceName.
var Device = ''
DeviceInfo.getDeviceName().then(...)
// the call above will not wait before it goes to next line
// so `Device` will stay as empty string and will be exported as such
export default Device
Instead, if you want to re-use this logic in multiple places, I suggest turning this into a function, like in the following example:
import DeviceInfo from 'react-native-device-info';
function getDeviceFullName() {
return DeviceInfo.getDeviceName()
.then(deviceName => {
return `${deviceName} (${DeviceInfo.getSystemVersion()})`
})
}
export default getDeviceFullName
Then, somewhere else, you could call this function like in the following example:
import getDeviceFullName from './getDeviceFullName'
class App extends React.Component {
state = {deviceName: ""}
componentDidMount() {
getDeviceFullName()
.then(deviceName => {
this.setState({ deviceName })
})
.catch(/* handle errors appropriately */)
}
render() {
return this.state.deviceName === ""
? "Loading"
: this.state.deviceName;
}
}
EDIT as OP mentioned something about Formik integration.
Haven't tested this, but something like the following would be my approach.
class MyReactNativeForm extends React.Component {
state = {
initialValues: { email: "johndoe#gmail.com", deviceName: "" }
}
componentDidMount() {
getDeviceFullName()
.then(deviceName => {
this.setState(prevState => {
return {
initialValues: {...prevState.initialValues, deviceName}
}
})
})
.catch(/* handle errors appropriately*/)
}
render() {
return this.state.initialValues.deviceName === ""
? "Loading"
: <Formik initialValues={this.state.initialValues} />
}
Edit: Someone else posted a great answer while I was typing mine up. My answer re-uses the value for Device because that is how the original question worked. Like the approved answer, you don't need to set it and forget it, but instead you can only return a cb/promise that always gets the most recent data.
Two things immediately stand out. Like #mhodges says, you can't reassign a value to a constant. Instead, you should use let.
Your next issue is trying to export the value returned by an asynchronous call. When you import Device from your js file, the export statement executes before you re-assign the value of Device.
What if you exported your async function or a callback instead?
Promise'able:
var Device;
export function getDeviceInfo() {
if (Device) return Promise.resolve(Device);
return DeviceInfo.getDeviceName().then(deviceName => {
Device = deviceName + ' ('+DeviceInfo.getSystemVersion()+')';
return Promise.resolve(Device);
});
}
Usage might look like:
import { getDeviceInfo } from './utils';
getDeviceInfo().then((deviceInfo) => console.log('Got it!', deviceInfo));
Callback'able:
var Device;
export function getDeviceInfo(cb) {
if (Device) return cb(Device);
DeviceInfo.getDeviceName().then(deviceName => {
Device = deviceName + ' ('+DeviceInfo.getSystemVersion()+')';
cb(Device);
});
}
Usage might look like:
import { getDeviceInfo } from './utils';
getDeviceInfo(function(deviceInfo) {
console.log('Got it!', deviceInfo)
});
Related
When the page is being loaded for the first time, vue component is not waiting for my custom store file to process it. I thought it might fix it with promises but I am not sure on how to do so on functions that do not really require extra processing time.
I am not including the entire .vue file because I know it surely works just fine. My store includes couple of functions and it is worth mentioning it is not set up using vuex but works very similarly. Since I also tested what causes the issue, I am only adding the function that is related and used in MainComp.
Vue component
import store from "./store";
export default {
name: "MainComp",
data() {
return {
isLoading: true,
storageSetup: store.storage.setupStorage,
cards: Array,
};
},
created() {
this.storageSetup().then(() => {
this.cards= store.state.cards;
});
this.displayData();
},
methods: {
displayData() {
this.isLoading = false;
},
}
My custom store.js file
const STORAGE = chrome.storage.sync;
const state = {
cards: []
};
const storage = {
async setupStorage() {
await STORAGE.get(['cards'], function (data) {
if (Object.keys(data).length === 0) {
storage.addToStorage('ALL');
// else case is the one does not work as required
} else {
data.cards.forEach((elem) => {
// modifies the element locally and then appends it to state.cards
actions.addCard(elem);
});
}
});
}
};
export default {
state,
storage
};
Lastly, please ignore the case in setupStorage() when the length of data is equal to 0. If there is nothing in Chrome's local space, then a cards is added properly(state.cards is an empty array every time the page loads). The problem of displaying the data only occurs when there are existing elements in the browser's storage.
How can I prevent vue from assuming cards is not an empty array but instead wait until the the data gets fetched and loaded to state.cards (i.e cards in MainComp)?
Sorry if the problem can be easily solved but I just lost hope of doing it myself. If any more information needs to be provided, please let me know.
Your main issue is that chrome.storage.sync.get is an asynchronous method but it does not return a promise which makes waiting on it difficult.
Try something like the following
const storage = {
setupStorage() {
return new Promise(resolve => { // return a promise
STORAGE.get(["cards"], data => {
if (Object.keys(data).length === 0) {
this.addToStorage("All")
} else {
data.cards.forEach(elem => {
actions.addCard(elem)
})
}
resolve() // resolve the promise so consumers know it's done
})
})
}
}
and in your component...
export default {
name: "MainComp",
data: () => ({
isLoading: true,
cards: [], // initialise as an array, not the Array constructor
}),
async created() {
await store.storage.setupStorage() // wait for the "get" to complete
this.cards = store.state.cards
this.isLoading = false
},
// ...
}
I have a js object in which I return my endpoint addresses from api. This is a very nice solution for me, it looks like this:
export const API_BASE_URL = 'http://localhost:3000';
export const USERS = '/Users';
export default {
users: {
checkEmail: (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`,
notifications: `${API_BASE_URL}${USERS}/notifications`,
messages: `${API_BASE_URL}${USERS}/messages`,
},
};
Now I can call this address in my redux-saga to execute the xhr query:
import { api } from 'utils';
const requestURL = api.users.notifications;
But I'm a bit stuck because now I have a problem - base path is missing here: '/users'.
Now when I call api.users, then I get a object. I would like to have a default value after calling the object like:
import { api } from 'utils';
const requestURL = api.users; // http://localhost:3000/Users
const requestURL2 = api.users.notifications; // http://localhost:3000/Users/notifications
I know that I could add a new string with the name 'base' to the object and add '/Users' there, but I don't like this solution and I think, there is a better solution.
You could do one of the following:
extend the String class
const API_BASE_URL = "http://localhost:3000"
const USERS = "/Users"
class UsersEndpoints extends String {
constructor(base) {
super(base)
}
// this is still a proposal at stage 3 to declare instance variables like this
// if u want a truly es6 way you can move them to the constructor
checkEmail = (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`
notifications = `${API_BASE_URL}${USERS}/notifications`
messages = `${API_BASE_URL}${USERS}/messages`
}
// you can use userEndpoints itself as a string everywhere a string is expected
const userEndpoints = new UsersEndpoints(API_BASE_URL)
export default {
users: userEndpoints
}
The previous is just actually equivalent to
...
const userEndpoints = new String(API_BASE_URL)
userEndpoints.notifications = `${API_BASE_URL}${USERS}/notifications`
...
Obviously this is not recommended: you should not extend native classes, there are many disadvantages to this approach.
An obvious example is that there could be a conflict between the properties you use and the properties that might be brought by the native class
override the toString method
...
export default {
users: {
checkEmail: (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`,
notifications: `${API_BASE_URL}${USERS}/notifications`,
messages: `${API_BASE_URL}${USERS}/messages`,
toString: () => API_BASE_URL
},
};
// this is actually not much different than the previous method, since a String is an objet with an overridden toString method.
// That said this method is also not recommended since toString is used in many places in native code, and overriding it just to substitute a string value will make information get lost in such places, error stacks for example
Achieve what u want using the language features intended for such a use case
What you are asking is to make the same variable to have different values in the same time, which is not possible in the language syntax, and it makes sense because it makes it hard to reason about code.
that being said i recommend something of the following nature
// it is also better to use named exports
export const getUsersEndpoint = ({
path = "",
dynamicEndpointPayload = {},
} = {}) => {
switch (path) {
case "notifications":
return `${API_BASE_URL}${USERS}/notifications`
case "messages":
return `${API_BASE_URL}${USERS}/messages`
case "checkEmail":
return `${API_BASE_URL}${USERS}/${dynamicEndpointPayload.email}/checkEmail`
// you still can do checkEmail like this, but the previous is more consistent
// case "checkEmail":
// return (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`
default:
return `${API_BASE_URL}`
}
}
// you can use it like this
getUsersEndpoint() // returns the base
getUsersEndpoint({path: 'notifications'})
You can extend prototype to achieve this behaviour:
export const API_BASE_URL = 'http://localhost:3000';
export const USERS = '/Users';
const users = `${API_BASE_URL}${USERS}`
const baseUrls = {
checkEmail: (email) => `${users}/${email}/checkEmail`,
notifications: `${users}/notifications`,
messages: `${users}/messages`,
}
Object.setPrototypeOf(users.__proto__, baseUrls);
export default {
users
};
Try having object will all user endpoint and a function that return a value of a end point
const user = {
default: '/users',
notification: '/notification',
profile: '/profile',
getEndPoint(prop) {
if(this[prop] === 'default' ){
return this[prop];
} else {
if(this[prop]) {
return this.default + this[prop];
}
}
}
}
So you can have more end points that come under user and you can simply call
const requestURL = api.user.getEndPoint('default'); // http://localhost:3000/Users
const requestURL2 = api.user.getEndPoint('notifications'); // http://localhost:3000/Users/notification
In TypeScript / Angular, you would usually call a function that returns an observable and subscribe to it in a component like this:
this.productsService.getProduct().subscribe((product) => { this.product = product });
This is fine when the code runs in a class that manages data, but in my opinion this should not be handled in the component. I may be wrong but i think the job of a component should be to ask for and display data without handling how the it is retrieved.
In the angular template you can do this to subscribe to and display the result of an observable:
<h1>{{ product.title | async }}</h1>
Is it possible to have something like this in the component class? My component displays a form and checks if a date is valid after input. Submitting the form is blocked until the value is valid and i want to keep all the logic behind it in the service which should subscribe to the AJAX call, the component only checks if it got a valid date.
class FormComponent {
datechangeCallback(date) {
this.dateIsValid$ = this.dateService.checkDate(date);
}
submit() {
if (this.dateIsValid$ === true) {
// handle form submission...
}
}
}
You can convert rxjs Observables to ES6 Promises and then use the async-await syntax to get the data without observable subscription.
Service:
export class DateService {
constructor(private http: HttpClient) {}
async isDateValid(date): Promise<boolean> {
let data = await this.http.post(url, date, httpOptions).toPromise();
let isValid: boolean;
// perform your validation and logic below and store the result in isValid variable
return isValid;
}
}
Component:
class FormComponent {
async datechangeCallback(date) {
this.dateIsValid = await this.dateService.isDateValid(date);
}
submit() {
if (this.dateIsValid) {
// handle form submission...
}
}
}
P.S:
If this is a simple HTTP request, which completes on receiving one value, then using Promises won't hurt. But if this obersvable produces some continuous stream of values, then using Promises isn't the best solution and you have to revert back to rxjs observables.
The cleanest way IMHO, using 7.4.0 < RxJS < 8
import { of, from, tap, firstValueFrom } from 'rxjs';
const asyncFoo = () => {
return from(
firstValueFrom(
of('World').pipe(
tap((foo) => {
console.info(foo);
})
)
)
);
};
asyncFoo();
// Outputs "World" once
asyncFoo().subscribe((foo) => console.info(foo));
// Outputs "World" twice
The "more cleanest" way would be having a factory (in some service) to build these optionally subscribeable function returns...
Something like this:
const buildObs = (obs) => {
return from(firstValueFrom(obs));
};
const asyncFoo = () => {
return buildObs(
of('World').pipe(
tap((foo) => {
console.info(foo);
})
)
);
};
I have been working on a chat app using Gifted-Chat and a Firebase RealTime database (and running it with Expo). At this point, the basic messaging works, but I am trying to enable to app to load earlier messages when the user scrolls up and hits the button that appears (I am aware of the GiftedChat prop for this). Unfortunately, I have been having trouble doing this and am a bit stumped.
There are two separate problems I have been running up against that I am aware of.
Clicking the loadEarlier button gives me an undefined is not a function (near '...this.setState...' runtime error (clearly, something is wrong with the skeleton function I put there).
The bigger issues is that I am still not clear on how to download the n number of messages before the oldest messages currently loaded. I have looked at the GiftedChat example and this post for help, but must confess that I am still lost (the best I can figure is that I need to sort the messages, possibly by timestamp, somehow get the right range, then parse them and prepend them to the messages array in state, but I cannot figure out how to do this, especially the later parts).
The relevant parts of the code for my chat screen are below, as is a screenshot of the structure of my firebase database. I would appreciate any help regarding both of these issues.
// Your run of the mill React-Native imports.
import React, { Component } from 'react';
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
import * as firebase from 'firebase';
// Our custom components.
import { Input } from '../components/Input';
import { Button } from '../components/Button';
import { BotButton } from '../components/BotButton';
// Array of potential bot responses. Might be a fancy schmancy Markov
// chain like thing in the future.
import {botResponses} from '../Constants.js';
// Gifted-chat import. The library takes care of fun stuff like
// rendering message bubbles and having a message composer.
import { GiftedChat } from 'react-native-gifted-chat';
// To keep keyboard from covering up text input.
import { KeyboardAvoidingView } from 'react-native';
// Because keyboard avoiding behavior is platform specific.
import {Platform} from 'react-native';
console.disableYellowBox = true;
class Chat extends Component {
state = {
messages: [],
isLoadingEarlier: false,
};
// Reference to where in Firebase DB messages will be stored.
get ref() {
return firebase.database().ref('messages');
}
onLoadEarlier() {
this.setState((previousState) => {
return {
isLoadingEarlier: true,
};
});
console.log(this.state.isLoadingEarlier)
this.setState((previousState) => {
return {
isLoadingEarlier: false,
};
});
}
// Get last 20 messages, any incoming messages, and send them to parse.
on = callback =>
this.ref
.limitToLast(20)
.on('child_added', snapshot => callback(this.parse(snapshot)));
parse = snapshot => {
// Return whatever is associated with snapshot.
const { timestamp: numberStamp, text, user } = snapshot.val();
const { key: _id } = snapshot;
// Convert timestamp to JS date object.
const timestamp = new Date(numberStamp);
// Create object for Gifted Chat. id is unique.
const message = {
_id,
timestamp,
text,
user,
};
return message;
};
// To unsubscribe from database
off() {
this.ref.off();
}
// Helper function to get user UID.
get uid() {
return (firebase.auth().currentUser || {}).uid;
}
// Get timestamp for saving messages.
get timestamp() {
return firebase.database.ServerValue.TIMESTAMP;
}
// Helper function that takes array of messages and prepares all of
// them to be sent.
send = messages => {
for (let i = 0; i < messages.length; i++) {
const { text, user } = messages[i];
const message = {
text,
user,
timestamp: this.timestamp,
};
this.append(message);
}
};
// Save message objects. Actually sends them to server.
append = message => this.ref.push(message);
// When we open the chat, start looking for messages.
componentDidMount() {
this.on(message =>
this.setState(previousState => ({
messages: GiftedChat.append(previousState.messages, message),
}))
);
}
get user() {
// Return name and UID for GiftedChat to parse
return {
name: this.props.navigation.state.params.name,
_id: this.uid,
};
}
// Unsubscribe when we close the chat screen.
componentWillUnmount() {
this.off();
}
render() {
return (
<View>
<GiftedChat
loadEarlier={true}
onLoadEarlier={this.onLoadEarlier}
isLoadingEarlier={this.state.isLoadingEarlier}
messages={this.state.messages}
onSend={this.send}
user={this.user}
/>
</View>
);
}
}
export default Chat;
For your first issue, you should declare your onLoadEarlier with => function so as to get the current instance this i.e. your code should look like below:
onLoadEarlier = () => {
this.setState((previousState) => {
return {
isLoadingEarlier: true,
};
}, () => {
console.log(this.state.isLoadingEarlier)
this.setState((previousState) => {
return {
isLoadingEarlier: false,
};
});
});
}
Also, setState is asynchronous in nature, so you should rather depend on the second parameter of the setState i.e. the callback to ensure that the next lines of code execute synchronously.
Lastly, if you are using class syntax then you should declare the state in constructor like below:
class Chat extends Component {
constructor (props) {
super (props);
state = {
messages: [],
isLoadingEarlier: false,
};
}
......
onLoadEarlier = () => {
this.setState((previousState) => {
return {
isLoadingEarlier: true,
};
}, () => {
console.log(this.state.isLoadingEarlier)
this.setState((previousState) => {
return {
isLoadingEarlier: false,
};
});
});
}
...
}
For loading the last messages from firebase , I recommend using limitToLast function on your reference. You should afterwards order the results by date before calling append in gifted chat.
For the second question, it should be the same with this question How Firebase on and once differ?
You can using filter feature in Firebase for example using createdAt field to compare with last loaded message to load more.
I am building an Angular 1.5 app using the component structure. After the promise comes back from the $http call in the service, I am trying to call another function to filter the dataset before it is displayed on the UI.
However, the filterApps function is not getting called.
Also...in the filterApps function I am trying to compare to arrays of objects and return back the ones that have the same name. Is this the best way to go about this or is there a cleaner way?
Controller :
import allApps from '../../resources/data/application_data.js';
class HomeController {
/*#ngInject*/
constructor(ItemsService) {
this.itemsService = ItemsService;
this.displayApps = [];
}
$onInit() {
this.itemsService
.getItems()
.success((apps) => this.filterApps(apps));
}
filterApps(siteApps) {
this.displayApps = allApps.applications.filter((app) => {
siteApps.applications.map((siteApp) => {
if(siteApp.name === app.name) {
return app;
}
})
});
}
}
export default HomeController;
I don't see any reason that filterApps method isn't geting call(as you already commented that success function is getting called). I guess you're just checking nothing has been carried in displayApps variable. The real problem is you have not return internal map function result to filter. So that's why nothing gets return.
Code
filterApps(siteApps) {
this.displayApps = allApps.applications.filter((app) => {
//returning map function result.
return siteApps.applications.map((siteApp) => {
if(siteApp.name === app.name) {
return app;
}
})
});
}