The simplest way to persist React state is to use the localStorage. Few third-party packages handle this for you, but they also use the localStorage under the hood. So why rely on such libraries that may not be maintained down the road? Let's implement our strategy to persist the state on page reload.
import { useState } from "react"
export const App = () => {
const [name, setName] = useState<string|undefined>(localStorage.getItem("name") || undefined)
const handleFormChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setName(event.target.value)
localStorage.setItem("name", event.target.value );
}
return(
<div>
<div>React js window dimensions</h3></div>
<div>Name:<div><input value={name} onChange={(e) => handleFormChange(e)}/><div>
</div>
)
}
export default App
In the above example, we set the local storage with localStorage.setItem("name", event.target.value)
and read the local storage with localStorage.getItem("name")
. If you refresh the page, you will notice that the name will persist in the name input field.
This method is ok to persist a few input values. If we want to utilize this functionality throughout the application, we need a more scalable solution.
Persist React Js state with custom hook
Below is an example of local storage using react custom hooks.
export const useLocalStorage = (name: string): Function[] => {
const getLocalStorage = () => {
const local = localStorage.getItem(name)
if(local != null){
return JSON.parse(local)
}
return null
}
const setLocalStorage = (item: Object) => {
localStorage.setItem(name, JSON.stringify(item))
}
const removeLocalStorage = () => {
return localStorage.removeItem(name)
}
return [getLocalStorage, setLocalStorage, removeLocalStorage]
}
The hook provides three services.
The getLocalStorage
retrieves the data by calling localStorage.getItem()
. The setLocalStorage
sets the state to the local storage, and the removeLocalStorage
simply removes the data from the local storage.
import { useEffect, useState } from "react"
import { useLocalStorage } from "./components/useLocalStorage"
export const App = () => {
interface InputForm {
name: string
website: string
contact: Contact
}
interface Contact {
cell: string
email: string
}
let initialForm: InputForm = {
name: "",
website: "",
contact: {
cell: "",
email: ""
}
}
const [savedForm, setSavedForm, clearLocalStorage] = useLocalStorage("inputForm")
const [inputFormState, setInputFormState] = useState<InputForm>(savedForm() || initialForm)
const handleFormChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const {name, value} = event.target
if(name === "name" || name === "website"){
setInputFormState((prev) => {
const newForm = {...prev}
newForm[name] = value
return newForm
})
}
if(name === "cell" || name === "email"){
setInputFormState((prev) => {
let newForm:InputForm = {...prev}
newForm.contact[name] = value
return newForm
})
}
}
useEffect(() => {
setSavedForm(inputFormState)
},[setSavedForm,inputFormState])
return(
<div>
<div><h3>React js Local Storage</h3></div>
<div>Name:</div><div><input name="name" value={inputFormState?.name} onChange={(e) => handleFormChange(e)}/></div>
<div>Website:</div><div><input name="website" value={inputFormState?.website} onChange={(e) => handleFormChange(e)}/></div>
<div>Cell:</div><input name="cell" value={inputFormState?.contact?.cell} onChange={(e) => handleFormChange(e)}/></div>
<div>Email:</div><input name="email" value={inputFormState?.contact?.email} onChange={(e) => handleFormChange(e)}/></div>
<div><button onClick={()=>clearLocalStorage()}>Clear Cache</button></div>
</div>
)
}
export default App
Note that we initialize the inputFormState as
const [inputFormState, setInputFormState] = useState<InputForm>(savedForm() || initialForm)
.
This handles the initial page load without any local storage values present. If the local storage is not yet set, then the initial state is used to create the inputFormState
.
Then we call setSavedForm(inputFormState)
in the useEffect hook to persist any input values to the form.