I'm trying to create a payment screen in React using Recurly.js. I'm using the official React library and am trying to hook into the set.plan event to retrieve plan information. I previously did this without issue using vanilla Recurly.js in Angular, but I haven't been able to do so in React and it's not clear to me if I'm doing something wrong or if there's a bug.
I adapted one of the Interactive Demo scenarios to demonstrate this. Please see the comments added in my sandbox:
const [{ price, loading }, setPricing, checkoutPricing] = useCheckoutPricing(
null,
setRecurlyError
);
useEffect(() => {
/*
Should this be called frequently? I know the price state gets updated, but I would not
expect to constantly receive a new/updated instance of checkoutPricing.
*/
console.log("checkoutPricing updated", checkoutPricing);
/*
This never gets called even after selecting a new plan in the dropdown menu
*/
checkoutPricing.on("set.plan", (plan) => {
console.log("plan updated", plan);
});
}, [checkoutPricing]);
Am I getting a new instance of checkoutPricing on each render? That might explain why my event is never firing, but I'm not sure how to avoid that.
Related
I use react-gtm-module (version 2.0.11) to create Google Tag Manager for my ReactJs app.
My problem is that each time a component renders, the function TagManager.initialize runs again. This creates duplicate GTM scripts (as attached).
I have to get gtm-ID from other data which changes many times. Each time this data changes, my component renders.
Is there a way to avoid the function TagManager.initialize run again if my GTM is already initialized?
My code:
useEffect(() => {
if (data.gtm_id){
TagManager.initialize({
gtmId: data.gtm_id,
});
}
}, [data.gtm_id])
Thank you so much.
I've been playing around with React and Azure App Insights.
const appInsights = useAppInsightsContext();
For Events and Metrics only, there seems to be 2 ways of doing things. Why is this? And why is it only for these 2 things only ie for PageViews or exceptions you can only use the second way (appInsights.trackPageView, appInsights.trackException)
//first way
const trackEventHook = useTrackEvent(
appInsights,
"AppInsightsPage Track Event Hook",
{ extraData: "some extra data important to this" },
false
);
trackEventHook({ extraData: "function call extra data" });
//2nd way
appInsights.trackEvent({ name: "AppInsightsPage Custom Event" }, undefined);
While using Application Insight, we use TrackEvent in our code to count various events. How often users choose a particular feature or maybe how often they make particular choices.
For Example, we want to understand the user behavior on a site and we want to know about specific actions like clicking the Add to Cart button.
This can be done by two ways :
Using trackEvent Method
appInsights.trackEvent({ name: 'EventName', properties: { anyProperty } })
We use appInsights object that we are exporting and pass some data to trackEvent, the name of the event we are tracking and any custom properties we want to include in the event.
Using React Plugin useTrackEvent Hook
const trackEventName = useTrackEvent(appInsights, "Event Name", condition);
The useTrackEvent Hook is used to track any custom event that an application may need to track, such as a button click or other API call. It takes four arguments:
Application Insights instance (which can be obtained from the useAppInsightsContext Hook).
Name for the event.
Event data object that encapsulates the changes that has to be tracked.
skipFirstRun (optional) flag to skip calling the trackEvent call on initialization. Default value is set to true.
trackExpection is used to log exception which are related to API, we don't know when they will happen and for trackPageView, page view telemetry is sent by default when each screen or page is loaded. So, in trackExpection and trackPageView we don't have any data object to track any changes. That's why we don't use useTrackEvent hook for this two.
For more information please check the following Microsoft Documents:
React Plugins for Application Insights.
Application Insight API.
I am new to RxJs in general but am investigating a bug in some React code in which, upon an unrelated action, an old event seems to be emitted and rendered to a display error. Think if you had two buttons that generated two messages somewhere on screen, and clicking one button was showing the message for the other button.
Being new to RxJs I'm not positive where the problem lays. I don't see a single ReplaySubject in the code, only Obserables, Subjects, and BehaviourSubjects. So this is either misuse of an RxJs feature or just some bad logic somewhere.
Anyway I found the code with the related Observable and I'm not quite sure what this person was trying to accomplish here. I have read up on combineLatest, map, and pipe, but this looks like pointless code to me. Could it also be somehow re-emitting old events? I don't see dynamic subscriptions anywhere, especially in this case.
Tldr I don't understand the intent of this code.
export interface IFeedback {
id: number
text: string
}
export interface IFeedbackMessages {
message: IFeedback | undefined
}
feedback$ = new BehaviorSubject<IFeedback | undefined>(undefined)
feedbackNotifs$: Observable<IFeedbackMessages> = combineLatest([
feedback$
]).pipe(
map(([feedback]) => ({
feedback
})
))
I also found this which maybe be an issue. In the React component that displays this message, am I wrong but does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
const FeedbackDisplay: React.FC () => {
const [feedbackNotifications, setFeedbackNotifications] = React.useState<IFeedbackMessages>()
React.useEffect(() =>
{
const sub = notification$.subscribe(setFeedbackNotifications)
return () => sub?.unsubscribe()
}, [notifications$])
}
Could it also be somehow re-emitting old events?
Yes, it probably is. BehaviorSubject has the unique property of immediately emitting the last value pushed to it as soon as you subscribe to it.
It's great when you want to model some persistent state value, and it's not good for events whose actual moment of occurrence is key. It sounds like the feedback messages you're working with fall into the second category, in which case Subject is probably a better choice.
does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
Not exactly. useEffect accepts a callback, and within that callback you can optionally return a "cleanup" function. React will hang onto that function until the effect is triggered again, then it calls it to clean things up (which in this case consists of closing out the subscription) to make room for the next effect.
So in this case, the unsubscribe will only happen when the component is rendered with a new value for notifications$. Also worth pointing out that notifications$ will only change if it's either passed as a prop or created within the component function. If it's defined outside the function (imported from another file for example), you don't need to (and in fact should not) put it into useEffect's dependency array.
I'm using react-apollo as a client to communicate with a GraphQL server that I created. I managed to successfully get subscriptions working with the data.subscribeToMore() function as detailed in the Apollo documentation and the up-to-date data shows up when I run my web application inside of two windows. What I'm trying to do is make it so that an notification alert gets displayed when another client changes data that I'm currently looking at so that I can tell that something changed in case I wasn't paying attention? What would be the correct way of doing this?
update method?
updateQueries method?
The dataFromObjectId and refetchQueries fields did not seem relevant for what I was trying to do. Since I'm using redux, is there a way I could dispatch actions directly from my subscription? Would notification alerts be something that I have to use client.subscribe() with?
Assuming you're using the latest version of Apollo, you should be handing the component a prop named "updateQuery" that contains logic for handling the data.
http://dev.apollodata.com/react/subscriptions.html#subscribe-to-more
This section goes over what you need to do, but essentially your "updateQuery" function should do the following:
Take in an object of structure argumentName.data which contains the new information.
Adds the new object to the results by creating a new object.
Returns the new results object.
so it might look something like this:
(prev, { subscriptionData }) => {
if (!subscription.data) {
//If no new data, return old results
return prev;
}
var newResults = Object.assign(
{},
prev,
queryName: { [subscriptionData.data, ...prev[queryName]] }
);
return newResults;
I have the following code in my render method:
render() {
return (
<div>
{this.props.spatulaReady.ready() ? <p>{this.props.spatula.name}</p> : <p>loading spatula</p>}
</div>
)
}
Which according to my understanding, checks if the subscriptionhandle is ready (data is there) and displays it. If no data is available, it should display a simple loading message. However, when I first load the page this snippet is on, it get's stuck on the loading part. On a page reload the data (usually) displays fine.
If I check the spatulaReady.ready() when the page first loads and while the display is stuck on 'loading spatula', and the data that should be there, the handle reports as ready and the data is there like it is supposed to be. If I refresh the page it all displays fine as well. The problem is, this way of checking for data and rendering if it has arrived has worked fine for me in the past. Is it because the render method is not reactive? Because handle.ready() should be reactive.
What makes it even weirder is that it sometimes DOES correctly display the data on page load, seemingly at random.
CreateContainer code:
export default createContainer(props => {
return {
user: Meteor.user(),
spatulaReady: Meteor.subscribe('spatula.byId', props.deviceId),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
Publication code:
Meteor.publish('spatula.byId', function(deviceId) {
const ERROR_MESSAGE = 'Error while obtaining spatula by id'
if (!this.userId) //Check for login
throw new Meteor.Error('Subscription denied!')
const spatula = SpatulaCollection.findOne({_id: deviceId})
if(!spatula) //No spatula by this id
throw new Meteor.Error(403, ERROR_MESSAGE)
if(spatula.ownedBy != this.userId) //Spatula does not belong to this user
throw new Meteor.Error(403, ERROR_MESSAGE)
return SpatulaCollection.find({_id: deviceId})
})
I know I'm missing a piece of the puzzle here, but I've been unsuccessful at finding it. If you don't know the solution to my specific problem, pointing me in the right direction with another way of waiting for the data to arrive before displaying it is also greatly appreciated.
EDIT: After doing some trial-and-error and reading various other posts somewhat related to my project, I figured out the solution:
export default createContainer(props => {
const sHandle= Meteor.subscribe('spatula.byId', props.deviceId)
return {
user: Meteor.user(),
spatulaReady: sHandle.ready(),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
It still makes no sense to me that moving the ready() call to create container fixed all my problems though.
As you figured out, moving the .ready() call to createContainer fixes the problem. This is because Meteor reactivity only works when you call a reactive data source (a reactive function), such as collection.find() or subscriptionHandle.ready() within a reactive context, such as Tracker.autorun or createContainer. Functions within the React component, including render, are not reactive contexts from Meteor's perspective.
Note that React and Meteor reactivity are two different things. React's reactivity works simply so that whenever a component's props or state change, it's render function is re-run. It does not understand anything about Meteor's reactive data sources. Since createContainer (that is re-run by Meteor reactivity when reactive data sources in it change) simply passes props to the underlying component, the component is re-rendered by React when the props given from createContainer change.