-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from murageh/refactor#strict-typing
refactor#data saving and retrieval logic
- Loading branch information
Showing
9 changed files
with
714 additions
and
180 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
// Export each component individually, as a named export. | ||
export { default as AutoSaveForm } from "./utilities/AutoSaveForm"; | ||
export {AutoSaveForm} from "./utilities/AutoSaveFormikForm"; | ||
export {default as AutoSaveFormikForm} from "./utilities/AutoSaveFormikForm"; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import {useFormikContext} from 'formik'; | ||
import React from 'react'; | ||
import {useProgressProps} from "../../hooks/saveProgress/useProgress"; | ||
import {useFormProgress} from "../../hooks"; | ||
|
||
export interface AutoSaveFormProps<T extends {}> extends useProgressProps<T> { | ||
children?: React.ReactNode | Element; | ||
} | ||
|
||
/** | ||
* This component is a wrapper for the `useFormProgress` custom hook. | ||
* It is used to save the form data when the user changes the form data. | ||
* This is useful for forms that are long, and the user may not want to click the save button. | ||
* | ||
* Note: This requires a valid Formik context. | ||
* Thus, this component will only work if there is a parent Formik React Context from which it can pull from. | ||
* If called without a parent context (i.e., a descendant of a `<Formik>` component or `withFormik` higher-order component), | ||
* you will get a warning in your console. | ||
* For more details regarding this, visit the [Formik documentation](https://formik.org/docs/api/useFormikContext). | ||
* | ||
* Note: The `values` from Formik will be prioritized over `initialValues`. | ||
* | ||
* @template T - The type of the form values. | ||
* @param {AutoSaveFormProps<T>} props - The properties for the component. | ||
* @param {string} props.dataKey - The dataKey to identify the stored values. | ||
* @param {T} [props.initialValues] - The initial values for the form. | ||
* @param {Storage} [props.storage] - The storage to use (localStorage or sessionStorage). Defaults to localStorage. | ||
* @param {(values: T) => void} [props.saveFunction] - Custom function to save values. If provided, this will be used instead of the storage. | ||
* @param {(values?: T) => void} [props.clearFunction] - Custom function to clear values. Crucial for custom save functions. | ||
* @param {boolean} [props.forceLocalActions] - Whether to force local storage actions even if custom functions are provided. Could be useful for debugging. | ||
* @param {React.ReactNode} [props.children] - The children of the form. | ||
* @returns {React.ReactNode} - Returns the children or null. | ||
*/ | ||
const AutoSaveFormikForm = React.memo( | ||
function AutoSaveFormikForm<T extends {}>(props: AutoSaveFormProps<T>) { | ||
const [initialized, setInitialized] = React.useState(false); | ||
const { values, setValues } = useFormikContext<T>(); | ||
const { dataKey, storage = localStorage, saveFunction, clearFunction, forceLocalActions, children } = props; | ||
const [_, updateValues] = useFormProgress<T>({ | ||
dataKey: dataKey, | ||
storage, | ||
saveFunction, | ||
clearFunction, | ||
forceLocalActions, | ||
}); | ||
|
||
React.useEffect(() => { | ||
const savedValues = storage.getItem(dataKey); | ||
if (savedValues) { | ||
const parsedValues = JSON.parse(savedValues); | ||
setValues((prevValues) => ({ ...prevValues, ...parsedValues })); | ||
} | ||
setInitialized(true); | ||
}, [dataKey, setValues, storage]); | ||
|
||
React.useEffect(() => { | ||
if (!initialized) return; | ||
try { | ||
updateValues(values); | ||
} catch (error) { | ||
console.warn("Error saving form data. Please check the save function. If the error persists, please contact the developer."); | ||
console.log(error); | ||
} | ||
}, [initialized, values, updateValues]); | ||
|
||
return <>{children || null}</>; | ||
}); | ||
|
||
/** | ||
* @deprecated Use {@link AutoSaveFormikForm} instead. | ||
* The naming was unclear regarding the Formik context, | ||
* so this function was renamed to {@link AutoSaveFormikForm}. | ||
* This function may be removed in the next major release. | ||
*/ | ||
export const AutoSaveForm = AutoSaveFormikForm; | ||
|
||
export default AutoSaveFormikForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/* export hooks */ | ||
|
||
export {useSaveProgress} from './saveProgress/useProgress'; | ||
export {default as useProgress} from './saveProgress/useProgress'; | ||
export {useProgress, useSaveProgress} from './saveProgress/useProgress'; | ||
export {default as useFormProgress} from './saveProgress/useProgress'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,99 +1,107 @@ | ||
import React from 'react'; | ||
|
||
export interface useProgressProps<T extends {}> { | ||
key: string; | ||
dataKey: string; | ||
initialValues?: T; | ||
storage?: Storage; | ||
saveFunction?: (values: T) => void; | ||
clearFunction?: (values?: T) => void; | ||
forceLocalActions?: boolean; | ||
fetchInitialValues?: () => Promise<T>; | ||
} | ||
|
||
/** | ||
* If you are using `useSaveProgress`, please migrate to `useProgress` instead. | ||
* Custom hook to save and manage the progress of user input, such as form data. | ||
* This hook ensures that user input is preserved even if the user navigates away from the page. | ||
* | ||
* This is a custom hook that is used to save the progress of the user's input, | ||
* for instance, if the user is filling out a form, and they leave the page, | ||
* the next time they come back, the form will be filled out with the data | ||
* they had previously entered. | ||
* | ||
* | ||
* Typical usage: | ||
* Use this hook to save the progress of the user's input, for instance, if the user is filling out a form, and they leave the page, | ||
* the next time they come back, the form will be filled out with the data they had previously entered. | ||
* | ||
* The {@link AutoSaveForm} component is a helper component that will automatically save the data when the form is submitted. | ||
* The {@link AutoSaveForm} requires a saveFunction prop, which is the setValues function returned by this hook, or any other function. | ||
* | ||
* @param key The key to use to save the data in local storage | ||
* @param initialValue The initial value to use if there is no data in local storage | ||
* @param storage The storage to use, defaults to local storage | ||
* @param saveFunction A function to save the data to a server. If this is provided, the data will not be saved locally. You can use this function to customize the save logic. | ||
* @param clearFunction A function to clear the data from a server. If this is provided, the data will not be cleared locally. You can use this function to customize the clear logic. | ||
* @param forceLocalActions If true, the data will be saved locally even if a saveFunction is provided. This is useful if you want to save the data locally, in addition to saving it to a server. This also applies to the clearFunction | ||
* | ||
* @returns [values, setValues, clearValues] The data, a function to update the data, and a function to clear the data | ||
*/ | ||
function useProgress<T extends {}>({ | ||
key, | ||
initialValues = {} as T, | ||
storage, | ||
saveFunction, | ||
clearFunction, | ||
forceLocalActions = false | ||
}: useProgressProps<T>) { | ||
* @template T - The type of the form values. | ||
* @param {Object} props - The properties for the hook. | ||
* @param {string} props.dataKey - The dataKey to identify the stored values. | ||
* @param {T} [props.initialValues] - The initial values for the form. | ||
* @param {Storage} [props.storage] - The storage to use (localStorage or sessionStorage). Defaults to localStorage. | ||
* @param {(values: T) => void} [props.saveFunction] - Custom function to save values. If provided, this will be used instead of the storage. | ||
* @param {(values?: T) => void} [props.clearFunction] - Custom function to clear values. Crucial for custom save functions. | ||
* @param {boolean} [props.forceLocalActions] - Whether to force local storage actions even if custom functions are provided. Could be useful for debugging. | ||
* @param {() => Promise<T>} [props.fetchInitialValues] - Function to fetch initial values asynchronously. Values from this function will override the storage values. | ||
* @returns {[T, React.Dispatch<React.SetStateAction<T>>, () => void]} - Returns the current values, a function to update the values, and a function to clear the values. | ||
* */ | ||
function useFormProgress<T extends {}>({ | ||
dataKey, | ||
initialValues = {} as T, | ||
storage, | ||
saveFunction, | ||
clearFunction, | ||
forceLocalActions = false, | ||
fetchInitialValues, | ||
}: useProgressProps<T>) { | ||
const [initialized, setInitialized] = React.useState(false); | ||
const [values, setValues] = React.useState(initialValues); | ||
|
||
React.useEffect(() => { | ||
if (typeof window !== 'undefined') { | ||
const saved = (storage ?? window.localStorage).getItem(key); | ||
let initialValue: T; | ||
try { | ||
initialValue = JSON.parse(saved!); | ||
} catch (e) { | ||
initialValue = {} as T; | ||
const initializeValues = async () => { | ||
let initialValue: T = initialValues; | ||
|
||
if (fetchInitialValues) { | ||
try { | ||
initialValue = await fetchInitialValues(); | ||
} catch (e) { | ||
console.error('Failed to fetch initial values:', e); | ||
} | ||
} else if (typeof window !== 'undefined') { | ||
const saved = (storage ?? window.localStorage).getItem(dataKey); | ||
try { | ||
initialValue = JSON.parse(saved!); | ||
} catch (e) { | ||
initialValue = {} as T; | ||
} | ||
} | ||
|
||
setValues(initialValue || initialValues || {} as T); | ||
saveValues(values); | ||
saveValues(initialValue); | ||
setInitialized(true); | ||
} | ||
}; | ||
|
||
void initializeValues(); | ||
}, []); | ||
|
||
React.useEffect(() => { | ||
if (!initialized) return; | ||
saveValues(values); | ||
}, [values]); | ||
|
||
// Helper function to save the data to local storage | ||
const saveValues = (values: T) => { | ||
if (saveFunction) { | ||
saveFunction(values); | ||
if (!forceLocalActions) return; | ||
} | ||
if (typeof window !== 'undefined') { | ||
(storage ?? window.localStorage).setItem(key, JSON.stringify(values)); | ||
(storage ?? window.localStorage).setItem(dataKey, JSON.stringify(values)); | ||
} | ||
}; | ||
|
||
// Provide helper function to clear the data from local storage | ||
const clearValues = () => { | ||
setValues(initialValues); | ||
if (clearFunction) { | ||
clearFunction(values); | ||
if (!forceLocalActions) return; | ||
} | ||
if (typeof window !== 'undefined') { | ||
(storage ?? window.localStorage).removeItem(key); | ||
(storage ?? window.localStorage).removeItem(dataKey); | ||
} | ||
}; | ||
|
||
return [values, setValues, clearValues] as const; | ||
} | ||
|
||
export default useProgress; | ||
export default useFormProgress; | ||
|
||
/** | ||
* @deprecated Use the {@link useProgress} hook instead. | ||
*/ | ||
export const useProgress = useFormProgress; | ||
|
||
/** | ||
* @deprecated Use the {@link useProgress} hook instead. | ||
* This will be removed in the next major version. | ||
*/ | ||
export const useSaveProgress = useProgress; | ||
export const useSaveProgress = useFormProgress; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters