Resource Picker not liking my array - React shopify app-bridge - javascript

So, i'm getting the selected items from the resource picker and trying to use it for initialSelectionIds.
However i'm getting this error when using it as in the beginning it is empty: I'm also not sure how to get selectedValue into the correct format.
error:
appBridgeError: APP::ERROR::INVALID_OPTIONS: `type_error_expected_object` thrown for path: ['initialSelectionIds']['0'] and value: `[]
const handleSelection = (resources) => {
const idsFromResources = resources.selection.map(v => "id:'" + v.id +'\'');
const namesFromResources = resources.selection.map(p => p.title);
selectedValue = JSON.parse(idsFromResources); <Page
title='Allergen Selector'
primaryAction={{
content: 'Select Allergens',
onAction: () => opend(),
}}>
<ResourcePicker
resourceType="Product"
showVariants={true}
open={active}
onSelection={(resources) => handleSelection(resources)}
onCancel={handleToggle}
initialSelectionIds={[selectedValue ] }
/>
</Page>
But it works when i use this:
const productWithAllVariantsSelected = {
id: 'gid://shopify/Product/6337793261759',
};
<ResourcePicker
resourceType="Product"
showVariants={true}
open={active}
onSelection={(resources) => handleSelection(resources)}
onCancel={handleToggle}
initialSelectionIds={[productWithAllVariantsSelected] }
/>

Related

How do I handle multiple query params in javascript?

I have a react app and I'm trying to use a table component.
This table component has multiple ways of filtering the table, and I want to be able to update the URL with these filters so that when I navigate away and go back, the table can read from the url params and have the table react accordingly.
I am able to get my table to already do something like this, but when working with multiple params, I'm a bit lost. My issue is that using history.replace or history.push` will overwrite the previous params, and then updating them will only just append more params when I want to simply replace them
Here's what I have working already:
const useQueryParams = () => {
const params = new URLSearchParams(useLocation().search);
const startingAfter = params.get('startingAfter');
const endingBefore = params.get('endingBefore');
const status = params.get('status');
return { params, startingAfter, endingBefore, status };
}
export const MyComponent = () => {
const history = useHistory();
const location = useLocation();
const { params, startingAfter, endingBefore, status } = useQueryParams();
const [statusFilter, setStatusFilter] = useState<IStatus | string>(
Status.Pending
);
const [dateFilter, setDateFilter] = useState<{
appointmentStartingAfter?: string;
appointmentEndingBefore?: string;
}>({
appointmentStartingAfter: undefined,
appointmentEndingBefore: undefined,
});
useEffect(() => {
if (startingAfter || endingBefore) {
setDateFilter({
appointmentStartingAfter: moment(startingAfter).toISOString() || undefined,
appointmentEndingBefore: moment(endingBefore).toISOString() || undefined,
});
}
if (status) {
setStatusFilter(status);
}
}, [startingAfter, endingBefore, status]);
// ......
const handleDateChange = (momentDateRange) => {
const startingAfter = momentDateRange[0].subtract(1, 'day');
const endingBefore = momentDateRange[1].add(1, 'day');
history.replace({
search: `startingAfter=${startingAfter.format('YYYY-MM-DD')}&endingBefore=${endingBefore.format('YYYY-MM-DD')}`,
});
setDateFilter({
appointmentStartingAfter: startingAfter.toISOString() || undefined,
appointmentEndingBefore: endingBefore.toISOString() || undefined,
});
};
const handleStatusFilter = (value: string) => {
history.replace({
search: `status=${value || 'ALL_DOCUMENTS'}`,
});
setStatusFilter(value);
};
return (
<>
// ......
<DatePicker.RangePicker defaultValue={initialDates} style={{ marginRight: 20 }} onChange={handleDateChange} />
<CareDocumentStatusFilter value={statusFilter} onChange={handleStatusFilter} />
</>
);
}
I'm able to get the URLs working with history.replace or history.push but it will overwrite each other when I want them to remain persistent.
For example,
history.replace({
search: `startingAfter=${startingAfter.format('YYYY-MM-DD')}&endingBefore=${endingBefore.format('YYYY-MM-DD')}`,
});
// localhost:3000/xyz?startingAfter=2021-08-06&endingBefore=2021-08-19
But then in handleStatusFilter
history.replace({
search: `status=${value || 'ALL_DOCUMENTS'}`,
});
// localhost:3000/xyz?status=CREATED
In the example above, status will overwrite the params when the intended URL should look something like:
localhost:3000/xyz?startingAfter=2021-08-06&endingBefore=2021-08-19&status=CREATED
in my different attempts, when I want to update this, instead of changing the params, the params just seem to append more and end up looking like this:
localhost:3000/xyz?startingAfter=2021-08-06&endingBefore=2021-08-19&status=CREATED?startingAfter=xxxx&endingBefore=yyyy&status=zzzz
I do have react-router-dom available to be used as well.
So I interestingly stumbled upon this answer and it seemed to solve my issue. I believe using URLSearchParams.set was the key here. I don't entirely super understand the inner workings... but it works! Here is my solution:
const handleDateChange = (momentDateRange) => {
const startingAfter = momentDateRange[0].subtract(1, 'day');
const endingBefore = momentDateRange[1].add(1, 'day');
// This was important!
params.set('startingAfter', startingAfter.format('YYYY-MM-DD'));
params.set('endingBefore', endingBefore.format('YYYY-MM-DD'));
history.push({
// and so was this ...?
pathname: location.pathname,
search: params.toString(),
});
setDateFilter({
appointmentStartingAfter: startingAfter.toISOString() || undefined,
appointmentEndingBefore: endingBefore.toISOString() || undefined,
});
};
const handleStatusFilter = (value: string) => {
// This was important!
params.set('status', value || 'ALL_DOCUMENTS');
history.push({
// ... and so was this?
pathname: location.pathname,
search: params.toString(),
})
setStatusFilter(value);
};

Vue async call REST API in Vuex store

My async actions do not run correctly. Im new to Vue and JS and I am not sure what is happening here. I placed some confirm() dialogs within my code, to see which line passed and which not.
Within the ScanView.vue I call my addProduct action. I get the confirm dialog saying "addProduct" and dispatch calles the next callAPI action where I get the "callAPI" confirm dialog but nothing more. Seems like fetch() isnt working at all, because no any other dialog is shown. What am I doing wrong?
ScanView.vue
export default defineComponent({
name: "Home",
methods: {
scanEan() {
// QR Code Scanner Logic
this.$store.dispatch("addProduct", ean);
}
});
main.js
const store = new Vuex.Store({
state: {
products: [{
name: 'Produkt',
ean: '123',
amount: '1',
smallImageUrl: 'smImage',
mediumImageUrl: 'mdImage',
largeImageUrl: 'lgImage',
expiration: []
}]
},
mutations: {
addProduct(state, product) {
state.products.unshift(product);
}
},
actions: {
addProduct(context, ean) {
confirm("addProduct: " + ean);
context.dispatch('callAPI', ean);
},
callAPI(context, ean) {
confirm("callAPI: ");
fetch("https://world.openfoodfacts.org/api/v0/product/" + ean + ".json") //
.then(response => {
confirm("reesponse");
return response.json();
}
) //
.then(data => {
confirm("data: " + data);
context.dispatch('saveProduct', data);
});
},
saveProduct(context, data) {
confirm("saveProduct: ");
const name = data.product.product_name;
const ean = data.code;
const smImage = data.product.image_front_thumb_url;
const mdImage = data.product.image_front_small_url;
const lgImage = data.product.image_front_url;
const expiration = new Array();
const date = new Date(data.product.expiration_date);
expiration.push(date);
const product = new Product(
name,
ean,
smImage,
mdImage,
lgImage,
expiration
)
confirm("Produktdata: " + product);
context.commit('addProduct', product);
}
}
});
app.use(store);
EDIT
I build a simulate button for better testing. QR Scanning does not work in Browser.
Result It does work in Browser. But not on my emulator or android device. Seems like fetch() isnt the right way with ionic-vue. If I catch the error I got TypeError: Failed to fetch...
<template>
<button #click="simulateScan">Simulate Scan</button>
</template>
<script>
export default {
methods: {
simulateScan() {
this.$store.dispatch('addProduct', 737628064502);
}
}
};
</script>
Final Solution
fetch() does not work on android. You have to use something like cordova-http, capacitor-http, ionic-http or else. I used capacitorcommunity-http.
npm install #capacitor-community/http
npx cap sync
import { Http } from '#capacitor-community/http';
[...]
callAPI(context, ean) {
var eanurl = "https://world.openfoodfacts.org/api/v0/product/" + ean + ".json";
Http.get({ url: eanurl}) //
.then(response => {
return response.data;
}
) //
.then(data => {
console.log(data);
context.dispatch('saveProduct', data);
}).catch(error => confirm(error));
},
[...]

Issue in throttling ajax requests using vue-multiselect and lodash

I have a vue app containing a vue-multiselect and I want to load the multiselect options through ajax. I am using lodash.throttle to throttle the firing of ajax requests as the user types in the search criteria. But whatever I do, I am seeing multiple requests being fired for each character I type in search. What I am doing wrong? Thanks in advance.
<template>
<multiselect :options="allLocations.map(p => p.externalId)"
:searchable="true"
:custom-label="uuid => {const sel = allLocations.filter(s => s.externalId === uuid); return sel.length === 1 ? sel[0].name + ' (' + sel[0].type + ')' : '';}"
class="mx-1 my-1"
style="width:500px;"
v-model="locations"
:clear-on-select="true"
:close-on-select="false"
:show-labels="true"
placeholder="Pick Locations to filter"
:multiple="true"
:loading="locationsLoading"
:internal-search="false"
#search-change="findLocations"
#input="updateLocations"
:allowEmpty="true" />
</template>
<script>
import {throttle} from 'lodash'
export default {
name: 'test-throttle-component',
data() {
allLocations: [],
locationsLoading: false,
locations: [],
},
methods: {
findLocations(search) {
this.$log.debug("Going to find locations for search criteria", search)
const params = {search: search}
this.locationsLoading = true
const self = this
throttle(() => self.$http.get("locations/ddlist", {params}).then(res => {
self.allLocations = res.data.items
self.locationsLoading = false
}), 5000)()
},
updateLocations() {
const self = this
this.$store.dispatch('updateSelectedLocations', this.locations)
.then(() => self.emitRefresh())
},
}
}
</script>
#strelok2010 is nearly right but I think he overlooked the fact that the vue multiselect #search-change handler expects a handler that takes the search argument and so the code won't work as is. Also I think, this won't resolve to the component inside the arrow function and so you may have to use standard JS function. Here is what I think would work.
findLocations: throttle(function(search) {
this.$log.debug("Going to find locations for search criteria", search)
const params = {search: search}
const self = this
this.locationsLoading = true
self.$http.get("locations/ddlist", {params}).then(res => {
self.allLocations = res.data.items
self.locationsLoading = false
}
}, 5000)
Try to wrap findLocations method to throttle function:
findLocations: throttle(() => {
this.$log.debug("Going to find locations for search criteria", search)
const params = {search: search}
const self = this
this.locationsLoading = true
self.$http.get("locations/ddlist", {params}).then(res => {
self.allLocations = res.data.items
self.locationsLoading = false
}
}, 5000)
More info here

Tcomb-form-native: How to add new options to a form dynamically

I have this form in my component :
this.state.customFieldsArray.length > 0 &&
(
<Form
ref="customForm"
type={this.customForm}
options={this.customFormOptions}
/>
)}
I want to add more options to the form (so it render more fields) when i click on a button.
This is the methode that handle the click on the button:
addCustomField = () => {
let newFieldName = this.state.customFieldLabel;
let newField = { newFieldName: "" };
this.customFormOptions.fields[newFieldName] = {
label: newFieldName
};
tempCustomFieldsArray.push(newField);
this.setState({
customFieldsArray: tempCustomFieldsArray
});
};
i have tried this but it didn't work.
this code works for me, create struct form model that call function, and user your condition there
formModel: t.struct({
customFields: this.getCustomFields(),
}),
then you create customFields function like,
getCustomFields = () => {
const customFields = {}
//write your condition here
return t.enums(customFields)
}

Rx timer state not updating in view in Cycle.js

I'm starting a timer when someone clicks a button that I intend to use as the opacity for some element. When I use do to trace the value I can see it spitting out to the console 40 times, but in the view the number stays put. Not sure where I'm going wrong here:
let intent = ({ DOM }) => ({
clickLogin$: DOM.select('.sign-in').events('click').map(ev => true)
})
let model = ({ clickLogin$ }) =>
Rx.Observable.combineLatest(
clickLogin$.startWith(false),
clickLogin$.map(x =>
Rx.Observable.timer(1, 1)
).switch().startWith(0).take(40),
(signingIn, fadeValue) => ({ signingIn, fadeValue })
)
let view = (state$) => {
return state$.do(
x => console.log(x.fadeValue)) // this fires |--1-2-3-4-5-6-7-8-->
.map(({ signingIn, fadeValue }) =>
div(`.app`, [
div([fadeValue]), // this value does not change
If(signingIn,
div(`.overlay`, {
style: {
backgroundColor: `rgba(0, 0, 0, 0.${fadeValue})` // nor does this
}
})
)
])
)
}
let main = (sources) => {
let view$ = view(model(intent(sources)))
return {
DOM: view$,
history: sources.History,
Props: sources.Props,
}
}
UPDATE: Turns out having a small error in hyperscript caused it strange behaviour. I didn't even include it in my example because I didn't think it was relevant.
div(`content`, [ `testing` ])
Simply changing the above to (adding indication of class)
div(`.content`, [ `testing` ])
Caused everything to magically work.
This is probably not a full answer, but it helps identifying the problem. I removed the If part of the view code generation, and added repeat, put that in tricycle and you can see that the fadeValue is generated sequentially as expected.
var Cycle = require('#cycle/core');
var CycleDOM = require('#cycle/dom');
var Rx = require('rx');
var makeDOMDriver = CycleDOM.makeDOMDriver;
var div = CycleDOM.div;
var sources = {
DOM: makeDOMDriver('.app')
};
let main = (sources) => {
let intent = ({ DOM }) => ({
clickLogin$: Rx.Observable.interval(3000).take(5).share()
})
let model = ({ clickLogin$ }) =>
Rx.Observable.combineLatest(
clickLogin$.startWith(false),
clickLogin$.flatMapLatest(function (x) {
return Rx.Observable.timer(200, 200);
}).take(10).repeat(),
(signingIn, fadeValue) => ({ signingIn, fadeValue })
)
let view = (state$) => {
return state$.do(
x => console.log(x.fadeValue)) // this fires |--1-2-3-4-5-6-7-8-->
.map(({ signingIn, fadeValue }) =>
div(`.app`, [
div([fadeValue]) // this value does not change
])
)
}
let view$ = view(model(intent(sources)))
return {
DOM: view$,
history: sources.History,
Props: sources.Props,
}
}

Categories