1
0
Fork 0
metamask-extension/ui/hooks/useEventFragment.js

101 lines
3.7 KiB
JavaScript

import { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { getEnvironmentType } from '../../app/scripts/lib/util';
import { selectMatchingFragment } from '../selectors';
import {
finalizeEventFragment,
createEventFragment,
updateEventFragment,
} from '../store/actions';
import { useSegmentContext } from './useSegmentContext';
/**
* Retrieves a fragment from memory or initializes new fragment if one does not
* exist. Returns three methods that are tied to the fragment, as well as the
* fragment id.
*
* @param {string} existingId
* @param {object} fragmentOptions
* @returns
*/
export function useEventFragment(existingId, fragmentOptions = {}) {
// To prevent overcalling the createEventFragment background method a ref
// is used to store a boolean value of whether we have already called the
// method.
const createEventFragmentCalled = useRef(false);
// In order to immediately return a created fragment, instead of waiting for
// background state to update and find the newly created fragment, we have a
// state element that is updated with the fragmentId returned from the
// call into the background process.
const [createdFragmentId, setCreatedFragmentId] = useState(undefined);
// Select a matching fragment from state if one exists that matches the
// criteria. If an existingId is passed in it is preferred, if not and the
// fragmentOptions has the persist key set to true, a fragment with matching
// successEvent will be pulled from memory if it exists.
const fragment = useSelector((state) =>
selectMatchingFragment(state, {
fragmentOptions,
existingId: existingId ?? createdFragmentId,
}),
);
// If no valid existing fragment can be found, a new one must be created that
// will then be found by the selector above. To do this, invoke the
// createEventFragment method with the fragmentOptions and current sessionId.
// As soon as we call the background method we also update the
// createEventFragmentCalled ref's current value to true so that future calls
// are suppressed.
useEffect(() => {
if (fragment === undefined && createEventFragmentCalled.current === false) {
createEventFragmentCalled.current = true;
createEventFragment({
...fragmentOptions,
environmentType: getEnvironmentType(),
}).then((createdFragment) => {
setCreatedFragmentId(createdFragment.id);
});
}
}, [fragment, fragmentOptions]);
const context = useSegmentContext();
/**
* trackSuccess is used to close a fragment with the affirmative action. This
* method is just a thin wrapper around the background method that sets the
* necessary values.
*/
const trackSuccess = useCallback(() => {
finalizeEventFragment(fragment.id, { context });
}, [fragment, context]);
/**
* trackFailure is used to close a fragment as abandoned. This method is just a
* thin wrapper around the background method that sets the necessary values.
*/
const trackFailure = useCallback(() => {
finalizeEventFragment(fragment.id, { abandoned: true, context });
}, [fragment, context]);
/**
* updateEventFragmentProperties is a thin wrapper around updateEventFragment
* that supplies the fragment id as the first parameter. This function will
* be passed back from the hook as 'updateEventFragment', but is named
* updateEventFragmentProperties to avoid naming conflicts.
*/
const updateEventFragmentProperties = useCallback(
(payload) => {
updateEventFragment(fragment.id, payload);
},
[fragment],
);
return {
trackSuccess,
trackFailure,
updateEventFragment: updateEventFragmentProperties,
fragment,
};
}