Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent redux-persist from using LocalStorage before it has been allowed by the user?

I would like to get the user's consent with a click on a banner button before I use LocalStorage. LocalStorage is used through redux-persist. I'm using redux and redux-persist as follows:

ReactDOM.render(
  <Provider store={store}>
    <PersistGate loading={null} persistor={persitor} >
      <MyAppRouter />
    </PersistGate>
  </Provider>,
  document.getElementById("root")
)

and store and persistor are coming from

import { createStore } from "redux"
import { persistStore, persistReducer } from "redux-persist"
import storage from "redux-persist/lib/storage"
import { rootReducer } from "../reducers/index"

const persistConfig = {
  key: "root",
  storage,
}

const persistedReducer = persistReducer(persistConfig, rootReducer)

const store = createStore(persistedReducer)
const persistor = persistStore(store as any)
// `as any` is necessary as long as https://github.com/reduxjs/redux/issues/2709 is not fixed

export { store, persistor }

redux-persist persists the initial state in LocalStorage as soon as the objects are created which is not what I want.

I'd like to use redux in my React components no matter whether the state is persisted or not.

I assume the details of the reducer don't matter as it doesn't have influence on where/how the state is stored.

The information that the user once has expressed consent to use LocalStorage and cookies is stored in a cookie.

How could I start using LocalStorage for persistence only after the user has expressed consent during the first visit or if the cookie is present already? The case that the cookie expired or has been deleted should be covered by the case of the first visit.

In order to minimize discussion I'm looking for a solution which solves the technical problem. The request might be overkill in terms of legal requirements.

Things I tried so far:

  • Use two store (probably bad practice) which are managed in the state of an App component which wraps Provider as shown in the example. Problem is that the page is re-rendered as soon as the banner button is clicked which is not acceptable.
  • Evaluated blacklisting which won't work because it has no impact on the initial persistence of the state.

[1] https://softwareengineering.stackexchange.com/questions/290566/is-localstorage-under-the-cookie-law

like image 964
Kalle Richter Avatar asked Apr 13 '20 10:04

Kalle Richter


People also ask

What is localStorage in Redux persist?

storage defines the storage engine to use. Redux Persist supports multiple different storage backends depending on the environment. For web use, the localStorage and sessionStorage APIs are both supported as well as basic cookies. Options are also available for React Native, Node.js, Electron and several other platforms.

What is persisting in Redux?

Redux simplifies state management in complex applications. As the Redux store contains your app’s entire state, persisting it lets you save and restore the user’s session. We’ll assume you’re familiar with Redux fundamentals.

How do I purge data from Redux persistent state?

You can purge all persisted data from the storage engine using .purge (). In most cases, this should be avoided – you should use a Redux action to clear your store, which would then automatically propagate to the persisted data. Redux Persist supports three different ways of hydrating your store from persisted state.

How to hydrate a redux persist store?

Redux Persist supports three different ways of hydrating your store from persisted state. Hydration occurs automatically when you call persistStore () and existing data is found in the storage engine. Redux Persist needs to inject that initial data into your store. The default strategy is to merge objects up to one level deep.


1 Answers

The idea here is that redux-persist's persistor component is responsible for persisting data or redux state in localStorage.

In order to make a decision based on the user's consent, you need to conditionally render or not the PersistGate component

To solve such a problem you can write a custom component over Persistor which renders it only when the permission is granted. Also the logic to prompt the user to grant or deny permission can go in the same component

Example

class PermissionAndPersist extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
        permission: this.getCookie('userLocalStoragePermission')
    }
  }

  getCookie(name) {
    //implement the getc ookie method using document.cookie or a library

    /* state returned must have the following syntax
         isPermissionGranted,
         isCookieExpired
    */ 

    // The above syntax can be determined based on whether cookie is present as well as by checking the expiry data if cookie was present
  }

  render() {
      const { permission } = this.state;
      const {children, persistor} = this.props;
      if(!permission.isPermissionGranted) {
         // no permission granted, return a plain div/Fragment
         return (
            <React.Fragment>
              {children}
            </React.Fragment>
         )
      }
      if(permission.isCookieExpired) {
          return <Modal>{/* here goes a component which asks for user permission and on click updates the state as well as update in cookie */}</Modal>
      }
      // now if the cookie is present and permission is granted and cookie is not expired you render the `PersistGate` component
      return <PersistGate persistor={persitor} >{children}</PersistGate> 
  }
}

Once you create the component as above you will render it as follows

ReactDOM.render(
  <Provider store={store}>
    <PermissionAndPersist persistor={persitor} >
      <MyAppRouter />
    </PermissionAndPersist >
  </Provider>,
  document.getElementById("root")
)

Note: You can always modify the implementation of PermissionAndPersist component depending on what the requirement is, but note that the PeristGate must only be rendered when all conditions are matching. Also you might want to clear localStorage if the user doesn't grant permission

EDIT: Since the requirement is to actually not re-render the entire app on click of user banner, we need to make a few changes.

Firstly, re re-render the ModalComponent based on a condition. Secondly, we cannot conditionally change the component we re-render or else the entire app is refreshed. The only way to achieve it as of now is to actually implement the logic to persist redux state in localStorage yourself and fetch it initially on refresh

class PermissionAndPersist extends React.Component {
      constructor(props) {
        super(props)
        this.state = {
            permission: this.getCookie('userLocalStoragePermission')
        }
      }

      getCookie(name) {
        //implement the getc ookie method using document.cookie or a library

        /* state returned must have the following syntax
             isPermissionGranted,
             isCookieExpired
        */ 

        // The above syntax can be determined based on whether cookie is present as well as by checking the expiry data if cookie was present
      }

      componenDidMount() {
          const { permission } = this.state;
          const { dispatch } = this.props;
          if(permission.isPermissionGranted && !permission.isCookieExpired) {
             // Idea here is to populate the redux store based on localStorage value
             const state= JSON.parse(localStorage.get('REDUX_KEY'));
             dispatch({type: 'PERSISTOR_HYDRATE', payload: state})
          }

          // Adding a listner on window onLoad
          window.addEventListener('unload', (event) => {
             this.persistStateInLocalStorage();
          });
      }

      persistStateInLocalStorage = () => {
         const { storeState} = this.props;
         const {permission} = this.state;
         if(permission.isPermissionGranted && !permission.isCookieExpired) {
            localStorage.set('REDUX_KEY', JSON.stringify(storeState))
         }
      }
      componentWillUnmount() {
          this.persistStateInLocalStorage();
      }
      render() {
          const {children} = this.props;
          const {permission} = this.state;
          return (
            <React.Fragment>
               {children}
               {permission.isCookieExpired ? <Modal>{/*Pemission handling here*/}</Modal>}
            </React.Fragment>
          )

    }

const mapStateToProps = (state) => {
   return {
     storeState: state
   }
}
export default connect(mapStateToProps)(PermissionAndPersist);

Once you implement the above component, you need to listen to the PERSISTOR_HYDRATE action in reducer and update the redux state.

NOTE: You might need to add more handling to make the persist and rehydrate correctly, but the idea remains the same

like image 100
Shubham Khatri Avatar answered Oct 13 '22 01:10

Shubham Khatri