How can I persist redux state tree on refresh? - reactjs

The first principle of Redux documentation is:
The state of your whole application is stored in an object tree within a single store.
And I actually thought that I understand all of the principles well.
But I'm now confused, what does application mean.
If application means just one of little complicated part in a website and works in just one page, I understand. But what if application means the whole website? Should I use LocalStorage or cookie or something for keeping the state tree? But what if the browser doesn't support LocalStorage?
I want to know how developers keep their state tree! :)

If you would like to persist your redux state across a browser refresh, it's best to do this using redux middleware. Check out the redux-persist and redux-storage middleware. They both try to accomplish the same task of storing your redux state so that it may be saved and loaded at will.
--
Edit
It's been some time since I've revisited this question, but seeing that the other (albeit more upvoted answer) encourages rolling your own solution, I figured I'd answer this again.
As of this edit, both libraries have been updated within the last six months. My team has been using redux-persist in production for a few years now and have had no issues.
While it might seem like a simple problem, you'll quickly find that rolling your own solution will not only cause a maintenance burden, but result in bugs and performance issues. The first examples that come to mind are:
JSON.stringify and JSON.parse can not only hurt performance when not needed but throw errors that when unhandled in a critical piece of code like your redux store can crash your application.
(Partially mentioned in the answer below): Figuring out when and how to save and restore your app state is not a simple problem. Do it too often and you'll hurt performance. Not enough, or if the wrong parts of state are persisted, you may find yourself with more bugs. The libraries mentioned above are battle-tested in their approach and provide some pretty fool-proof ways of customizing their behavior.
Part of the beauty of redux (especially in the React ecosystem) is its ability to be placed in multiple environments. As of this edit, redux-persist has 15 different storage implementations, including the awesome localForage library for web, as well as support for React Native, Electron, and Node.
To sum it up, for 3kB minified + gzipped (at the time of this edit) this is not a problem I would ask my team to solve itself.

Edit 25-Aug-2019
As stated in one of the comments. The original redux-storage package has been moved to react-stack. This approach still focuses on implementing your own state management solution.
Original Answer
While the provided answer was valid at some point it is important to notice that the original redux-storage package has been deprecated and it's no longer being maintained...
The original author of the package redux-storage has decided to deprecate the project and no longer maintained.
Now, if you don't want to have dependencies on other packages to avoid problems like these in the future it is very easy to roll your own solution.
All you need to do is:
1- Create a function that returns the state from localStorage and then pass the state to the createStore's redux function in the second parameter in order to hydrate the store
const store = createStore(appReducers, state);
2- Listen for state changes and everytime the state changes, save the state to localStorage
store.subscribe(() => {
//this is just a function that saves state to localStorage
saveState(store.getState());
});
And that's it...I actually use something similar in production, but instead of using functions, I wrote a very simple class as below...
class StateLoader {
loadState() {
try {
let serializedState = localStorage.getItem("http://contoso.com:state");
if (serializedState === null) {
return this.initializeState();
}
return JSON.parse(serializedState);
}
catch (err) {
return this.initializeState();
}
}
saveState(state) {
try {
let serializedState = JSON.stringify(state);
localStorage.setItem("http://contoso.com:state", serializedState);
}
catch (err) {
}
}
initializeState() {
return {
//state object
}
};
}
}
and then when bootstrapping your app...
import StateLoader from "./state.loader"
const stateLoader = new StateLoader();
let store = createStore(appReducers, stateLoader.loadState());
store.subscribe(() => {
stateLoader.saveState(store.getState());
});
Hope it helps somebody
Performance Note
If state changes are very frequent in your application, saving to local storage too often might hurt your application's performance, especially if the state object graph to serialize/deserialize is large. For these cases, you might want to debounce or throttle the function that saves state to localStorage using RxJs, lodash or something similar.

This is based on Leo's answer (which should be the accepted answer since it achieves the question's purpose without using any 3rd party libs).
I've created a Singleton class that creates a Redux Store, persists it using local storage and allows simple access to its store through a getter.
To use it, just put the following Redux-Provider element around your main class:
// ... Your other imports
import PersistedStore from "./PersistedStore";
ReactDOM.render(
<Provider store={PersistedStore.getDefaultStore().store}>
<MainClass />
</Provider>,
document.getElementById('root')
);
and add the following class to your project:
import {
createStore
} from "redux";
import rootReducer from './RootReducer'
const LOCAL_STORAGE_NAME = "localData";
class PersistedStore {
// Singleton property
static DefaultStore = null;
// Accessor to the default instance of this class
static getDefaultStore() {
if (PersistedStore.DefaultStore === null) {
PersistedStore.DefaultStore = new PersistedStore();
}
return PersistedStore.DefaultStore;
}
// Redux store
_store = null;
// When class instance is used, initialize the store
constructor() {
this.initStore()
}
// Initialization of Redux Store
initStore() {
this._store = createStore(rootReducer, PersistedStore.loadState());
this._store.subscribe(() => {
PersistedStore.saveState(this._store.getState());
});
}
// Getter to access the Redux store
get store() {
return this._store;
}
// Loading persisted state from localStorage, no need to access
// this method from the outside
static loadState() {
try {
let serializedState = localStorage.getItem(LOCAL_STORAGE_NAME);
if (serializedState === null) {
return PersistedStore.initialState();
}
return JSON.parse(serializedState);
} catch (err) {
return PersistedStore.initialState();
}
}
// Saving persisted state to localStorage every time something
// changes in the Redux Store (This happens because of the subscribe()
// in the initStore-method). No need to access this method from the outside
static saveState(state) {
try {
let serializedState = JSON.stringify(state);
localStorage.setItem(LOCAL_STORAGE_NAME, serializedState);
} catch (err) {}
}
// Return whatever you want your initial state to be
static initialState() {
return {};
}
}
export default PersistedStore;

Related

How to store data in this very simple React Native app?

I'm developing an app using React Native that allows you to create your own checklists and add items to them.
For example you'd have "Create Checklist", and inside that you'll have the option to "Add Item", "Delete Item" "Edit Item", basic CRUD methods etc.
It's going to be completely offline but I'm wondering what the best approach to storing this data locally would be.
Should I be using a DB such as firebase? I have read that it is overkill and to use something like Redux but I'm not sure if the latter will accomplish everything I need. As long as it's storing data which can be edited, and will save on the user's device (with minimal effort) it sounds good to me.
Would appreciate some input on this, thanks!
You could use AsyncStorage for persisting data locally on the user's phone. It is a simple persistent key-value-storage.
Each checklist is most likely an array of JS objects. The documentation provides an example on how to store objects.
const storeData = async (value) => {
try {
const jsonValue = JSON.stringify(value)
await AsyncStorage.setItem('#storage_Key', jsonValue)
} catch (e) {
// saving error
}
}
The value parameter is any JS object. We use JSON.stringify to create a JSON string. We use AsyncStorage.setItem in order to persist the data. The string #storage_Key is the key for the object. This could be any string.
We retrieve a persisted object as follows.
const getData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('#storage_Key')
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch(e) {
// error reading value
}
}
Both examples are taken from the official documentation.
Keep in mind that this functionality should be used for persistence only. If the application is running, you should load the complete list, or parts of the list if it is very large, in some sort of application cache. The implementation for this functionality now heavily depends on how your current code looks like. If you have a plain view, then you could access the local storage in an effect and just store it in a local state.
function MySuperList() {
const [list, setList] = useState([]);
React.useEffect(() => {
// retrieve data using the above functionality and set the state
}, [])
// render list
return (...)
}
I would implement some sort of save button for this list. If it is pressed, then we persist the data in the local storage of the phone.

React-Redux update and redirect logic

I have data structures Student, Class and Registrant. Registrant is the link between Class and Student:
Registrant
-----------
StudentId
ClassId
I also have an auxiliary data source when you can pick from - call it PotentialStudents.
An end user can browse the list of PotentialStudents, and register them for a class.
If a Student record does not exist for the PotentialStudent record, I need to create one, otherwise I need get the fetched record. I can know this because the PotentialStudentId is a field in Student, but there is no true relation between Student and PotentialStudent - they come from different databases and are actually different data shapes.
Anyways, this is all to setup my question. I need a function to "createOrFetchStudent", and then I send them on to the RegistrationDetail form.
My question, is how should this be done in react-redux?
I could -
Try to dispatch the save/fetch and have it signal when it's done, and then redirect, but it gets kind of messy
let student = useSelector(state => state.createdOrFetchedStudent);
useEffect(() => {
if(student) {
location.push("/registration", {studentId: student.id});
}
}, [student]);
onRegisterDetailClick = (potentialStudent) => {
// redux-thunk action that does multiple API calls
dispatch(createOrFetchStudent(potentialStudent));
}
Or do all the work from the action?
onRegisterDetailClick = (potentialStudent) => {
dispatch(createOrFetchStudentAndThenRedirect(potentialStudent));
}
But don't know if it's Ok to be using location in a redux action.
Other thoughts I had -
I could pass a callback to the action
onRegisterDetailClick = (potentialStudent) => {
dispatch(createOrFetchStudent(
potentialStudent,
(student) => { location.push("/registration", { studentId: student.id } ); })
);
}
I could return the dipatched data from the thunk method and await it
onRegisterDetailClick = async (potentialStudent) => {
let student = await dispatch(createOrFetchStudent(potentialStudent));
location.push("/registration", { studentId: student.id } ); }
}
None of these solutions seems very good. Are any of these solutions acceptable or is there a better way?
Keeping your action creators pure functions is a good idea (although it is not strictly required like for reducers).
It will improve testability and make your code easier to understand. Putting the side effects (like location change) into thunks is totally valid (your last example).
If you still don't feel comfortable with that (i kinda understand why) you can try utilise redux-saga which will allow you to manage all side effects in one place and keep your functions pure.
You can read more about each approach in redux docs

React Firebase Callback

I started learning React using Firebase about one year ago, and it's all going pretty well, I guess. However, ever since I started writing, I really miss mastering the art of callbacks like I did with Swift.
I feel that there's barely any information that's going straight to the point regarding callbacks and Firebase using React. For instance, there has been so many times I want to be able to use callbacks, but have ended up writing numerous extra functions to perform the exact same task. For instance, I want to be able to call a function, and then return a value inside a Snapshot.once. But how?
You see, this is an example of an issue I'm facing at this very moment. I'm currently mapping out a list of users on my website, with the following information: firstname, lastname and companyId. Upon display, I want to be able to verify that the companyId does in fact exist in a separate table called 'Companies' structured like this:
Companies -> uniqueCompanyId -> information
I know that I can't return values in an async function, but this is what I am thinking about:
isCompanyVerified(input){
databaseCompanies.child(input).once('value', (snapshot) => {
if(snapshot.val().isVerified){
return true;
} else {
return false;
}
})
}
<div>
allUsers.map((singleUser) => {
return(
<p>{singleUser.name}</p>
<p>{this.isCompanyVerified(singleUser.companyId)}</p>
)
})
</div>
It would really mean the world to me if someone could explain to me how I would do this the correct way, or at least point me in the right direction. The problem is that I honestly don't know where to seek information.
I'm used to having code structured with having all networking/database functions in one file, like I did in Swift called ex. 'Networking.swift'. And then do function calls that I fetch from the specific file. Using React, I have ended up having all of my code in each Component, resulting in a lot of duplicated code, a lot of extra work and making it all look unstructured.
I'm fairly familiar to the syntax right now, and I do believe I have learned a lot, yet - I feel that I do numerous things the wrong way, and still have a lot to learn. Where would be the best place for me to learn to really master React? Going from intermediate to an 'experienced' (writing and structuring React code like it's supposed to be done).
All help is appreciated. I'm making this post as I really want to put in the effort to learn.
You cannot directly write the dom in react like this. The dom is not being re-rendered in this case. Store the verification data in a state and the dom is being re-rendered as soon as the async data arrived.
SOLUTION 1
This is an implementation of a single UserItem component:
UserItem.js
class UserItem extends React.Component {
state {
isUserVerified: false
}
handleVerified = snapshot => {
this.setState({ isUserVerified: snapshot.val().isVerified })
}
componentDidMount {
const {userId} = this.props;
databaseCompanies.child(userId).once('value', this.handleVerified)
}
render() {
const { isUserVerified } = this.state;
return (
<div>{isUserVerified ? 'verified' : 'not verified'}</div>
)
}
}
UserList.js
...
render(){
allUsers.map(singleUser => <UserItem userId={singleUser.id} />
}
SOLUTION 2
If you'd like to list all the users, get a snapshot from the complete users object
componentDidMount {
databaseCompanies.child('users').once('value', this.handleStoreUsers)
}
This will result something like this in the state:
state = {
users: {
_key1: { id..., verified: true },
_key2: { id..., verified: false }
...
}
}
And map trough them.

Ways to access firebase storage (photos) via web app

I'm confused as to the appropriate way to access a bunch of images stored in Firebase storage with a react redux firebase web app. In short, I'd love to get a walkthrough of, once a photo has been uploaded to firebase storage, how you'd go about linking it to a firebase db (like what exactly from the snapshot returned you'd store), then access it (if it's not just <img src={data.downloadURL} />), and also how you'd handle (if necessary) updating that link when the photo gets overwritten. If you can answer that, feel free to skip the rest of this...
Two options I came across are either
store the full URL in my firebase DB, or
store something less, like the path within the bucket, then call downloadURL() for every photo... which seems like a lot of unnecessary traffic, no?
My db structure at the moment is like so:
{
<someProjectId>: {
imgs: {
<someAutoGenId>: {
"name":"photo1.jpg",
"url":"https://<bucket, path, etc>token=<token>"
},
...
},
<otherProjectDetails>: "",
...
},
...
}
Going forward with that structure and the first idea listed, I ran into trouble when a photo was overwritten, so I would need to go through the list of images and remove the db record that matches the name (or find it and update its URL). I could do this (at most, there would be two refs with the old token that I would need to replace), but then I saw people doing it via option 2, though not necessarily with my exact situation.
The last thing I did see a few times, were similar questions with generic responses pointing to Cloud Functions, which I will look into right after posting, but I wasn't sure if that was overcomplicating things in my case, so I figured it couldn't hurt too much to ask. I initially saw/read about Cloud Functions and the fact that Firebase's db is "live," but wasn't sure if that played well in a React/Redux environment. Regardless, I'd appreciate any insight, and thank you.
In researching Cloud Functions, I realized that the use of Cloud Functions wasn't an entirely separate option, but rather a way to accomplish the first option I listed above (and probably the second as well). I really tried to make this clear, but I'm pretty confident I failed... so my apologies. Here's my (2-Part) working solution to syncing references in Firebase DB to Firebase Storage urls (in a React Redux Web App, though I think Part One should be applicable regardless):
PART ONE
Follow along here https://firebase.google.com/docs/functions/get-started to get cloud functions enabled.
The part of my database with the info I was storing relating to the images was at /projects/detail/{projectKey}/imgs and had this structure:
{
<autoGenKey1>: {
name: 'image1.jpg',
url: <longURLWithToken>
},
<moreAutoGenKeys>: {
...
}, ...}
My cloud function looked like this:
exports.updateURLToken = functions.database.ref(`/projects/detail/{projectKey}/imgs`)
.onWrite(event => {
const projectKey = event.params.projectKey
const newObjectSet = event.data.val()
const newKeys = Object.keys(newObjectSet)
const oldObjectSet = event.data.previous.val()
const oldKeys = Object.keys(oldObjectSet)
let newObjectKey = null
// If something was removed, none of this is necessary - return
if (oldKeys.length > newKeys.length) {
return null
}
for (let i = 0; i < newKeys.length; ++i) {// Looking for the new object -> will be missing in oldObjectSet
const key = newKeys[i]
if (oldKeys.indexOf(key) === -1) {// Found new object
newObjectKey = key
break
}
}
if (newObjectKey !== null) {// Checking if new object overwrote an existing object (same name)
const newObject = newObjectSet[newObjectKey]
let duplicateKey = null
for (let i = 0; i < oldKeys.length; ++i) {
const oldObject = oldObjectSet[oldKeys[i]]
if (newObject.name === oldObject.name) {// Duplicate found
duplicateKey = oldKeys[i]
break
}
}
if (duplicateKey !== null) {// Remove duplicate
return event.data.ref.child(duplicateKey).remove((error) => error ? 'Error removing duplicate project detail image' : true)
}
}
return null
})
After loading this function, it would run every time anything changed at that location (projects/detail/{projectKey}/imgs). So I uploaded the images, added a new object to my db with the name and url, then this would find the new object that was created, and if it had a duplicate name, that old object with the same name was removed from the db.
PART TWO
So now my database had the correct info, but unless I refreshed the page after every time images were uploaded, adding the new object to my database resulted (locally) in me having all the duplicate refs still, and this is where the realtime database came in to play.
Inside my container, I have:
function mapDispatchToProps (dispatch) {
syncProjectDetailImages(dispatch) // the relavant line -> imported from api.js
return bindActionCreators({
...projectsContentActionCreators,
...themeActionCreators,
...userActionCreators,
}, dispatch)
}
Then my api.js holds that syncProjectDetailImages function:
const SAVING_PROJECT_SUCCESS = 'SAVING_PROJECT_SUCCESS'
export function syncProjectDetailImages (dispatch) {
ref.child(`projects/detail`).on('child_changed', (snapshot) => {
dispatch(projectDetailImagesUpdated(snapshot.key, snapshot.val()))
})
}
function projectDetailImagesUpdated (key, updatedProject) {
return {
type: SAVING_PROJECT_SUCCESS,
group: 'detail',
key,
updatedProject
}
}
And finally, dispatch is figured out in my modules folder (I used the same function I would when saving any part of an updated project with redux - no new code was necessary)

My flux store gets re-instantiated on reload

Okay. I'm kinda new to react and I'm having a #1 mayor issue. Can't really find any solution out there.
I've built an app that renders a list of objects. The list comes from my mock API for now. The list of objects is stored inside a store. The store action to fetch the objects is done by the components.
My issue is when showing these objects. When a user clicks show, it renders a page with details on the object. Store-wise this means firing a getSpecific function that retrieves the object, from the store, based on an ID.
This is all fine, the store still has the objects. Until I reload the page. That is when the store gets wiped, a new instance is created (this is my guess). The store is now empty, and getting that specific object is now impossible (in my current implementation).
So, I read somewhere that this is by design. Is the solutions to:
Save the store in local storage, to keep the data?
Make the API call again and get all the objects once again?
And in case 2, when/where is this supposed to happen?
How should a store make sure it always has the expected data?
Any hints?
Some if the implementation:
//List.js
componentDidMount() {
//The fetch offers function will trigger a change event
//which will trigger the listener in componentWillMount
OfferActions.fetchOffers();
}
componentWillMount() {
//Listen for changes in the store
offerStore.addChangeListener(this.retriveOffers);
}
retrieveOffers() {
this.setState({
offers: offerStore.getAll()
});
}
.
//OfferActions.js
fetchOffers(){
let url = 'http://localhost:3001/offers';
axios.get(url).then(function (data) {
dispatch({
actionType: OfferConstants.RECIVE_OFFERS,
payload: data.data
});
});
}
.
//OfferStore.js
var _offers = [];
receiveOffers(payload) {
_offers = payload || [];
this.emitChange();
}
handleActions(action) {
switch (action.actionType) {
case OfferConstants.RECIVE_OFFERS:
{
this.receiveOffers(action.payload);
}
}
}
getAll() {
return _offers;
}
getOffer(requested_id) {
var result = this.getAll().filter(function (offer) {
return offer.id == requested_id;
});
}
.
//Show.js
componentWillMount() {
this.state = {
offer: offerStore.getOffer(this.props.params.id)
};
}
That is correct, redux stores, like any other javascript objects, do not survive a refresh. During a refresh you are resetting the memory of the browser window.
Both of your approaches would work, however I would suggest the following:
Save to local storage only information that is semi persistent such as authentication token, user first name/last name, ui settings, etc.
During app start (or component load), load any auxiliary information such as sales figures, message feeds, and offers. This information generally changes quickly and it makes little sense to cache it in local storage.
For 1. you can utilize the redux-persist middleware. It let's you save to and retrieve from your browser's local storage during app start. (This is just one of many ways to accomplish this).
For 2. your approach makes sense. Load the required data on componentWillMount asynchronously.
Furthermore, regarding being "up-to-date" with data: this entirely depends on your application needs. A few ideas to help you get started exploring your problem domain:
With each request to get offers, also send or save a time stamp. Have the application decide when a time stamp is "too old" and request again.
Implement real time communication, for example socket.io which pushes the data to the client instead of the client requesting it.
Request the data at an interval suitable to your application. You could pass along the last time you requested the information and the server could decide if there is new data available or return an empty response in which case you display the existing data.

Resources