-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add useLocalStorage hook #29
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
bb44f46
Add useLocalStorage hook
thomaslombart ffe132e
Merge branch 'main' into use-local-storage
thomaslombart 114acf2
`useFetch`: Fix types to allow returning `cursor` from `mapResult`.
sxn 763ad01
Publish 1.13.6
sxn d102ce0
Add `useStreamJSON` hook (#26)
sxn f393dca
Add useLocalStorage hook
thomaslombart 5454ee1
Reviews
thomaslombart 9356bff
Merge branch 'main' into use-local-storage
thomaslombart 65a1c24
Bump version
thomaslombart fb0fde5
Remove remark
thomaslombart 84c5b24
Merge branch 'main' into use-local-storage
thomaslombart 6d3f23f
Add optimistic updates
thomaslombart f1cc83b
review
thomaslombart 2588f56
Merge branch 'main' into use-local-storage
thomaslombart 0c4c161
Review
thomaslombart 46a7713
Fix build
thomaslombart 3baef0b
Review
thomaslombart File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# `useLocalStorage` | ||
|
||
A hook to manage a value in the local storage. | ||
|
||
## Signature | ||
|
||
```ts | ||
function useLocalStorage<T>(key: string, initialValue?: T): { | ||
value: T | undefined; | ||
setValue: (value: T) => Promise<void>; | ||
removeValue: () => Promise<void>; | ||
isLoading: boolean; | ||
} | ||
``` | ||
|
||
### Arguments | ||
|
||
- `key` - The key to use for the value in the local storage. | ||
- `initialValue` - The initial value to use if the key doesn't exist in the local storage. | ||
|
||
### Return | ||
|
||
Returns an object with the following properties: | ||
|
||
- `value` - The value from the local storage or the initial value if the key doesn't exist. | ||
- `setValue` - A function to update the value in the local storage. | ||
- `removeValue` - A function to remove the value from the local storage. | ||
- `isLoading` - A boolean indicating if the value is loading. | ||
|
||
## Example | ||
|
||
```tsx | ||
import { Action, ActionPanel, Color, Icon, List } from "@raycast/api"; | ||
import { useLocalStorage } from "@raycast/utils"; | ||
|
||
const exampleTodos = [ | ||
{ id: "1", title: "Buy milk", done: false }, | ||
{ id: "2", title: "Walk the dog", done: false }, | ||
{ id: "3", title: "Call mom", done: false }, | ||
]; | ||
|
||
export default function Command() { | ||
const { value: todos, setValue: setTodos, isLoading } = useLocalStorage("todos", exampleTodos); | ||
|
||
async function toggleTodo(id: string) { | ||
const newTodos = todos?.map((todo) => (todo.id === id ? { ...todo, done: !todo.done } : todo)) ?? []; | ||
await setTodos(newTodos); | ||
} | ||
|
||
return ( | ||
<List isLoading={isLoading}> | ||
{todos?.map((todo) => ( | ||
<List.Item | ||
icon={todo.done ? { source: Icon.Checkmark, tintColor: Color.Green } : Icon.Circle} | ||
key={todo.id} | ||
title={todo.title} | ||
actions={ | ||
<ActionPanel> | ||
<Action title={todo.done ? "Uncomplete" : "Complete"} onAction={() => toggleTodo(todo.id)} /> | ||
<Action title="Delete" style={Action.Style.Destructive} onAction={() => toggleTodo(todo.id)} /> | ||
</ActionPanel> | ||
} | ||
/> | ||
))} | ||
</List> | ||
); | ||
} | ||
``` |
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 |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function replacer(this: any, key: string, _value: unknown) { | ||
const value = this[key]; | ||
if (value instanceof Date) { | ||
return `__raycast_cached_date__${value.toString()}`; | ||
} | ||
if (Buffer.isBuffer(value)) { | ||
return `__raycast_cached_buffer__${value.toString("base64")}`; | ||
} | ||
return _value; | ||
} | ||
|
||
export function reviver(_key: string, value: unknown) { | ||
if (typeof value === "string" && value.startsWith("__raycast_cached_date__")) { | ||
return new Date(value.replace("__raycast_cached_date__", "")); | ||
} | ||
if (typeof value === "string" && value.startsWith("__raycast_cached_buffer__")) { | ||
return Buffer.from(value.replace("__raycast_cached_buffer__", ""), "base64"); | ||
} | ||
return value; | ||
} |
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
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,64 @@ | ||
import { LocalStorage } from "@raycast/api"; | ||
import { showFailureToast } from "./showFailureToast"; | ||
import { replacer, reviver } from "./helpers"; | ||
import { usePromise } from "./usePromise"; | ||
|
||
/** | ||
* A hook to manage a value in the local storage. | ||
* | ||
* @remark The value is stored as a JSON string in the local storage. | ||
* | ||
* @param key - The key to use for the value in the local storage. | ||
* @param initialValue - The initial value to use if the key doesn't exist in the local storage. | ||
* @returns An object with the following properties: | ||
* - `value`: The value from the local storage or the initial value if the key doesn't exist. | ||
* - `setValue`: A function to update the value in the local storage. | ||
* - `removeValue`: A function to remove the value from the local storage. | ||
* - `isLoading`: A boolean indicating if the value is loading. | ||
* | ||
* @example | ||
* ``` | ||
* const { value, setValue } = useLocalStorage<string>("my-key"); | ||
* const { value, setValue } = useLocalStorage<string>("my-key", "default value"); | ||
* ``` | ||
*/ | ||
export function useLocalStorage<T>(key: string, initialValue?: T) { | ||
const { | ||
data: value, | ||
isLoading, | ||
mutate, | ||
} = usePromise( | ||
async (storageKey: string) => { | ||
const item = await LocalStorage.getItem<string>(storageKey); | ||
|
||
return typeof item !== "undefined" ? (JSON.parse(item, reviver) as T) : initialValue; | ||
}, | ||
[key], | ||
); | ||
|
||
async function setValue(value: T) { | ||
try { | ||
await mutate(LocalStorage.setItem(key, JSON.stringify(value, replacer)), { | ||
optimisticUpdate(value) { | ||
return value; | ||
}, | ||
}); | ||
} catch (error) { | ||
await showFailureToast(error, { title: "Failed to set value in local storage" }); | ||
} | ||
} | ||
|
||
async function removeValue() { | ||
try { | ||
await mutate(LocalStorage.removeItem(key), { | ||
optimisticUpdate() { | ||
return undefined; | ||
}, | ||
}); | ||
} catch (error) { | ||
await showFailureToast(error, { title: "Failed to remove value from local storage" }); | ||
} | ||
} | ||
|
||
return { value, setValue, removeValue, isLoading }; | ||
} |
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 |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Action, ActionPanel, Color, Icon, List } from "@raycast/api"; | ||
import { useLocalStorage } from "@raycast/utils"; | ||
|
||
const exampleTodos = [ | ||
{ id: "1", title: "Buy milk", done: false }, | ||
{ id: "2", title: "Walk the dog", done: false }, | ||
{ id: "3", title: "Call mom", done: false }, | ||
]; | ||
|
||
export default function Command() { | ||
const { value: todos, setValue: setTodos, isLoading } = useLocalStorage("todos", exampleTodos); | ||
|
||
async function toggleTodo(id: string) { | ||
const newTodos = todos?.map((todo) => (todo.id === id ? { ...todo, done: !todo.done } : todo)) ?? []; | ||
await setTodos(newTodos); | ||
} | ||
|
||
return ( | ||
<List isLoading={isLoading}> | ||
{todos?.map((todo) => ( | ||
<List.Item | ||
icon={todo.done ? { source: Icon.Checkmark, tintColor: Color.Green } : Icon.Circle} | ||
key={todo.id} | ||
title={todo.title} | ||
actions={ | ||
<ActionPanel> | ||
<Action title={todo.done ? "Uncomplete" : "Complete"} onAction={() => toggleTodo(todo.id)} /> | ||
<Action title="Delete" style={Action.Style.Destructive} onAction={() => toggleTodo(todo.id)} /> | ||
</ActionPanel> | ||
} | ||
/> | ||
))} | ||
</List> | ||
); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had another thought: I'm wondering if we could support a
setTodos(todos => ...)
signature for the setter (similar to the setter ofuseState
). This could some async check 🤔But I guess it can come in a follow up PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'd keep it simple for now. We can always iterate on it later since it won't be a breaking change.