AlpineJS can not use magic method $watch - events

Having the following AlpineJS code and trying to use the magic method $watch, the code will fail with ReferenceError: $watch is not defined
window.experts = {
apiUrl: 'http://test.local:8991/api/',
data: [],
list: [],
expertsForm: null,
expertType: 'all',
queryUrl: '',
currentPage: 1,
sortByName: 'asc',
sortByZip: 'asc',
q: '',
fetchStatus: 'loading...',
retrieveList: () => {
const membersUrl = `${experts.apiUrl}members?include=user,association,affiliate`;
$watch('specialistType', (value) => console.log(value) );
experts.apiCalls(membersUrl)
},
setExpertType: (type) => {
console.log(type)
},
apiCalls: (url) => {
const response = fetch(url).then(res => {
if (!res.ok) {
experts.fetchStatus = 'error'
}
return res.json()
}).then(result => {
experts.list = result.data;
experts.data = result;
experts.fetchStatus = 'idle'
});
}
}
what goes wrong in this case?

Try accessing it via this. So it should be this.$watch(value, callback).

You should not use arrow function and add this
retrieveList(){
const membersUrl = `${experts.apiUrl}members?include=user,association,affiliate`;
this.$watch('specialistType', (value) => console.log(value) );
experts.apiCalls(membersUrl)
},

Related

how do i update array of objects in Zustand?

i am trying to update the masterSteps variable inside the store but the store is undefined that's why it is saying MasterSteps is undefined but on similar way the setRoute method is working and routekey variable is getting set
const useStore = create((set) => ({
...initialState,
routeKey: "",
MasterSteps: [],
setRoute: (routeKeyToUse) => {
set((state) => {
state.routeKey = routeKeyToUse;
})
},
setMasterSteps: (o) => {
set((state) => {
state.MasterSteps= [...state.MasterSteps, o ]
})},
}));
NOTE : the o as parameter in setMasterSteps is having data
i am expecting how to setState the array in zustand store
Setter function should return new state, not imperativerly update it.
Try:
o => {
set((state) => ({ MasterSteps: [...state.MasterSteps, o ] }))
}
Edit:
Can you try:
const useStore = create((set) => ({
...initialState,
routeKey: "",
MasterSteps: [],
setRoute: (routeKeyToUse) => {
set((state) => ({
routeKey: routeKeyToUse;
}))
},
setMasterSteps: (o) => {
set((state) => ({
MasterSteps: [...state.MasterSteps, o ]
}))},
}));
Also what is definition of initialData? If it provides value for MasterSteps, it's overwritten by later MasterSteps = [].

react-hooks/exhaustive-deps inconsistent warning when object destructuring

I use the useHistory hook but I am receiving the exhaustive-deps warning when I don't destructure the object.
const history = useHistory();
useEffect(() => {
history.replace({ search: '' });
}, [history.replace]);
const change = useCallback(() => {
history.replace({ search: '' });
}, [history.replace]);
I receive the following warning:
React Hook useCallback has a missing dependency: 'history'. Either include it or remove the dependency array
But, if I do something like this, it works:
const { replace } = useHistory();
useEffect(() => {
replace({ search: '' });
}, [replace]);
const change = useCallback(() => {
replace({ search: '' });
}, [replace]);

selector returning different value despite custom equalityFn check returning true

I have the following selector and effect
const filterValues = useSelector<State, string[]>(
state => state.filters.filter(f => f.field === variableId).map(f => f.value),
(left, right) => {
return left.length === right.length && left.every(l => right.includes(l));
},
);
const [value, setValue] = useState<SelectionRange>({ start: null, end: null });
useEffect(() => {
const values = filterValues
.filter(av => av).sort((v1, v2) => v1.localeCompare(v2));
const newValue = {
start: values[0] ?? null,
end: values[1] ?? null,
};
setValue(newValue);
}, [filterValues]);
the selector above initially returns an empty array, but a different one every time and I don't understand why because the equality function should guarantee it doesn't.
That makes the effect trigger, sets the state, the selector runs again (normal) but returns another different empty array! causing the code to run in an endless cycle.
Why is the selector returning a different array each time? what am I missing?
I am using react-redux 7.2.2
react-redux e-runs the selector if the selector is a new reference, because it assumes the code could have changed what it's selecting entirely
https://github.com/reduxjs/react-redux/issues/1654
one solution is to memoize the selector function
const selector = useMemo(() => (state: State) => state.filters.filter(f => f.field === variableId).map(f => f.value), [variableId]);
const filterValues = useSelector<State, string[]>(
selector ,
(left, right) => {
return left.length === right.length && left.every(l => right.includes(l));
},
);
You can try memoizing the result of your filter in a selector and calculate value in a selector as well, now I'm not sure if you still need the local state of value as it's just a copy of a derived value from redux state and only causes an extra render when you copy it but here is the code:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector, defaultMemoize } = Reselect;
const { useState, useEffect, useMemo } = React;
const initialState = {
filters: [
{ field: 1, value: 1 },
{ field: 2, value: 2 },
{ field: 1, value: 3 },
{ field: 2, value: 4 },
],
};
//action types
const TOGGLE = 'NEW_STATE';
const NONE = 'NONE';
//action creators
const toggle = () => ({
type: TOGGLE,
});
const none = () => ({ type: NONE });
const reducer = (state, { type }) => {
if (type === TOGGLE) {
return {
filters: state.filters.map((f) =>
f.field === 1
? { ...f, field: 2 }
: { ...f, field: 1 }
),
};
}
if (type === NONE) {
//create filters again should re run selector
// but not re render
return {
filters: [...state.filters],
};
}
return state;
};
//selectors
const selectFilters = (state) => state.filters;
const createSelectByVariableId = (variableId) => {
const memoArray = defaultMemoize((...args) => args);
return createSelector([selectFilters], (filters) =>
memoArray.apply(
null,
filters
.filter((f) => f.field === variableId)
.map((f) => f.value)
)
);
};
const createSelectSelectValue = (variableId) =>
createSelector(
[createSelectByVariableId(variableId)],
//?? does not work in SO because babel is too old
(values) => ({
start: values[0] || null,
end: values[1] || null,
})
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
var last;
const App = ({ variableId }) => {
const selectValue = useMemo(
() => createSelectSelectValue(variableId),
[variableId]
);
const reduxValue = useSelector(selectValue);
if (last !== reduxValue) {
console.log('not same', last, reduxValue);
last = reduxValue;
}
//not sure if you still need this, you are just
// copying a value you already have
const [value, setValue] = useState(reduxValue);
const dispatch = useDispatch();
useEffect(() => setValue(reduxValue), [reduxValue]);
console.log('rendering...', value);
return (
<div>
<button onClick={() => dispatch(toggle())}>
toggle
</button>
<button onClick={() => dispatch(none())}>none</button>
<pre>{JSON.stringify(value, undefined, 2)}</pre>
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App variableId={1} />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>

How to get data from failed forkJoin request?

Using Angular Rxjs and ngrx
I have an action that dispatch 4 API and I am doing the following =>
#Effect()
getAllModels$ = this.actions$.pipe(
ofType<featureActions.GetAllModelsRequest>(featureActions.ActionTypes.GetAllModelsRequest),
switchMap((action) =>
forkJoin([
this.dataService.GetAllModelFromServer(),
this.dataService.GetAllModelFromHost(),
this.dataService.GetAllModelFromCache(),
this.dataService.GetAllModelFromPreference(),
]).pipe(
map(
([server, host, cache, preference]) =>
new featureActions.GetAllModelsSuccess({
//...
})
),
catchError((error: HttpErrorResponse) => {
return of(new featureActions.GetAllModelsFailed({ error: error.message }));
})
)
)
);
The problem is, when one of those API fail, everything fail and I am in fail action. all the data that got retrieved (before the one endpoint that failed) is lost.
Is there a way to get the data retrieved in the catchError or the only solution is to chain the api one after the other ?
You can write your own implementation of forkJoin. Here is a simple example sourced from the original (https://github.com/ReactiveX/rxjs/blob/master/src/internal/observable/forkJoin.ts):
export function forkJoin2(...args: any[]): Observable<any> {
const resultSelector = popResultSelector(args);
const { args: sources, keys } = argsArgArrayOrObject(args);
if (resultSelector) {
// deprecated path.
return forkJoinInternal(sources, keys).pipe(map((values: any[]) => resultSelector!(...values)));
}
return forkJoinInternal(sources, keys);
}
function forkJoinInternal(sources: ObservableInput<any>[], keys: string[] | null): Observable<any> {
return new Observable((subscriber) => {
const len = sources.length;
if (len === 0) {
subscriber.complete();
return;
}
const values = new Array(len);
let completed = 0;
let emitted = 0;
for (let sourceIndex = 0; sourceIndex < len; sourceIndex++) {
const source = innerFrom(sources[sourceIndex]);
let hasValue = false;
subscriber.add(
source.subscribe({
next: (value) => {
if (!hasValue) {
hasValue = true;
emitted++;
}
values[sourceIndex] = value;
},
error: (err) => { return subscriber.error({ error: err, values }) },
complete: () => {
completed++;
if (completed === len || !hasValue) {
if (emitted === len) {
subscriber.next(keys ? keys.reduce((result, key, i) => (((result as any)[key] = values[i]), result), {}) : values);
}
subscriber.complete();
}
},
})
);
}
});
}
Notice, when an error occurs, you are returning the error along with the values:
error: (err) => { return subscriber.error({ error: err, values }) }
I went with this solution found here : https://medium.com/better-programming/rxjs-error-handling-with-forkjoin-3d4027df70fc
#Effect()
getAllModels$ = this.actions$.pipe(
ofType<featureActions.GetAllModelsRequest>(featureActions.ActionTypes.GetAllModelsRequest),
switchMap((action) =>
forkJoin([
this.dataService.GetAllModelFromServer().pipe(catchError(() => of({ data: [] }))),
this.dataService.GetAllModelFromHost().pipe(catchError(() => of({ data: [] }))),
this.dataService.GetAllModelFromCache().pipe(catchError(() => of({ data: [] }))),
this.dataService.GetAllModelFromPreference().pipe(catchError(() => of({ data: [] }))),
]).pipe(
map(
([server, host, cache, preference]) =>
new featureActions.GetAllModelsSuccess({
//...
})
),
catchError((error: HttpErrorResponse) => {
return of(new featureActions.GetAllModelsFailed({ error: error.message }));
})
)
)
);

Laravel vue stripe: how to pass client_secret PaymentIntent from clientside to serverside?

I'm using stripe with laravel and vue js. Stripe support told me that I have to implent the paymentIntent function. All the code works fine, the problem is that on the server side I have to pass the client_secre and I dont know how to do it...
Here's the code...
SERVER SCRIPT
\Stripe\Stripe::setApiKey('MY_KEY');
try {
\Stripe\PaymentIntent::create([
'currency' => 'EUR',
'amount' => $request->amount * 100,
'description' => 'Donazione',
'metadata' => [
'customer' => $request->name,
'integration_check' => 'accept_a_payment'
]
]);
CLIENT SIDE SCRIPT
import { Card, createToken } from 'vue-stripe-elements-plus'
export default {
components: { Card },
data () {
return {
complete: false,
errorMessage: '',
stripeOptions: {
// see https://stripe.com/docs/stripe.js#element-options for details
style: {
base: {
color: '#32325d',
lineHeight: '18px',
fontFamily: '"Raleway", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
},
hidePostalCode: true
}
}
},
methods: {
pay () {
//createToken().then(data => console.log(data.token))
// Instead of creatToken I have to use confirmCardPayment() and pass the client_secret
},
change(event) {
// if (event.error) {
// this.errorMessage = event.error.message;
// } else {
// this.errorMessage = ''
// }
this.errorMessage = event.error ? event.error.message : ''
}
}
}
I recently had to set this up in my platform and here is how I did it. I created a controller called:
PaymentIntentController.php
Stripe::setApiKey(env('STRIPE_SECRET'));
$payment_intent = PaymentIntent::create([
'payment_method_types' => ['card'],
'amount' => $request->invoice['total'] * 100,
'currency' => $this->currency($request),
'receipt_email' => $request->invoice['clients_email']
],
[
'stripe_account' => $request->user['stripe_user_id']
]);
return $payment_intent;
On the client-side, you need to have an Axios request hit this controller so you can get the payment_intent.
Like this:
loadPaymentIntent () {
axios.post('/api/stripe/connect_payment_intent', {'invoice': this.invoice, 'user': this.user}).then((response) => {
this.paymentIntent = response.data
})
},
I have my payment intent setup to load when a checkout form is displayed. Then when the form is submitted we have access to the payment_intent which we can use in the confirmCardPayment method like such:
submit () {
let self = this
self.isLoading = true
self.stripe.confirmCardPayment(self.paymentIntent.client_secret, {
return_url: self.returnUrl + `/clients/${self.invoice.client_id}/invoices/${self.invoice.id}`,
receipt_email: self.invoice.clients_email,
payment_method: {
card: self.card,
billing_details: {
name: self.formData.name,
}
}
}).then(function(result) {
if (result.error) {
self.isLoading = false
self.cardError.status = true
self.cardError.message = result.error.message
setTimeout(() => {
self.cardError = {}
}, 3000)
} else {
if (result.paymentIntent.status === 'succeeded') {
self.handleInvoice(result.paymentIntent)
self.closeModal()
setTimeout(() => {
location.href = self.returnUrl + `/clients/${self.invoice.client_id}/invoices/${self.invoice.id}?success=true`
}, 1000)
}
}
});
},

Resources