Code Monkey home page Code Monkey logo

recoil-persist's Introduction

Recoil Persist

Tiny module for recoil to store and sync state to Storage. It is only 354 bytes (minified and gzipped). No dependencies. Size Limit controls the size.

Demo

If you are using recoil-persist with version 1.x.x please check migration guide to version 2.x.x.

Example of persist state in localStorage

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { atom, RecoilRoot, useRecoilState } from 'recoil'
import { recoilPersist } from 'recoil-persist'

const { persistAtom } = recoilPersist()

const counterState = atom({
  key: 'count',
  default: 0,
  effects_UNSTABLE: [persistAtom],
})

function App() {
  const [count, setCount] = useRecoilState(counterState)
  return (
    <div>
      <h3>Counter: {count}</h3>
      <button onClick={() => setCount(count + 1)}>Increase</button>
      <button onClick={() => setCount(count - 1)}>Decrease</button>
    </div>
  )
}

ReactDOM.render(
  <React.StrictMode>
    <RecoilRoot>
      <App />
    </RecoilRoot>
  </React.StrictMode>,
  document.getElementById('root'),
)

Install

npm install recoil-persist

or

yarn add recoil-persist

Now you could add persisting a state to your app:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { RecoilRoot } from "recoil";
+import { recoilPersist } from 'recoil-persist'

+const { persistAtom } = recoilPersist()

const counterState = atom({
  key: 'count',
  default: 0,
+ effects_UNSTABLE: [persistAtom],
})

function App() {
  const [count, setCount] = useRecoilState(counterState)
  return (
    <div>
      <h3>Counter: {count}</h3>
      <button onClick={() => setCount(count + 1)}>Increase</button>
      <button onClick={() => setCount(count - 1)}>Decrease</button>
    </div>
  )
}

ReactDOM.render(
  <React.StrictMode>
    <RecoilRoot>
      <App />
    </RecoilRoot>
  </React.StrictMode>,
  document.getElementById('root'),
)

After this each changes in atom will be store and sync to localStorage.

Usage

import { recoilPersist } from 'recoil-persist'

const { persistAtom } = recoilPersist({
  key: 'recoil-persist', // this key is using to store data in local storage
  storage: localStorage, // configure which storage will be used to store the data
  converter: JSON // configure how values will be serialized/deserialized in storage
})

Example of persist state in localStorage

Server Side Rendering

If you are using SSR you could see that error:

Unhandled Runtime Error

Error: Text content does not match server-rendered HTML.

It happens because on server you don't have any storages and react renders component with default value. However in browser it is rendering with values from storage. To prevent it we need to introduce hook for render with default value for the first time.

const defaultValue = [{ id: 1 }]

export const recoilTest = atom<{ id: number }[]>({
  key: "recoilTest",
  default: defaultValue,
  effects_UNSTABLE: [persistAtom],
});

export function useSSR() {
  const [isInitial, setIsInitial] = useState(true);
  const [value, setValue] = useRecoilState(recoilTest);

  useEffect(() => {
    setIsInitial(false);
  }, []);

  return [isInitial ? defaultValue : value, setValue] as const;
}


export default function Component() {
  const [text, setText] = useSSR();

  // rest of the code
}

API

recoilPersist(config)

config parameter

type config.key = String

Default value of config.key is recoil-persist. This key is using to store data in storage.

type config.storage = Storage

Set config.storage with sessionStorage or other Storage implementation to change storage target. Otherwise localStorage is used (default).

type config.converter = {
  stringify: (value: any) => string
  parse: (value: string) => any
}

Set config.converter to an object which implements both stringify and parse functions to convert state values to and from strings. One use of this would be to wrap the standard JSON.stringify and JSON.parse functions, e.g. to insert your own reviver and replacer functions:

{
  parse: (value) => JSON.parse(value, myCustomReviver),
  stringify: (value) =>  JSON.stringify(value, myCustomReplacer)
};

Migration from version 1.x.x to 2.x.x

The API changed from version 1.x.x.

To update your code just use this migration guide:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { RecoilRoot } from "recoil";
import { recoilPersist } from 'recoil-persist' // import stay the same

const {
-  RecoilPersist,
-  updateState
+  persistAtom
} = recoilPersist(
-   ['count'], // no need for specifying atoms keys
    {
        key: 'recoil-persist', // configuration stay the same too
        storage: localStorage
    }
)

const counterState = atom({
  key: 'count',
  default: 0,
- persistence_UNSTABLE: { // Please remove persistence_UNSTABLE from atom definition
-   type: 'log',
- },
+ effects_UNSTABLE: [persistAtom], // Please add effects_UNSTABLE key to atom definition
})

function App() {
  const [count, setCount] = useRecoilState(counterState)
  return (
    <div>
      <h3>Counter: {count}</h3>
      <button onClick={() => setCount(count + 1)}>Increase</button>
      <button onClick={() => setCount(count - 1)}>Decrease</button>
    </div>
  )
}

ReactDOM.render(
  <React.StrictMode>
-   <RecoilRoot initializeState={({set}) => updateState({set})>
+   <RecoilRoot> // Please remove updateState function from initiallizeState
-     <RecoilPersist /> // and also remove RecoilPersist component
      <App />
    </RecoilRoot>
  </React.StrictMode>,
  document.getElementById('root')
);

Demo

$ git clone [email protected]:polemius/recoil-persist.git
$ cd recoil-persist
$ npm install
$ npm run start

Please open localhost:1234.

recoil-persist's People

Contributors

cuzzlor avatar dependabot[bot] avatar dimaip avatar jedelson-pagerduty avatar oliv37 avatar polemius avatar rhys-saldanha avatar toddmcbrearty 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  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  avatar  avatar  avatar

recoil-persist's Issues

Checklist for migration to 0.6.x and React 18.

It would be nice if the community knew what sort of contributions are needed to test recoil-persist's compatibility with 0.6/R18.

I'm doing it over a --force install and will report my findings here, but if anyone has some information on that already, let's coordinate here.

Support for asynchronous Storage (React Native)

Using this package does not work with React Native since their Storage API (aka the AsyncStorage API, here's a link to their docs) is asynchronous and this package does not seem to support an asynchronous Storage API. This issue is to simply open the conversation this subject.

The following is what I used to set the storage:

const {persistAtom} = recoilPersist({
  storage: AsyncStorage,
});

The error message:

There's an error here since toParse is a Promise.
image

Looking at the code, you could check if getItem() or/and setItem() is a promise to know if its async or not, then transform the functions to be async. This is potentially a naïve idea, but I'm not sure how it could be implemented otherwise, maybe you have a better idea on its implementation.

Fatal error when hydrating state

I've recently started seeing the following error:
Uncaught (in promise) TypeError: Cannot read property '__tag' of null
referring to this line:

if (newValue.__tag === 'DefaultValue' && state.hasOwnProperty(node.key)) {

Any idea of what could be causing it?

Support for Next.js

Hello! I'm trying to make this work with Next.js, but unfortunately it doesn't support ES modules yet, so I'm getting this error:

import { useTransactionObservation_UNSTABLE } from 'recoil'
^^^^^^

SyntaxError: Cannot use import statement outside a module

I would love to help you adding support for CommonJS. I'm actually trying to make it work following this guide (option 4) but with no success so far. Can you tell me if I'm in the good path?

Thanks a lot for your attention and for this package!

Cookie policy report

Hello.

I have issue with recoil-persist cookie. It is loaded every time before user has given consent.

Hydration Issue nextjs

https://stackoverflow.com/questions/68110629/nextjs-react-recoil-persist-values-in-local-storage-initial-page-load-in-wrong/73536131?noredirect=1#comment129857349_73536131

I've gone through this question but the value stored in the state of my atom is an object that is returned by an API call which is relatively complex compared to the boolean value mentioned in the stackoverflow question above. Unlike the case above, I don't think I can set the value of SSR to be the same as the CSR. How should I handle the hydration? Or can I just ignore the issue? WIll it impact the website when it is deployed to production?

Usage with localForage/IndexedDB/Dexie

Thanks for creating this library. Saved me a ton of time and is super simple. I am however getting errors as I am using up all the provided space in local storage. Read up that using localforage or Dexie can get around this. Are we able to integrate these into recoil-persist?

persistAtom error on initialLoad

Did not expect server HTML to contain a <span> in <div>.

persistAtom will create an error as above on initial load. which will effects on layout to be broken a little ... Is there anything that missing from me to prevent this problem ?

** Atoms **

export const keySecret = atom({
  key: "keySecret",
  default: {
    mKey : '',
    mSecret : ''
  },
  effects_UNSTABLE: [persistAtom] // Problem
})
** _app.tsx **

<>
  <Head>
    <title>Title</title>
  </Head>
  <ApolloProvider client={apolloClient}>
    <RecoilRoot>
      <RootLayout {...pageProps}>
        <Component {...pageProps} />
      </RootLayout>
    </RecoilRoot>
  </ApolloProvider>
</>
** Dependencies **

"next": "^11.1.0",
"antd": "4.15.6",
"recoil-persist": "^3.0.0",

Screen Shot 2564-08-23 at 09 32 28

Notify when state has rehydrated (with async storage)

Since asynchronous storage is supported, when the app opens, the default value of the atom is returned, then its persisted value is returned shortly afterwards. For example, in my mobile application, I have a disclaimer screen that is shown once when the app is first open, then it is never shown again, but since the default state is returned, it always shows the disclaimer. After simply putting a console.log in the useEffect() on that atom state, I see the following:

image

How could the situation be handled? I thought of putting a setTimeout() and show a "loading" component, but I feel like there must be a better way. I am coming from redux-persist and they render a "loading" component while the state rehydrates using the <PersistGate/> component.

recoil-persist not working as expected with useSetRecoilState in the useEffect hook

I want to update an atom with the query string in the URL. With useSetRecoilState I can set the atom but recoil-persist doesn't save in localStorage. I can see that the onSet callback in the effects of the atom is not triggered.

See this example:

import { atom } from 'recoil';
import { recoilPersist } from 'recoil-persist';

const { persistAtom } = recoilPersist();

export const atomState = atom({
  key: 'example',
  default: null,
  effects: [persistAtom],
});

import React from 'react';
import { Navigate } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { atomState } from 'state';
import { useSaveQueryString } from 'hooks/useSaveQueryString';

function MobileRoute() {
  useSaveQueryString ();

  const state = useRecoilValue(atomState );

  if (state ) {
    return <Navigate to="/something" />;
  }

  return <div>Loading...</div>;
}

export default MobileRoute;
function useSaveQueryString () {
  const [search] = useSearchParams();
  const setAtom = useSetRecoilState(atomState);

useEffect(() => {
const searchAsObject = Object.fromEntries(new URLSearchParams(search));

  if (searchAsObject.id) {
    setAtom (searchAsObject.id);
  }
}, [search, setAuth])
}

How can I solve it? Some example with recoil-sync??

Update peer dependency

I'm receiving this error when I'm installing the latest recoil-persist version right after installing recoil:

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/recoil
npm ERR!   recoil@"0.2.0" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer recoil@"^0.1.2" from [email protected]
npm ERR! node_modules/recoil-persist
npm ERR!   recoil-persist@"*" from the root project

These are my current npm and node versions:

$ npm -v
7.5.3
$ node -v
v15.10.0

Deploy Demo to GitHub Pages

You've got a demo in your tests folder. I think it would be beneficial to deploy it to GitHub Pages so that users can see how his library works without having to clone the repository.

Doesn't seem to handle atomFamily

I've noticed that the library doesn't seem to be able to restore state when the item being restored belongs to an atomFamily, is there a way around it?

Support arbitrary storages?

I'm thinking of doing something along what you are doing but with IndexedDB. Given that API is asynchronous, it looks like the API of recoil-persist would have to change a little bit to accommodate this. Would you be open for a PR?

The other part of this for me is subscribing to changes from IndexedDB as third parties can change the data but my feeling is that it's something that can be solved outside of recoil-persist and on Recoil side.

Nextjs Incompatibility

Unfortunately, this library cannot work with a nextjs project.

I managed to make it work, but I had to change too many stuffs in order to make it work and I cannot make a pr because of that.

If you want, we can think together about this topic.

persistence_UNSTABLE not mentioned in Recoil documentation

I think this is a symptom of the property being unstable, but in the current latest version of Recoil, adding it to my atom definition is throwing a TypeScript error.

I've checked the Recoil documentation and persistence_UNSTABLE isn't mentioned anywhere. For reference, annotating the property with ts-ignore fixes the issue and local persistence works as expected.

Is this a missing type within Recoil, or is this property only needed and used by recoil-persist?

how to testing with jest!

when I plug effects_UNSTABLE: [ReactNativeRecoilPersist.persistAtom], into myAtom, and then run test it gives error: TypeError: effect is not a function.
I try command line //effects_UNSTABLE: [ReactNativeRecoilPersist.persistAtom]. and run test again, so it test passed.
I don't why and don't find any solution. Anyone have solutions, please show me.
thank all so much.

does not support multiple state setter in local storage

let say I have a button that will set multiple states when being clicked:

const handleMultipleState = () => {
   setCount2(count2 + 1)
   setCount(count - 1)
 }

On UI (recoil state), the states are being mutated as an expected behavior. However, when you look into local storage, only the first state being persisted (in this case 'count2')

Async/await causes a race condition in React

When using useRecoilCallback and setting multiple atoms using the same atomEffect the current implementation breaks. It will cause a race condition getting the current localStorage value because of the async/await that was added to onSet and getState.

This bug causes you to lose state persisted in localStorage

App crashes when this data is loaded into localStorage...

Hi,

Each time I save this data to localStorage, my app crashes with:

Uncaught Error: Objects are not valid as a React child (found: object with keys {NAME, LIGHTS}). If you meant to render a collection of children, use an array instead.

{ NAME:"STUDY", LIGHTS: [
     {dali:6,type:"C",x:200,y:100,W:5},
     {dali:6,type:"C",x:130,y:120,W:5},
     {dali:3,type:"W",x:230,y:220,W:5},
     {dali:3,type:"S",x:330,y:320,W:5},
     {dali:0,type:"D",x:220,y:160},
     {dali:0,type:"D",x:120,y:260} ] }

Do you think this is a file format error ?

Is there another data format that I could use here ?

Thanks !

can we use indexedb?

Can we use indexDB instead of localStorage? There is an issue in latest electron wrapper that clears localStorage when the app closes...

Next.js compatibility

One of the limitations of next.js is that the HTML produced by the initial template must match that of the server. This leads the following contrived issue.

import type { NextPage } from 'next'
const isBrowser = () => !!global?.window
const data = isBrowser() ? 'browser' : 'server'
const Home: NextPage = () => {  return (
      <p data-ref={data}>{data}</p>
  )
}
export default Home

This code produced the following HTML:
<p data-ref="server">browser</p>

Now, when we apply this to recoil-persist, what we are seeing is situations where, because the storage is not available to the server, any scenarios where we use state to derive the HTML parameter, we are required to manually force an additional render (by adding the recoil state into a component state and updating on first tick with an effect).

I wonder whether there is a way to improve compatibility at this end? Perhaps setting persisted state on first tick rather than on init would force a render and therefore not require the end user to do so?

Happy to help with this, but have been struggling to get the plugin building locally.

Feature Request: transformer like in redux-persist

Hi,

currently I'm experimenting to completely replace redux with recoil and react state. One of the core features I'm using and that is not available currently are transformers.

As an example I would like to encrypt the data in the storage and this could be done by a transformer.

Before suggesting a pull request with the feature I would like to ask @polemius and the community if they have some suggestions for that or conditions to this feature.

When we find a consent I'm start developing this feature for generic transformers.

Persist state using chrome storage api instead of local storage?

Hi there,
Thanks for developing this wonderful package. It saves me lots of time on setting up state management for my chrome extension.
Just wondering if it's possible to persist using chrome storage instead of localStorage? As I'm developing a chrome extension, it would be great if I could use chrome storage API to store my state so that I can access my data from different area (i.e. contentScript, backgroundScript/serviceWorker, popUp).

I really appreciate any help you can provide. Thanks in advance

Something like this

const localStorageBase64 = () => {
  return {
    setItem: (key, value) => {
      chrome.storage.local.set(key, encode(value))
    },
    getItem: (key) => {
      const a = chrome.storage.local.get(key)
      return decode(a || '')
    },
    clear: () => {
      chrome.storage.local.clear()
    }
  }
}

const { persistAtom } = recoilPersist({
  key: 'recoil-storage', // this key is using to store data in local storage
  storage: localStorageBase64() // configurate which stroage will be used to store the data
})

Seeding starting data

I'd like my app to start with some more complex defaults than what recoil could give by itself. I was hoping that there'd be a way to seed the state into local storage and then have recoil pick that up, but I'm having difficulty with it.
I'm using an atomFamily + atom<id[]> pair, and want to start the app with 2 of the objects. Is there a good way to do this with recoil-persist?

How rehydration works?

Hi hackers!

I was wondering... what is this the right way to rehydrate the state?

My current version is:

state/user/atom.ts

import { atom } from "recoil";

import StateUser from "state/user/types";
import { statePersist } from "config/constants";

const ATOM_KEY = "user";

const userDefault: StateUser = {
  email: "",
  firstName: "",
  id: "0",
  lastName: "",
  token: "",
};

/**
 * Get the localStorage persisted state, then rehydrate the atom.
 */
const userPersistedStorage = window.localStorage.getItem(statePersist.key);

const userPersisted = userPersistedStorage
  ? JSON.parse(userPersistedStorage)[ATOM_KEY]
  : userDefault;

/**
 * User State - Create or Rehydrate the Atom.
 */
const userAtom = atom({
  key: ATOM_KEY,
  default: userPersisted,
  // @ts-ignore
  persistence_UNSTABLE: {
    type: ATOM_KEY,
  },
});

export default userAtom;

index.tsx

import React, { StrictMode } from "react";
import ReactDOM from "react-dom";
import { RecoilRoot } from "recoil";
import recoilPersist from "recoil-persist";

import { statePersist } from "config/constants";

import "./index.css";
import App from "./App";

const { RecoilPersist, updateState } = recoilPersist(
  statePersist.atomsToPersist,
  {
    key: statePersist.key,
    storage: localStorage,
  }
);

ReactDOM.render(
  <StrictMode>
    <RecoilRoot initializeState={updateState}>
      <RecoilPersist />
      <App />
    </RecoilRoot>
  </StrictMode>,
  document.getElementById("root")
);

Do recoil-persist offers state rehydration?

MergeItem for ReactNative?

Hi, I faced an issue for persisting multiple atoms when I set them right after each other, how can I fix this problem?

can not install 5.0.0 in npm

I can not install 5.0.0 version in my project.

It appears to be installed after npm install recoil-persist or yarn add recoil-persist,
but my react project is not build with Module not found: Error: Can't resolve 'recoil-persist' error.
(but there is dependency in package.json)

i was able to fix the problem using 4.2.0,
but it seems that there is a problem with version 5.0.0

my project env : (ubuntu 20.04.6 / node 20.3.1 / npm 9.6.7 / react 18.2.0 / recoil 0.7.7)

AsyncStorage implementation issue.

will it be possible to provide an example for AsyncStorage when I tried it says "Possible Unhandled Promise Rejection (id: 0):
TypeError: Cannot read property 'hasOwnProperty' of null"

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.