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:
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.[1] https://softwareengineering.stackexchange.com/questions/290566/is-localstorage-under-the-cookie-law
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.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With