Enable auto-saving while searching for items
This means hoisting `useOutfitSaving` up to the top of the page! We had it down lower for iteration convenience to start :)
This commit is contained in:
parent
93bc960221
commit
3088b97ad2
4 changed files with 249 additions and 239 deletions
|
@ -21,7 +21,7 @@ import SearchPanel from "./SearchPanel";
|
|||
* performing some wiring to help them interact with each other via simple
|
||||
* state and refs.
|
||||
*/
|
||||
function ItemsAndSearchPanels({ loading, outfitState, dispatchToOutfit }) {
|
||||
function ItemsAndSearchPanels({ loading, outfitState, outfitSaving, dispatchToOutfit }) {
|
||||
const [searchQuery, setSearchQuery] = React.useState(emptySearchQuery);
|
||||
const scrollContainerRef = React.useRef();
|
||||
const searchQueryRef = React.useRef();
|
||||
|
@ -68,6 +68,7 @@ function ItemsAndSearchPanels({ loading, outfitState, dispatchToOutfit }) {
|
|||
<ItemsPanel
|
||||
loading={loading}
|
||||
outfitState={outfitState}
|
||||
outfitSaving={outfitSaving}
|
||||
dispatchToOutfit={dispatchToOutfit}
|
||||
/>
|
||||
</Box>
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
MenuItem,
|
||||
Portal,
|
||||
Button,
|
||||
useToast,
|
||||
Spinner,
|
||||
useColorModeValue,
|
||||
} from "@chakra-ui/react";
|
||||
|
@ -27,17 +26,12 @@ import {
|
|||
WarningTwoIcon,
|
||||
} from "@chakra-ui/icons";
|
||||
import { CSSTransition, TransitionGroup } from "react-transition-group";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import { Delay, Heading1, Heading2, useDebounce } from "../util";
|
||||
import { Delay, Heading1, Heading2 } from "../util";
|
||||
import Item, { ItemListContainer, ItemListSkeleton } from "./Item";
|
||||
import { BiRename } from "react-icons/bi";
|
||||
import { IoCloudUploadOutline } from "react-icons/io5";
|
||||
import { MdMoreVert } from "react-icons/md";
|
||||
import useCurrentUser from "../components/useCurrentUser";
|
||||
import gql from "graphql-tag";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { outfitStatesAreEqual } from "./useOutfitState";
|
||||
|
||||
/**
|
||||
* ItemsPanel shows the items in the current outfit, and lets the user toggle
|
||||
|
@ -52,7 +46,7 @@ import { outfitStatesAreEqual } from "./useOutfitState";
|
|||
* to have extra padding. Essentially: while the Items _do_ stretch out the
|
||||
* full width of the container, it doesn't look like it!
|
||||
*/
|
||||
function ItemsPanel({ outfitState, loading, dispatchToOutfit }) {
|
||||
function ItemsPanel({ outfitState, outfitSaving, loading, dispatchToOutfit }) {
|
||||
const { zonesAndItems, incompatibleItems } = outfitState;
|
||||
|
||||
return (
|
||||
|
@ -62,6 +56,7 @@ function ItemsPanel({ outfitState, loading, dispatchToOutfit }) {
|
|||
<Box px="1">
|
||||
<OutfitHeading
|
||||
outfitState={outfitState}
|
||||
outfitSaving={outfitSaving}
|
||||
dispatchToOutfit={dispatchToOutfit}
|
||||
/>
|
||||
</Box>
|
||||
|
@ -258,234 +253,11 @@ function ItemZoneGroupSkeleton({ itemCount }) {
|
|||
);
|
||||
}
|
||||
|
||||
function useOutfitSaving(outfitState, dispatchToOutfit) {
|
||||
const { isLoggedIn, id: currentUserId } = useCurrentUser();
|
||||
const history = useHistory();
|
||||
const toast = useToast();
|
||||
|
||||
// There's not a way to reset an Apollo mutation state to clear out the error
|
||||
// when the outfit changes… so we track the error state ourselves!
|
||||
const [saveError, setSaveError] = React.useState(null);
|
||||
|
||||
// Whether this outfit is new, i.e. local-only, i.e. has _never_ been saved
|
||||
// to the server.
|
||||
const isNewOutfit = outfitState.id == null;
|
||||
|
||||
// Whether this outfit's latest local changes have been saved to the server.
|
||||
// And log it to the console!
|
||||
const latestVersionIsSaved =
|
||||
outfitState.savedOutfitState &&
|
||||
outfitStatesAreEqual(
|
||||
outfitState.outfitStateWithoutExtras,
|
||||
outfitState.savedOutfitState
|
||||
);
|
||||
React.useEffect(() => {
|
||||
console.debug(
|
||||
"[useOutfitSaving] Latest version is saved? %s\nCurrent: %o\nSaved: %o",
|
||||
latestVersionIsSaved,
|
||||
outfitState.outfitStateWithoutExtras,
|
||||
outfitState.savedOutfitState
|
||||
);
|
||||
}, [
|
||||
latestVersionIsSaved,
|
||||
outfitState.outfitStateWithoutExtras,
|
||||
outfitState.savedOutfitState,
|
||||
]);
|
||||
|
||||
// Only logged-in users can save outfits - and they can only save new outfits,
|
||||
// or outfits they created.
|
||||
const canSaveOutfit =
|
||||
isLoggedIn && (isNewOutfit || outfitState.creator?.id === currentUserId);
|
||||
|
||||
const [sendSaveOutfitMutation, { loading: isSaving }] = useMutation(
|
||||
gql`
|
||||
mutation UseOutfitSaving_SaveOutfit(
|
||||
$id: ID # Optional, is null when saving new outfits.
|
||||
$name: String # Optional, server may fill in a placeholder.
|
||||
$speciesId: ID!
|
||||
$colorId: ID!
|
||||
$pose: Pose!
|
||||
$wornItemIds: [ID!]!
|
||||
$closetedItemIds: [ID!]!
|
||||
) {
|
||||
outfit: saveOutfit(
|
||||
id: $id
|
||||
name: $name
|
||||
speciesId: $speciesId
|
||||
colorId: $colorId
|
||||
pose: $pose
|
||||
wornItemIds: $wornItemIds
|
||||
closetedItemIds: $closetedItemIds
|
||||
) {
|
||||
id
|
||||
name
|
||||
petAppearance {
|
||||
id
|
||||
species {
|
||||
id
|
||||
}
|
||||
color {
|
||||
id
|
||||
}
|
||||
pose
|
||||
}
|
||||
wornItems {
|
||||
id
|
||||
}
|
||||
closetedItems {
|
||||
id
|
||||
}
|
||||
creator {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
context: { sendAuth: true },
|
||||
update: (cache, { data: { outfit } }) => {
|
||||
// After save, add this outfit to the current user's outfit list. This
|
||||
// will help when navigating back to Your Outfits, to force a refresh.
|
||||
// https://www.apollographql.com/docs/react/caching/cache-interaction/#example-updating-the-cache-after-a-mutation
|
||||
cache.modify({
|
||||
id: cache.identify(outfit.creator),
|
||||
fields: {
|
||||
outfits: (existingOutfitRefs = []) => {
|
||||
const newOutfitRef = cache.writeFragment({
|
||||
data: outfit,
|
||||
fragment: gql`
|
||||
fragment NewOutfit on Outfit {
|
||||
id
|
||||
}
|
||||
`,
|
||||
});
|
||||
return [...existingOutfitRefs, newOutfitRef];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Also, send a `reset` action, to show whatever the server returned.
|
||||
// This is important for suffix changes to `name`, but can also be
|
||||
// relevant for graceful failure when a bug causes a change not to
|
||||
// persist. (But don't do it if it's not the current outfit anymore,
|
||||
// we don't want laggy mutations to reset the outfit!)
|
||||
if (outfit.id === outfitState.id) {
|
||||
dispatchToOutfit({
|
||||
type: "resetToSavedOutfitData",
|
||||
savedOutfitData: outfit,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const saveOutfitFromProvidedState = React.useCallback(
|
||||
(outfitState) => {
|
||||
sendSaveOutfitMutation({
|
||||
variables: {
|
||||
id: outfitState.id, // Optional, is null when saving new outfits
|
||||
name: outfitState.name, // Optional, server may fill in a placeholder
|
||||
speciesId: outfitState.speciesId,
|
||||
colorId: outfitState.colorId,
|
||||
pose: outfitState.pose,
|
||||
wornItemIds: [...outfitState.wornItemIds],
|
||||
closetedItemIds: [...outfitState.closetedItemIds],
|
||||
},
|
||||
})
|
||||
.then(({ data: { outfit } }) => {
|
||||
// Navigate to the new saved outfit URL. Our Apollo cache should pick
|
||||
// up the data from this mutation response, and combine it with the
|
||||
// existing cached data, to make this smooth without any loading UI.
|
||||
history.push(`/outfits/${outfit.id}`);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setSaveError(e);
|
||||
toast({
|
||||
status: "error",
|
||||
title: "Sorry, there was an error saving this outfit!",
|
||||
description: "Maybe check your connection and try again.",
|
||||
});
|
||||
});
|
||||
},
|
||||
// It's important that this callback _doesn't_ change when the outfit
|
||||
// changes, so that the auto-save effect is only responding to the
|
||||
// debounced state!
|
||||
[sendSaveOutfitMutation, history, toast]
|
||||
);
|
||||
|
||||
const saveOutfit = React.useCallback(
|
||||
() => saveOutfitFromProvidedState(outfitState.outfitStateWithoutExtras),
|
||||
[saveOutfitFromProvidedState, outfitState.outfitStateWithoutExtras]
|
||||
);
|
||||
|
||||
// Auto-saving! First, debounce the outfit state. Use `outfitStateWithoutExtras`,
|
||||
// which only contains the basic fields, and will keep a stable object
|
||||
// identity until actual changes occur. Then, save the outfit after the user
|
||||
// has left it alone for long enough, so long as it's actually different
|
||||
// than the saved state.
|
||||
const debouncedOutfitState = useDebounce(
|
||||
outfitState.outfitStateWithoutExtras,
|
||||
2000,
|
||||
{
|
||||
// When the outfit ID changes, update the debounced state immediately!
|
||||
forceReset: (debouncedOutfitState, newOutfitState) =>
|
||||
debouncedOutfitState.id !== newOutfitState.id,
|
||||
}
|
||||
);
|
||||
// HACK: This prevents us from auto-saving the outfit state that's still
|
||||
// loading. I worry that this might not catch other loading scenarios
|
||||
// though, like if the species/color/pose is in the GQL cache, but the
|
||||
// items are still loading in... not sure where this would happen tho!
|
||||
const debouncedOutfitStateIsSaveable =
|
||||
debouncedOutfitState.speciesId &&
|
||||
debouncedOutfitState.colorId &&
|
||||
debouncedOutfitState.pose;
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
!isNewOutfit &&
|
||||
canSaveOutfit &&
|
||||
debouncedOutfitStateIsSaveable &&
|
||||
!outfitStatesAreEqual(debouncedOutfitState, outfitState.savedOutfitState)
|
||||
) {
|
||||
console.info(
|
||||
"[useOutfitSaving] Auto-saving outfit\nSaved: %o\nCurrent (debounced): %o",
|
||||
outfitState.savedOutfitState,
|
||||
debouncedOutfitState
|
||||
);
|
||||
saveOutfitFromProvidedState(debouncedOutfitState);
|
||||
}
|
||||
}, [
|
||||
isNewOutfit,
|
||||
canSaveOutfit,
|
||||
debouncedOutfitState,
|
||||
debouncedOutfitStateIsSaveable,
|
||||
outfitState.savedOutfitState,
|
||||
saveOutfitFromProvidedState,
|
||||
]);
|
||||
|
||||
// When the outfit changes, clear out the error state from previous saves.
|
||||
// We'll send the mutation again after the debounce, and we don't want to
|
||||
// show the error UI in the meantime!
|
||||
React.useEffect(() => {
|
||||
setSaveError(null);
|
||||
}, [outfitState.outfitStateWithoutExtras]);
|
||||
|
||||
return {
|
||||
canSaveOutfit,
|
||||
isNewOutfit,
|
||||
isSaving,
|
||||
latestVersionIsSaved,
|
||||
saveError,
|
||||
saveOutfit,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* OutfitSavingIndicator shows a Save button, or the "Saved" or "Saving" state,
|
||||
* if the user can save this outfit. If not, this is empty!
|
||||
*/
|
||||
function OutfitSavingIndicator({ outfitState, dispatchToOutfit }) {
|
||||
function OutfitSavingIndicator({ outfitSaving }) {
|
||||
const {
|
||||
canSaveOutfit,
|
||||
isNewOutfit,
|
||||
|
@ -493,7 +265,7 @@ function OutfitSavingIndicator({ outfitState, dispatchToOutfit }) {
|
|||
latestVersionIsSaved,
|
||||
saveError,
|
||||
saveOutfit,
|
||||
} = useOutfitSaving(outfitState, dispatchToOutfit);
|
||||
} = outfitSaving;
|
||||
|
||||
const errorTextColor = useColorModeValue("red.600", "red.400");
|
||||
|
||||
|
@ -586,7 +358,7 @@ function OutfitSavingIndicator({ outfitState, dispatchToOutfit }) {
|
|||
* OutfitHeading is an editable outfit name, as a big pretty page heading!
|
||||
* It also contains the outfit menu, for saving etc.
|
||||
*/
|
||||
function OutfitHeading({ outfitState, dispatchToOutfit }) {
|
||||
function OutfitHeading({ outfitState, outfitSaving, dispatchToOutfit }) {
|
||||
return (
|
||||
// The Editable wraps everything, including the menu, because the menu has
|
||||
// a Rename option.
|
||||
|
@ -612,10 +384,7 @@ function OutfitHeading({ outfitState, dispatchToOutfit }) {
|
|||
</Box>
|
||||
<Box width="4" flex="1 0 auto" />
|
||||
<Box flex="0 0 auto">
|
||||
<OutfitSavingIndicator
|
||||
outfitState={outfitState}
|
||||
dispatchToOutfit={dispatchToOutfit}
|
||||
/>
|
||||
<OutfitSavingIndicator outfitSaving={outfitSaving} />
|
||||
</Box>
|
||||
<Box width="2" />
|
||||
<Menu placement="bottom-end">
|
||||
|
|
|
@ -4,6 +4,7 @@ import { loadable } from "../util";
|
|||
|
||||
import ItemsAndSearchPanels from "./ItemsAndSearchPanels";
|
||||
import SupportOnly from "./support/SupportOnly";
|
||||
import useOutfitSaving from "./useOutfitSaving";
|
||||
import useOutfitState, { OutfitStateContext } from "./useOutfitState";
|
||||
import { usePageTitle } from "../util";
|
||||
import WardrobePageLayout from "./WardrobePageLayout";
|
||||
|
@ -26,6 +27,11 @@ function WardrobePage() {
|
|||
const toast = useToast();
|
||||
const { loading, error, outfitState, dispatchToOutfit } = useOutfitState();
|
||||
|
||||
// We manage outfit saving up here, rather than at the point of the UI where
|
||||
// "Saving" indicators appear. That way, auto-saving still happens even when
|
||||
// the indicator isn't on the page, e.g. when searching.
|
||||
const outfitSaving = useOutfitSaving(outfitState, dispatchToOutfit);
|
||||
|
||||
usePageTitle(outfitState.name || "Untitled outfit");
|
||||
|
||||
// TODO: I haven't found a great place for this error UI yet, and this case
|
||||
|
@ -64,6 +70,7 @@ function WardrobePage() {
|
|||
<ItemsAndSearchPanels
|
||||
loading={loading}
|
||||
outfitState={outfitState}
|
||||
outfitSaving={outfitSaving}
|
||||
dispatchToOutfit={dispatchToOutfit}
|
||||
/>
|
||||
}
|
||||
|
|
233
src/app/WardrobePage/useOutfitSaving.js
Normal file
233
src/app/WardrobePage/useOutfitSaving.js
Normal file
|
@ -0,0 +1,233 @@
|
|||
import React from "react";
|
||||
import { useToast } from "@chakra-ui/react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useDebounce } from "../util";
|
||||
import useCurrentUser from "../components/useCurrentUser";
|
||||
import gql from "graphql-tag";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { outfitStatesAreEqual } from "./useOutfitState";
|
||||
|
||||
function useOutfitSaving(outfitState, dispatchToOutfit) {
|
||||
const { isLoggedIn, id: currentUserId } = useCurrentUser();
|
||||
const history = useHistory();
|
||||
const toast = useToast();
|
||||
|
||||
// There's not a way to reset an Apollo mutation state to clear out the error
|
||||
// when the outfit changes… so we track the error state ourselves!
|
||||
const [saveError, setSaveError] = React.useState(null);
|
||||
|
||||
// Whether this outfit is new, i.e. local-only, i.e. has _never_ been saved
|
||||
// to the server.
|
||||
const isNewOutfit = outfitState.id == null;
|
||||
|
||||
// Whether this outfit's latest local changes have been saved to the server.
|
||||
// And log it to the console!
|
||||
const latestVersionIsSaved =
|
||||
outfitState.savedOutfitState &&
|
||||
outfitStatesAreEqual(
|
||||
outfitState.outfitStateWithoutExtras,
|
||||
outfitState.savedOutfitState
|
||||
);
|
||||
React.useEffect(() => {
|
||||
console.debug(
|
||||
"[useOutfitSaving] Latest version is saved? %s\nCurrent: %o\nSaved: %o",
|
||||
latestVersionIsSaved,
|
||||
outfitState.outfitStateWithoutExtras,
|
||||
outfitState.savedOutfitState
|
||||
);
|
||||
}, [
|
||||
latestVersionIsSaved,
|
||||
outfitState.outfitStateWithoutExtras,
|
||||
outfitState.savedOutfitState,
|
||||
]);
|
||||
|
||||
// Only logged-in users can save outfits - and they can only save new outfits,
|
||||
// or outfits they created.
|
||||
const canSaveOutfit =
|
||||
isLoggedIn && (isNewOutfit || outfitState.creator?.id === currentUserId);
|
||||
|
||||
const [sendSaveOutfitMutation, { loading: isSaving }] = useMutation(
|
||||
gql`
|
||||
mutation UseOutfitSaving_SaveOutfit(
|
||||
$id: ID # Optional, is null when saving new outfits.
|
||||
$name: String # Optional, server may fill in a placeholder.
|
||||
$speciesId: ID!
|
||||
$colorId: ID!
|
||||
$pose: Pose!
|
||||
$wornItemIds: [ID!]!
|
||||
$closetedItemIds: [ID!]!
|
||||
) {
|
||||
outfit: saveOutfit(
|
||||
id: $id
|
||||
name: $name
|
||||
speciesId: $speciesId
|
||||
colorId: $colorId
|
||||
pose: $pose
|
||||
wornItemIds: $wornItemIds
|
||||
closetedItemIds: $closetedItemIds
|
||||
) {
|
||||
id
|
||||
name
|
||||
petAppearance {
|
||||
id
|
||||
species {
|
||||
id
|
||||
}
|
||||
color {
|
||||
id
|
||||
}
|
||||
pose
|
||||
}
|
||||
wornItems {
|
||||
id
|
||||
}
|
||||
closetedItems {
|
||||
id
|
||||
}
|
||||
creator {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
context: { sendAuth: true },
|
||||
update: (cache, { data: { outfit } }) => {
|
||||
// After save, add this outfit to the current user's outfit list. This
|
||||
// will help when navigating back to Your Outfits, to force a refresh.
|
||||
// https://www.apollographql.com/docs/react/caching/cache-interaction/#example-updating-the-cache-after-a-mutation
|
||||
cache.modify({
|
||||
id: cache.identify(outfit.creator),
|
||||
fields: {
|
||||
outfits: (existingOutfitRefs = []) => {
|
||||
const newOutfitRef = cache.writeFragment({
|
||||
data: outfit,
|
||||
fragment: gql`
|
||||
fragment NewOutfit on Outfit {
|
||||
id
|
||||
}
|
||||
`,
|
||||
});
|
||||
return [...existingOutfitRefs, newOutfitRef];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Also, send a `reset` action, to show whatever the server returned.
|
||||
// This is important for suffix changes to `name`, but can also be
|
||||
// relevant for graceful failure when a bug causes a change not to
|
||||
// persist. (But don't do it if it's not the current outfit anymore,
|
||||
// we don't want laggy mutations to reset the outfit!)
|
||||
if (outfit.id === outfitState.id) {
|
||||
dispatchToOutfit({
|
||||
type: "resetToSavedOutfitData",
|
||||
savedOutfitData: outfit,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const saveOutfitFromProvidedState = React.useCallback(
|
||||
(outfitState) => {
|
||||
sendSaveOutfitMutation({
|
||||
variables: {
|
||||
id: outfitState.id,
|
||||
name: outfitState.name,
|
||||
speciesId: outfitState.speciesId,
|
||||
colorId: outfitState.colorId,
|
||||
pose: outfitState.pose,
|
||||
wornItemIds: [...outfitState.wornItemIds],
|
||||
closetedItemIds: [...outfitState.closetedItemIds],
|
||||
},
|
||||
})
|
||||
.then(({ data: { outfit } }) => {
|
||||
// Navigate to the new saved outfit URL. Our Apollo cache should pick
|
||||
// up the data from this mutation response, and combine it with the
|
||||
// existing cached data, to make this smooth without any loading UI.
|
||||
history.push(`/outfits/${outfit.id}`);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setSaveError(e);
|
||||
toast({
|
||||
status: "error",
|
||||
title: "Sorry, there was an error saving this outfit!",
|
||||
description: "Maybe check your connection and try again.",
|
||||
});
|
||||
});
|
||||
},
|
||||
// It's important that this callback _doesn't_ change when the outfit
|
||||
// changes, so that the auto-save effect is only responding to the
|
||||
// debounced state!
|
||||
[sendSaveOutfitMutation, history, toast]
|
||||
);
|
||||
|
||||
const saveOutfit = React.useCallback(
|
||||
() => saveOutfitFromProvidedState(outfitState.outfitStateWithoutExtras),
|
||||
[saveOutfitFromProvidedState, outfitState.outfitStateWithoutExtras]
|
||||
);
|
||||
|
||||
// Auto-saving! First, debounce the outfit state. Use `outfitStateWithoutExtras`,
|
||||
// which only contains the basic fields, and will keep a stable object
|
||||
// identity until actual changes occur. Then, save the outfit after the user
|
||||
// has left it alone for long enough, so long as it's actually different
|
||||
// than the saved state.
|
||||
const debouncedOutfitState = useDebounce(
|
||||
outfitState.outfitStateWithoutExtras,
|
||||
2000,
|
||||
{
|
||||
// When the outfit ID changes, update the debounced state immediately!
|
||||
forceReset: (debouncedOutfitState, newOutfitState) =>
|
||||
debouncedOutfitState.id !== newOutfitState.id,
|
||||
}
|
||||
);
|
||||
// HACK: This prevents us from auto-saving the outfit state that's still
|
||||
// loading. I worry that this might not catch other loading scenarios
|
||||
// though, like if the species/color/pose is in the GQL cache, but the
|
||||
// items are still loading in... not sure where this would happen tho!
|
||||
const debouncedOutfitStateIsSaveable =
|
||||
debouncedOutfitState.speciesId &&
|
||||
debouncedOutfitState.colorId &&
|
||||
debouncedOutfitState.pose;
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
!isNewOutfit &&
|
||||
canSaveOutfit &&
|
||||
debouncedOutfitStateIsSaveable &&
|
||||
!outfitStatesAreEqual(debouncedOutfitState, outfitState.savedOutfitState)
|
||||
) {
|
||||
console.info(
|
||||
"[useOutfitSaving] Auto-saving outfit\nSaved: %o\nCurrent (debounced): %o",
|
||||
outfitState.savedOutfitState,
|
||||
debouncedOutfitState
|
||||
);
|
||||
saveOutfitFromProvidedState(debouncedOutfitState);
|
||||
}
|
||||
}, [
|
||||
isNewOutfit,
|
||||
canSaveOutfit,
|
||||
debouncedOutfitState,
|
||||
debouncedOutfitStateIsSaveable,
|
||||
outfitState.savedOutfitState,
|
||||
saveOutfitFromProvidedState,
|
||||
]);
|
||||
|
||||
// When the outfit changes, clear out the error state from previous saves.
|
||||
// We'll send the mutation again after the debounce, and we don't want to
|
||||
// show the error UI in the meantime!
|
||||
React.useEffect(() => {
|
||||
setSaveError(null);
|
||||
}, [outfitState.outfitStateWithoutExtras]);
|
||||
|
||||
return {
|
||||
canSaveOutfit,
|
||||
isNewOutfit,
|
||||
isSaving,
|
||||
latestVersionIsSaved,
|
||||
saveError,
|
||||
saveOutfit,
|
||||
};
|
||||
}
|
||||
|
||||
export default useOutfitSaving;
|
Loading…
Reference in a new issue