Giter VIP home page Giter VIP logo

use-chrome-storage's Introduction

use-chrome-storage

Russian invaders must die. Glory to Ukraine. Тримаймося, браття 🇺🇦

☝️ This package is for usage in Chrome Extensions. Should work with Firefox extensions but not tested.

Custom React hooks for chrome.storage. You may use it for keeping global persisted state in Chrome Extensions.

Note: Since it's a React hook, it may be used only in the React context. So it's impossible to use this package in the background service worker.

  • Simplify work with chrome.storage
  • Supports chrome.storage.local, chrome.storage.sync, and chrome.storage.session
  • May be used as persisted state available in different extension's contexts (content script, popup, options page)
  • Listen for chrome.storage changes and keep local state updated

Install

npm i use-chrome-storage

Usage

This package requires the storage permission in manifest.json:

{
    "name": "My Extension",
    "permissions": [
        "storage"
    ]
}

For usage with chrome.storage.local use useChromeStorageLocal hook. For chrome.storage.sync use useChromeStorageSync hook.

Usage of useChromeStorage

import React from 'react';
import {useChromeStorageLocal} from 'use-chrome-storage';


const LocalCounter = () => {
    // if you need to state be preserved in `chrome.storage.sync` use useChromeStorageSync
    // for `chrome.storage.session` use useChromeStorageSession
    const [value, setValue, isPersistent, error, isInitialStateResolved] = useChromeStorageLocal('counterLocal', 0);
    return (
            <div>
                <button
                        onClick={() => {
                            setValue(prev => (prev + 1));
                        }}
                >
                    Increment in Local Storage
                </button>
                <div>Value: {value}</div>
                <div>Persisted in chrome.storage.local: {isPersistent.toString()}</div>
                <div>Error: {error}</div>
                <div>Is state from chrome.storage already loaded? - {isInitialStateResolved.toString()}</div>
            </div>
    );
};

Usage of createChromeStorageStateHook

If you want to use same key in different components in different extension parts in React context (like in PopUp, content scripts, ) you need to use createChromeStorageStateHookLocal(for chrome.storage.local), createChromeStorageStateHookSync (for chrome.storage.sync) and createChromeStorageStateHookSession (for chrome.storage.session).

Initialize storage:

// common/useSettingsStore.js
import {createChromeStorageStateHookLocal} from 'use-chrome-storage';


const SETTINGS_KEY = 'settings';
const INITIAL_VALUE = {
    showAvatar: true,
    showHistory: false,
};

export const useSettingsStore = createChromeStorageStateHookLocal(SETTINGS_KEY, INITIAL_VALUE);

Use useSettingsStore on options page:

// options.js
import React from 'react';
import {useSettingsStore} from './common/useSettingsStore';


const Options = () => {
    const [settings, setSettings, isPersistent, error, isInitialStateResolved] = useSettingsStore();

    const handleChange = event => {
        setSettings(prevState => {
            return {
                ...prevState,
                [event.target.name]: event.target.checked
            };
        });
    };

    return (
            <div>
                <label>
                    <input
                            type="checkbox"
                            name="showAvatar"
                            checked={settings.showAvatar}
                            onChange={handleChange}
                    />
                    <span>Show Avatar</span>
                </label>
                <label>
                    <input
                            type="checkbox"
                            name="showHistory"
                            checked={settings.showHistory}
                            onChange={handleChange}
                    />
                    <span>Show History</span>
                </label>
                {isInitialStateResolved && <div>Initial state from "chrome.storage" is loaded</div>}
                {!isPersistent && <div>Error writing to the chrome.storage: {error}</div>}
            </div>
    );
};

Or from content script:

// contentScript.js
import React from 'react';
import Avatar from './common/Avatar';
import History from './common/History';
import {useSettingsStore} from './common/useSettingsStore';


const Card = () => {
    const [settings] = useSettingsStore();

    return (
            <div>
                {settings.showAvatar && <Avatar/>}
                {settings.showHistory && <History/>}
            </div>
    );
};

In the same way you may use it for PopUp.

Initial Value flow

Say we have the next hook:

const [value, setValue, isPersistent, error, isInitialStateResolved] = useChromeStorageLocal('counterLocal', 1);

Say in the chrome.storage.local we already have: counterLocal: 10.

Changes of value:

  1. value is 1 (initialValue set in the hook)
  2. useChromeStorageLocal call to chrome API (this API is async) to get the value of counterLocal.
  • value changes to 10
  • isInitialStateResolved changes to true indicating that value synchronized with data saved in chrome.storage

Usage with chrome.storage.session

useChromeStorageSession and createChromeStorageStateHookSessin use chrome.storage.session to persist state. By default, it's not exposed to content scripts, but this behavior can be changed by calling chrome.storage.session.setAccessLevel('TRUSTED_AND_UNTRUSTED_CONTEXTS') (call it from background script). https://developer.chrome.com/docs/extensions/reference/storage/#method-StorageArea-setAccessLevel

Clearing or removing storage items

Suppose you want to reset all your stored items back to their initial values.

You could, for example, use chrome.storage.local.clear() (or their 'session', 'sync' counterparts) to clear the entire storage.
Alternatively, you can use chrome.storage.local.remove(key) to remove a specific storage item.

This will trigger a sync event in your useChromeStorage[Local|Session|Sync] hooks, setting them back to their initial value.

API

useChromeStorageLocal(key, initialValue?)

State will be persisted in chrome.storage.local (and updated from chrome.storage.local if it was updated in other contexts). If you want to use this hook in more than one place, use createChromeStorageStateHookLocal.

  • key: string - The key used in chrome.storage.local
  • initialValue: any = undefined - value which will be used if chrome.storage.local has no stored value yet or when a stored item is removed (unset)

Returns

[value, setValue, isPersistent, error, isInitialStateResolved]

  • value: any - stateful value like first one returned from React.useState()
  • setValue: function - function to update value like second one returned from React.useState()
  • isPersistent: boolean - Will be true if data is persisted in chrome.storage.local. In case of error during chrome.storage.local.get or chrome.storage.local.set value will be stored in memory only and isPersistent will be set to false
  • error: string - If isPersistent is true will contain empty string. Otherwise, will contain error returned by chrome.runtime.lastError.
  • isInitialStateResolved: boolean - will set to true once initialValue will be replaced with stored in chrome.storage

useChromeStorageSync(key, initialValue?)

Similar to useChromeStorageLocal but will use chrome.storage.sync. State will be persisted in chrome.storage.sync (and updated from chrome.storage.sync if it was updated in other contexts). If you want to use this hook in more than one place, use createChromeStorageStateHookSync.

  • key: string - The key used in chrome.storage.sync
  • initialValue: any = undefined - value which will be used if chrome.storage.sync has no stored value yet

Returns

[value, setValue, isPersistent, error]

  • value: any - stateful value like first one returned from React.useState()
  • setValue: function - function to update value like second one returned from React.useState()
  • isPersistent: boolean - Will be true if data is persisted in chrome.storage.sync. In case of error during chrome.storage.local.get or chrome.storage.local.set value will be stored in memory only and isPersistent will be set to false
  • error: string - If isPersistent is true will contain empty string. Otherwise, will contain error returned by chrome.runtime.lastError.
  • isInitialStateResolved: boolean - will set to true once initialValue will be replaced with stored in chrome.storage

useChromeStorageSession(key, initialValue?)

Similar to useChromeStorageLocal but will use chrome.storage.session. State will be persisted in chrome.storage.session (and updated from chrome.storage.session if it was updated in other contexts). If you want to use this hook in more than one place, use createChromeStorageStateHookSession.

  • key: string - The key used in chrome.storage.session
  • initialValue: any = undefined - value which will be used if chrome.storage.session has no stored value yet

Returns

[value, setValue, isPersistent, error]

  • value: any - stateful value like first one returned from React.useState()
  • setValue: function - function to update value like second one returned from React.useState()
  • isPersistent: boolean - Will be true if data is persisted in chrome.storage.session. In case of error during chrome.storage.session.get or chrome.storage.session.set value will be stored in memory only and isPersistent will be set to false
  • error: string - If isPersistent is true will contain empty string. Otherwise, will contain error returned by chrome.runtime.lastError.
  • isInitialStateResolved: boolean - will set to true once initialValue will be replaced with stored in chrome.storage.session

createChromeStorageStateHookLocal(key, initialValue?)

In case you want to use same key in different components/extension contextsInstead you may create state hook which may be used across extension. See example. State will be persisted in chrome.storage.local.

  • key: string - The key used in chrome.storage.local
  • initialValue: any = undefined - value which will be used if chrome.storage.local has no stored value yet

Returns

function(): [any, (value: any) => void, boolean, string, boolean] - useChromeStorageLocal hook which may be used across extension's components/pages

createChromeStorageStateHookSync(key, initialValue?)

Similar to createChromeStorageStateHookLocal but uses chrome.storage.sync. In case you want to use same key in different components/extension contextsInstead you may create state hook which may be used across extension. See example and replace with createChromeStorageStateHookSync. State will be persisted in chrome.storage.sync.

  • key: string - The key used in chrome.storage.sync
  • initialValue: any = undefined - value which will be used if chrome.storage.sync has no stored value yet

Returns

function(): [any, (value: any) => void, boolean, string, boolean] - useChromeStorageSync hook which may be used across extension's components/pages

createChromeStorageStateHookSession(key, initialValue?)

Similar to createChromeStorageStateHookLocal but uses chrome.storage.session. In case you want to use same key in different components/extension contextsInstead you may create state hook which may be used across extension. See example and replace with createChromeStorageStateHookSession. State will be persisted in chrome.storage.session.

  • key: string - The key used in chrome.storage.session
  • initialValue: any = undefined - value which will be used if chrome.storage.session has no stored value yet

Returns

function(): [any, (value: any) => void, boolean, string, boolean] - useChromeStorageSession hook which may be used across extension's components/pages

Thanks to

use-local-storage-state for inspiration

use-chrome-storage's People

Contributors

chaysen avatar darhagonable avatar dependabot-preview[bot] avatar dependabot[bot] avatar eamonwoortman avatar hidemitsufcamara avatar joshua-scott avatar kruzkasu223 avatar onikienko avatar spencerc99 avatar vinicoder avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

use-chrome-storage's Issues

`createChromeStorageStateHookLocal` knowing when value has been fetched (or none found in storage)

hi there! thanks for making the library :) I'm trying to leverage local chrome storage to 1) persist some remote data that is fetched and 2) annotate that remote data based on actions done via the chrome extension.

I'm running into some issues with this because all of the data I annotate is blown away whenever I fetch from the remote data (even though I check if the existing store has the annotated data). The issue is that there's no way for me to know whether the value that the hook returns is the initialValue because it has not yet been loaded or if it's because it has no information about it. I wonder if there's any way to return another boolean from the hook to indicate hasLoaded where it would be false until successfully loaded from local storage.

Open to ideas of other ways to solve this as well.

Add proper typing support for typescript

Currently the typing looks like this:

useChromeStorageLocal(key: string, initialValue?: any): [any, (value: any) => void, boolean, string];

The problem with this is that the state always is any instead of being properly typed.

Would be better it it looked something similar to this:

useChromeStorageLocal<T>(key: string, initialValue?: T): [T, (value: T) => void, boolean, string];

This would allow one to say what type the the value is using type arguments and perform proper type checking:

const [value, setValue, isPersistent, error] = useChromeStorageLocal<number>('counterLocal', 0);

console.log(value) // here value will property be seen as a number instead of any

chrome.storage.local.get doesn't return the initial values

when providing an initial value to the createChromeStorageStateHookLocal hook, and trying to access the data from chrome.storage.local.get, it returns undefined instead of the initial false.
P.S.: the hook is already mounted by the timechrome.storage.local.get was called.

export const SETTINGS_KEY = 'settings';
const INITIAL_VALUE = {
  isDark: false,
};

export const useSettingsStore = createChromeStorageStateHookLocal(SETTINGS_KEY, INITIAL_VALUE);
  chrome.storage.local.get([SETTINGS_KEY], async function (result) {
      console.log(result.isDark) // undefined
  });

value in useEffect is undefined

I want to get the value of the chrome-storage when the page is mounted and then display it back on the page, if the chrome-storage is not there then set the default value, but in useEffect my projectList is undefined, how to solve it?

"The second argument of useChromeStorageLocal does not initialize values in Chrome storage, What does the second parameter do?

import { useChromeStorageLocal } from 'use-chrome-storage';

 const [projectList, setProjectList] = 
useChromeStorageLocal(AJAX_INTERCEPTOR_PROJECTS, [{ "pathUrl": "localhost:3000", "rules": [] }]);
 const [currentProject, setCurrentProject] =
 useChromeStorageLocal(AJAX_INTERCEPTOR_CURRENT_PROJECT, "localhost:3000");

  useEffect(() => {
    console.log('projectList', projectList)// this is undifined

    if (!projectList || !projectList.length) { 
      const defaultProjectProduct = {
        pathUrl: 'localhost:3000',
        rules: []
      }

      setProjectList(() => {
        return [defaultProjectProduct]
      })

      setCurrentProject(() => defaultProjectProduct.pathUrl)
     }
  
  }, []);

Create Chrome Storage State Hook Local/Sync wrong type?

Hi,

Documentation show how to use the create state hook, but when i use in my project eslint show this error:

(alias) const useSyncProfile: [{}, React.Dispatch<React.SetStateAction<{}>>, boolean, string]
import useSyncProfile
This expression is not callable.
  Type '[{}, Dispatch<SetStateAction<{}>>, boolean, string]' has no call signatures.ts(2349)

When i look the type i see:

  function createChromeStorageStateHookSync<S>(key: string, initialValue: S | (() => S)): [S, Dispatch<SetStateAction<S>>, boolean, string];
  function createChromeStorageStateHookSync<S = undefined>(key: string): [S | undefined, Dispatch<SetStateAction<S | undefined>>, boolean, string];

Looking the comments i see other type in return:

  /**
   * Use to create state with chrome.storage.sync.
   * Useful if you want to reuse same state across components and/or context (like in popup, content script, background pages)
   *
   * @param {string} key - they key name in chrome's storage. Nested keys not supported
   * @param {*} [initialValue] - default value to use
HERE   ----> * @returns {function(): [any, (value: any) => void, boolean, string]}
   */

So, the correct type is doesn't this?

  function createChromeStorageStateHookSync<S>(key: string, initialValue: S | (() => S)): () => [S, Dispatch<SetStateAction<S>>, boolean, string];
  function createChromeStorageStateHookSync<S = undefined>(key: string): () => [S | undefined, Dispatch<SetStateAction<S | undefined>>, boolean, string];

Review and improve typing - index.d.ts

TypeScript is not my language.
So this part is supported by the community.

I think index.d.ts may be improved.

Just need to implement everything as described in API docs.

Will be great if someone may help with it.

createChromeStorageStateHookLocal returns initial value first then the persisted one

I've been using createChromeStorageStateHookLocal for some time now for my Chrome extension but I noticed an annoying rerender issue that I couldn't debug, for some reason, the hook always returns the initial values on the first render then rerenders again with the persisted values.

The expected behavior:
on the first render, the hook should return the persisted values if they exist and fallback to the initial values, and should not render twice.

How I'm using it right now:

const SETTINGS_KEY = 'settings';
const INITIAL_VALUE: Settings = {
  isDark: false,
};

export const useSettingsStore = createChromeStorageStateHookLocal(SETTINGS_KEY, INITIAL_VALUE);
    const [{ isDark }] = useSettingsStore()

    // assuming that the persisted value of isDark is "true"
    console.log(isDark)  // 1st render: false, 2nd render: true

Update Types

Hey,
Thanks for the awesome package. Could you update the return types from the hooks. Gettin TS errors
e.g.
function createChromeStorageStateHookLocal<S>(key: string, initialValue: S | (() => S)): [S, Dispatch<SetStateAction<S>>, boolean, string];
should be
function createChromeStorageStateHookLocal<S>(key: string, initialValue: S | (() => S)): () => [S, Dispatch<SetStateAction<S>>, boolean, string];

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.