【问题标题】:How to prevent redux-persist from using LocalStorage before it has been allowed by the user?如何防止 redux-persist 在用户允许之前使用 LocalStorage?
【发布时间】:2020-07-25 20:54:13
【问题描述】:

我想在使用 LocalStorage 之前通过单击横幅按钮来征得用户的同意。 LocalStorage 通过redux-persist 使用。我使用reduxredux-persist 如下:

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

storepersistor 来自

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 在创建对象后立即在 LocalStorage 中保持初始状态,这不是我想要的。

无论状态是否持久化,我都想在我的 React 组件中使用 redux。

我认为 reducer 的细节并不重要,因为它不会影响状态的存储位置/方式。

用户曾经表示同意使用 LocalStorage 和 cookie 的信息存储在 cookie 中。

只有在用户在第一次访问期间表示同意或 cookie 已经存在时,我如何才能开始使用 LocalStorage 进行持久性? cookie过期或被删除的情况,应包含在首次访问的情况下。

为了尽量减少讨论,我正在寻找解决技术问题的解决方案。就法律要求而言,该请求可能过于矫枉过正。

到目前为止我尝试过的事情:

  • 使用两个存储(可能是不好的做法),它们在包装ProviderApp 组件的状态下进行管理,如示例中所示。问题是,一旦单击横幅按钮,页面就会重新呈现,这是不可接受的。
  • 已评估的黑名单不起作用,因为它对状态的初始持久性没有影响。

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

【问题讨论】:

    标签: reactjs cookies redux local-storage redux-persist


    【解决方案1】:

    这里的想法是 redux-persist 的持久化组件负责在 localStorage 中持久化数据或 redux 状态。

    为了在用户同意的基础上做出决定,需要有条件地渲染或不渲染PersistGate组件

    要解决这样的问题,您可以在 Persistor 上编写一个自定义组件,该组件仅在授予权限时才呈现它。提示用户授予或拒绝权限的逻辑也可以放在同一个组件中

    例子

    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> 
      }
    }
    

    如上创建组件后,您将按如下方式呈现它

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

    注意: 您可以随时根据需求修改 PermissionAndPersist 组件的实现,但请注意 PeristGate 必须仅在所有条件都匹配时才呈现。如果用户未授予权限,您可能还需要清除 localStorage

    编辑: 由于要求实际上是在单击用户横幅时不重新渲染整个应用程序,因此我们需要进行一些更改。

    首先,根据条件重新渲染 ModalComponent。其次,我们不能有条件地更改我们重新渲染的组件,否则整个应用程序都会被刷新。到目前为止,实现它的唯一方法是自己实际实现将 redux 状态保存在 localStorage 中的逻辑,并在刷新时初始获取它

    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);
    

    实现上述组件后,需要在reducer中监听PERSISTOR_HYDRATE动作,更新redux状态。

    注意:您可能需要添加更多处理以使持久化和重新水化正确,但想法保持不变

    【讨论】:

    • 感谢您的意见。 Afaik “问题是一旦单击横幅按钮就会重新呈现页面,这是不可接受的。”适用于此解决方案。
    • 你希望行为是什么
    • 我希望在没有重新呈现页面的情况下单击横幅后激活持久性。我更喜欢这个,因为大多数网站在点击 cookie 横幅后不会重新呈现。
    • 这似乎是个好主意,但是本地存储永远不会被写入。
    • 能否请您调试一下,看看是否调用了localStorage set函数。它也应该在刷新窗口或更改页面时发生。此外,您可以使用 beforeunload 事件来代替卸载,看看是否有帮助
    【解决方案2】:

    以下内容是我能想到的最好的。我找不到阻止 redux-persist 持久化的方法,但是使用自定义序列化程序,可以使其持久化一个空字符串,直到获得同意。因此,在您同意之前,您将在存储中获得persist:root = ''

    // cookieConsent.js
    import CookieConsent from "@grrr/cookie-consent"
    export const BASIC_FUNCTIONALITY = 'basic-functionality';
    export const cookieConsent = CookieConsent({
      ...
      cookies: [
        {
          id: BASIC_FUNCTIONALITY,
          ...
        },
        ...
      ]
    });
    
    // configureStore.js
    import {cookieConsent, BASIC_FUNCTIONALITY} from './cookieConsent'
    
    ...
    
    export default function configureStore(initialState) {
    
      ...
    
      const serialize = (data) => {
        return cookieConsent.isAccepted(BASIC_FUNCTIONALITY) ? JSON.stringify(data) : ''
      }
      const persistedReducer = persistReducer({
        key: 'root',
        storage,
        serialize,
        whitelist: config.reduxPersistWhitelist,
      }, createRootReducer(history))
    
      const store = createStore(...)
    
      const persistor = persistStore(store)
      cookieConsent.on('update', (cookies) => {
        // Just persist on every update to the consent; depend on the serializer to do the right thing
        persistor.persist()
      })
    
      ...
    }
    

    【讨论】:

      猜你喜欢
      • 2018-04-13
      • 1970-01-01
      • 2022-08-02
      • 2010-09-11
      • 1970-01-01
      • 2021-03-25
      • 2020-07-12
      • 2020-05-17
      • 2018-11-22
      相关资源
      最近更新 更多