Skip to content
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

Proposal: useObjectStore Hook #16

Open
SKaplanOfficial opened this issue Jul 18, 2023 · 3 comments
Open

Proposal: useObjectStore Hook #16

SKaplanOfficial opened this issue Jul 18, 2023 · 3 comments

Comments

@SKaplanOfficial
Copy link

Summary: A hook to simplify the creation and management of object dictionaries in LocalStorage, with a simple interface for CRUD operations. This would be useful for storing objects that populate List and Grid views, for example.

Details: This hook would create a LocalStorage entry containing a dictionary of objects as a JSON string, and it would provide easy access to methods for accessing, removing, and updating objects, as well as for performing object key migrations and batch-adding of new keys. A cached option could be provided.

Quite a few extensions store objects in this way and would benefit from a dedicated implementation, especially one that supports future development by providing methods for migrations (since the need/want to rename or delete keys is often unforeseen). Moreover, a standard implementation would allow additional features going forward, e.g. an option to use a JSON file in the extension's support directory rather than in LocalStorage, with minimal changes in extensions' code.

I'd be happy to work on this if there's support for it.

Proposed Interface:

interface LocalObjectStore<T> {
  allObjects: () => T[];

  // Add an object to the store, return the UUID for the object
  addObject: (object: T) => string;
  
  removeObject: (object: T) => void;
  getObject: (id: string) => T | undefined;
  setObject: (id: string, object: T) => void;
  updateObject(id: string, updateFunction: (oldObject: T) => T): void;
  removeObject: (id: string) => void;
  clear: () => void;

  // Rename a key within each object in the store, preserving the value
  renameKey: (oldKey: string, newKey: string) => void;

  // Add a key to each object in the store, using the provided function to generate the value
  addKey: (key: string, valueFunction: (object: T) => any) => void;

  // Delete a key from each object in the store
  deleteKey: (key: string) => void;
}

Example:

import { Action, ActionPanel, Grid } from "@raycast/api";
import { useObjectStore } from '@raycast/utils';

interface EmojiItem {
  name: string;
  emoji: string;
  description: string;
}

export default function Command() {

// Creates the store if it doesn't exist, or returns the existing store
const emojiStore = useObjectStore<EmojiItem>('emoji-items');

useEffect(() => {
  emojiStore.addObject({
    name: 'Smile',
    emoji: '😀',
    description: 'A happy face',
  });
}, []);

// Loads items once upon first render, updates when items are added/removed/modified
const emojiItems = emojiStore.allObjects();

return(
  <Grid>
    {emojiItems.map((item) => (
      <Grid.Item
        key={item.id} // Auto-generated UUID
        title={item.name}
        subtitle={item.description}
        content={item.emoji}
        actions={
          <ActionPanel>
            <Action
              title="Remove"
              onAction={() => emojiStore.removeObject(item.id)}
            />
          </ActionPanel>
        }
      />
    ))}
  </Grid>
);
}
@sxn
Copy link
Contributor

sxn commented Jul 19, 2023

hi @SKaplanOfficial, thanks for the thorough proposal!

I agree that it would be nice to have a hook to abstract away a bunch of the details interact LocalStorage (the async nature of its API, the (de-)serialisation of the data etc.), and unifying the way extensions interact with it.

To that end, though, I wonder if you'd be open to a simpler approach? 😅 It looks like the problems this hook tries to address would be equally solved by a useLocalStorage hook with a similar API to what useCachedState.

What do you think?

@SKaplanOfficial
Copy link
Author

Hey @sxn, I think useLocalStorage would address a different problem than what I've proposed. useObjectStore wraps functionality that could be built on top of useLocalStorage, namely a guarantee of uniquely identifiable items and methods to perform simple migrations.

In the example I gave, useLocalStorage would require several more lines to add and remove objects from the list, and even more to add/remove keys on each object, etc. Over time, many extensions will end up implementing the same logic to manage their data. This approach would minimize that.

Perhaps useLocalStorage would be good for the Raycast utils package, and this higher-level interface can be its own (unofficial) library.

@sxn
Copy link
Contributor

sxn commented Jul 20, 2023

hey @SKaplanOfficial, you're right. it just sounds like maybe a too specific problem for a general-purpose toolset.

Perhaps useLocalStorage would be good for the Raycast utils package, and this higher-level interface can be its own (unofficial) library

This sounds sensible, yeah. I'm aware of raycast-toolkit already, there's no reason it couldn't be extended, or have another library to cover more specific use cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants