I can successfully upload to firebase storage and retrieve image URL using promises, but I wanted to link a progress bar to the percent completion of the upload. What I have achieved thus far: When I call this function from a component:
this.props.handleNewPrizeImageUpload(this.progressCallback, this.imageUploadCompletionCallback, prizeImage)
with these callbacks defined in the component:
progressCallback (progress) {
**//this.someOtherFunction(progress).bind(this)**
return console.log('Upload is ' + progress + '% done')
}
imageUploadCompletionCallback (url) {
**//this.props.someOtherFunctionB(url)**
console.log('SAVEPRIZEIMAGE RAN SUCCESFULLY RETURN URL : ', url)}
this function runs:
export function handleNewPrizeImageUpload (progressCallback, imageUploadCompletionCallback,
prizeImage) {
savePrizeImage(progressCallback, imageUploadCompletionCallback,prizeImage)
}
The savePrizeImage is a function that save the image and runs callback back functions accordingly.
I can successfully retrieve the progress value and the URL Data but the Problem I am facing is that I can't use my other defined function within those callbacks to do something the retrieved Data, I keep getting the error that this is not defined. I have tried bind(this) it does not work.
error:
Uncaught TypeError: Cannot read property 'someOtherFunction' of
undefined
What I have tried:
constructor (props) {
super(props)
this.someOtherFunction = this.someOtherFunction.bind(this)
}
and then calling it like so:
progressCallback (progress) {
console.log('Upload is ' + progress + '% done')
this.someOtherFunction(progress)
}
someOtherFunction (progress) {
console.log('HAHAHA')
}
Heer is the Entire component Code Block:
import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import {CreatePrizeForm} from 'components'
import * as ActionCreators from 'redux/modules/prizes'
export class CreatePrizeFormContainer extends Component {
static contextTypes = {
router: PropTypes.object.isRequired
}
constructor (props) {
super(props)
// this.someOtherFunction = this.someOtherFunction.bind(this)
// this.progressCallback = this.progressCallback.bind(this)
}
handlePrizeData (prizeImage) {
this.props.handleNewPrizeImageUpload(this.progressCallback, this.imageUploadCompletionCallback, PrizeImage)
}
progressCallback (progress) {
console.log('Upload is ' + progress + '% done')
//this.someOtherFunction(progress)
}
imageUploadCompletionCallback (url) {
console.log('SAVE TO FIREBASE RAN SUCCESFULLY RETURNED IMAGE URL : ', url)
}
someOtherFunction (progress) {
console.log('HAHAHA')
}
render () {
return (
<div>
<CreatePrizeForm addPrizeData = {(prizeImage) => { this.handlePrizeData(prizeImage) }}/>
</div>
)
}
}
function mapStateToProps (state, props) {
return {
}
}
function mapDispatchToProps (dispatch, props) {
return bindActionCreators(ActionCreators, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(CreatePrizeFormContainer)
When you pass a reference to a function (e.g. as argument, or other assignment), it will not have its context bound to it.
This is the case with the this.progressCallback argument:
this.props.handleNewPrizeImageUpload(this.progressCallback, ... )
That progressCallback argument variable is not linked to this, even though you pass it like that -- which is the cause of a lot of misunderstanding; you are not the only one.
Solve it as follows:
this.props.handleNewPrizeImageUpload(this.progressCallback.bind(this), ... )
See the very good Q&A on this.
Here is some of your code trimmed down to a working snippet:
"strict"
// Dummy Component class for this snippet only
class Component { constructor (props) { this.props = props } }
// Your class:
class CreatePrizeFormContainer extends Component {
constructor (props) { super(props) }
handlePrizeData (prizeImage) {
this.props.handleNewPrizeImageUpload(
this.progressCallback.bind(this),
this.imageUploadCompletionCallback.bind(this), prizeImage)
}
progressCallback (progress) {
console.log('Upload is ' + progress + '% done')
this.someOtherFunction(progress)
}
imageUploadCompletionCallback (url) {
console.log('SAVE TO FIREBASE RAN SUCCESFULLY RETURNED IMAGE URL : ', url)
}
someOtherFunction (progress) { console.log('HAHAHA') }
}
// Dummy savePrizeImage function for this snippet only
function savePrizeImage(progressCallback, imageUploadCompletionCallback, ImageFile) {
// Simulate two progress events
setTimeout(function() { progressCallback(50) }, 0)
setTimeout(function() { progressCallback(100) }, 500)
setTimeout(function() { imageUploadCompletionCallback('http://example.com') }, 510)
}
// Create instance, passing simple prop literal
var obj = new CreatePrizeFormContainer({
handleNewPrizeImageUpload: function(progressCallback,
imageUploadCompletionCallback, ImageFile) {
savePrizeImage(progressCallback, imageUploadCompletionCallback, ImageFile)
}
})
obj.handlePrizeData('dummyPrizeImage.img') // Call handlePrizeData
Related
I have component MyComponent.vue where I have data value that constantly changes. I want to pass this value to javascript file(js file should know about changes of value everytime)
Why do I do that? Because my regular js file is a service layer for axios methods. I can import this file in many other components. The file contains axios methods and urls are dynamic.
I want those urls depend on data variable. This data variable comes from MyComponent.js
So the main goal is to make dynamic urls of axios that depend on data variable
I tried some code but it doesn't work, because js file(CategoryService.js) know nothing about this.categoryNumber.
MyComponent.vue:
<script>
export default {
data() {
return {
categoryNumber: 1
}
}
}
</script>
CategoryService.js
import http from "../../../http-common";
let category = "category1";
if (this.categoryNumber === 1) {
category = "category1";
} if (this.categoryNumber === 2) {
category = "category2";
}
class CategoryService {
get(id) {
return http.get(`/${category}/${id}`);
}
update(id, data) {
return http.put(`/${category}/${id}`, data);
}
create(data) {
return http.post(`/${category}`, data);
}
delete(id) {
return http.delete(`/${category}/${id}`);
}
getAll() {
return http.get(`/${category}/all`);
}
}
export default new CategoryService();
So with a bit of refactoring, you could easily get this working.
First of all, I would put the if/else logic of your class into it.
For convenience and scalability, I would use a Vuex store that will keep track of your categoryNumber and share it accross all your components.
Then I would bind my service to my Vue instance so I can easily access it in all my components as well as the store and I would pass the latter to my class as a parameter.
For the last part, I don't know the logic in the http-common file so the code I will show you is a bit nasty. But depending on wether or not you bound 'http' to axios, you could make use of axios interceptors to call the getCategoryNumber() method in every request.
Here's an idea of the implementation I would go for:
const CategoryService = class CategoryService {
constructor(store) {
this._store = store;
this.category = "category1";
}
getCategoryNumber() {
if (this._store.state.categoryNumber === 1) {
this.category = "category1";
}
if (this._store.state.categoryNumber === 2) {
this.category = "category2";
}
console.log(this.category); // for demo puprose
}
get(id) {
this.getCategoryNumber(); // We could use axios request interceptor instead of calling that in every route, but that works !
return http.get(`/${this.category}/${id}`);
}
update(id, data) {
this.getCategoryNumber();
return http.put(`/${this.category}/${id}`, data);
}
create(data) {
this.getCategoryNumber();
return http.post(`/${this.category}`, data);
}
delete(id) {
this.getCategoryNumber();
return http.delete(`/${this.category}/${id}`);
}
getAll() {
this.getCategoryNumber();
return http.get(`/${this.category}/all`);
}
}
const store = new Vuex.Store({
state: {
categoryNumber: 1
},
mutations: {
setCategoryNumber(state, payload) {
state.categoryNumber = payload;
}
}
});
// Bind your service to the Vue prototype so you can easily use it in any component with 'this.$service'
// pass it the store instance as parameter
Vue.prototype.$service = new CategoryService(store);
new Vue({
el: "#app",
store, // dont forget to bind your store to your Vue instance
methods: {
updateCategoryNumber() {
// Put here any logic to update the number
this.categoryNumber = this.categoryNumber === 1 ? 2 : 1;
this.checkServiceCategoryValue();
},
checkServiceCategoryValue() {
// for demonstration purpose
this.$service.getCategoryNumber();
}
},
computed: {
// Look for the store value and update it
categoryNumber: {
get() {
return this.$store.state.categoryNumber;
},
set(value) {
this.$store.commit("setCategoryNumber", value);
}
}
}
});
<div id="app">
<h2>number: {{ categoryNumber }}</h2>
<button type="button" #click="updateCategoryNumber()">
updateCategoryNumber
</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex#2.0.0"></script>
Thanks to #Solar
I just added one more parameter for all urls and put the number of category to it
CategoryService.js:
class CategoryOneService {
get(id, category) {
return http.get(`/${category}/${id}`);
}
getAll(category) {
return http.get(`/${category}/all`);
}
}
functions.js:
let catNum = "";
function getQuestion() {
if (this.categoryNumber === 1) {
catNum = "category1";
}
if (this.categoryNumber === 2) {
catNum = "category2";
}
let questionId = this.questionNumber;
CategoryOneService.get(questionId, catNum)
.then(response => {
this.question = response.data.question;
this.answer = response.data.answer;
})
.catch(error => {
console.log(error);
});
}
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)
});
I initiate loading scans through this action:
export function loadPickingScans (orderReference) {
return { type: SCANNING_LOAD_SCANS, orderReference };
}
It's called in my smart (page) component:
componentDidMount() {
const { loadPickingScans } = this.props;
loadPickingScans(this.props.match.params.orderReference);
}
This is the url:
enter code herehttp://localhost:3000/orders/my-order-reference/scans
this.props.match.params.orderReference correctly contains my-order-reference.
However, adding a log to my action, orderReference is received as undefined.
What should I do to receive this expected value?
Update
By request:
function mapDispatchToProps(dispatch) {
return {
loadPickingScans: () => dispatch(loadPickingScans())
};
}
In mapDispatchToProps, while dispatching the action, you haven't passed any argument to it and hence it logs undefined in the method, you need to write it like
function mapDispatchToProps(dispatch) {
return {
loadPickingScans: (value) => dispatch(loadPickingScans(value))
};
}
or simply
const mapDispatchToProps = {
loadPickingScans
}
I'm having trouble trying to read from apollo cache from within a react component the mutation works and writes to my server and returns the data but when passed to my update function it seems to lose the context of this when in inMemoryCache.js
"apollo-cache-inmemory": "^1.2.5"
"react-apollo": "^2.1.4"
"apollo-boost": "^0.1.7"
TypeError: Cannot read property 'read' of undefined
at ./node_modules/apollo-cache-inmemory/lib/inMemoryCache.js.InMemoryCache.readQuery
import React, { Component } from "react";
import { graphql } from "react-apollo";
import trim from "lodash/trim";
import AuthorForm from '../components/author-form';
import ALL_AUTHORS from "../graphql/getPosts.query";
import CREATE_AUTHOR from "../graphql/createAuthor.mutation";
class CreateAuthor extends Component {
state = {
errors: false
};
onSubmit(event) {
event.preventDefault();
const form = new FormData(event.target);
const data = {
firstName: form.get("firstName"),
lastName: form.get("lastName")
};
if (!data.firstName || !data.lastName) {
return this.setState({ errors: true });
}
this.create({
firstName: trim(data.firstName),
lastName: trim(data.lastName)
});
}
async create(variables) {
const { createAuthor } = this.props;
this.setState({ errors: false });
try {
await createAuthor({
variables,
update: (cache, data) => this.updateCache(cache, data)
})
} catch (e) {
this.setState({ errors: true })
}
}
updateCache({ readQuery, writeQuery }, { data: { createAuthor }, errors }) {
if (errors) {
return;
}
const { allAuthors } = readQuery({
query: ALL_AUTHORS,
defaults: {
allAuthors: []
}
});
/*eslint-disable*/ console.log(allAuthors);
}
render() {
return (
<div>
<AuthorForm onSubmit={this.onSubmit.bind(this)}/>
<OnError/>
</div>
);
}
}
export default graphql(CREATE_AUTHOR, { name: "createAuthor" })(CreateAuthor);
is it to do with me binding this to the onSubmit button? if so what is the proper way to attach a function to a element without losing context of this within the component and still allow apollo cache to function properly.
I was losing the context of this because I was deconstructing the first argument. this is what I settled on in the end.
It throw errors when there were no allAuthors on the ROOT_QUERY object so added it to my return statement.
this doesn't feel like an ideal way of updating the cache shouldn't default param passed to readQuery prevent the error thrown.
updateCache(cache, { data: { createAuthor }, errors }) {
if (errors || !cache.data.data.ROOT_QUERY.allAuthors) {
return;
}
const query = ALL_AUTHORS;
const { allAuthors } = cache.readQuery({
query,
defaults: {
allAuthors: []
}
});
const data = {
allAuthors: allAuthors.concat([createAuthor])
};
cache.writeQuery({
query,
data
});
}
I ran into an issue with Angular 2 using TypeScript that I could use an extra set of eyes on. I am requesting a token from an API which works great. In my response handler I am checking for basic errors and displaying them to the end users. If I log out the error and my message from the console it displays correctly but the view/template does not update.
In my class I have the following:
public message: string;
In my constructor I have:
constructor() {
this.message = 'Message to enduser';
}
My two methods are the following:
myRequest() {
(<any>window).privateAPI.getToken({
token: this.tmpData.token
}, this.responseHandler);
return false;
}
responseHandler(response: any) {
setTimeout(function() {
if (response.error) {
// this.message update is not updating in the template
this.message = response.error.message;
console.log('error: ', this.message);
} else {
// success
}
}, 100);
}
Any assistance would be greatly appreciated.
I was able to solve this issue by utilizing ngZone. The following resolves my problem with my component not updating my template in the API response.
// import NgZone
import {Component, NgZone} from '#angular/core';
// pass NgZone to constructor
constructor(private _zone: NgZone) {
this.message = 'Message to enduser';
}
requestToken() {
(<any>window).privateAPI.getToken({
token: this.tmpData.token
}, (status: number, response: any) => {
this._zone.run(() => {
if (response.error) {
this.message = response.error.message;
} else {
// good to go
}
});
});
}
This is because you created a new context within setTimeout. The keyword function does this automatically. In TypeScript you can use lambdas (under Lambdas and using this), also called arrow-functions. When using a lambda, it will automatically capture the this available when the function is created rather than when it is invoked.
Try this:
setTimeout(() => {
if (response.error) {
// this.message update is not updating in the template
this.message = response.error.message;
console.log('error: ', this.message);
} else {
// success
}
}, 100);