Updated April 6, 2023
Introduction to React Native Local Storage
Every major app needs local storage to store some information of the user locally even after getting offline, it helps in gathering each and every information once again when you login and provide the user a seamless experience. It also supports local storage for this purpose. We should not confuse the data stored with the state data as it is not a replacement for that. State Data always gets erased once the app is closed. Local Storage can also be achieved by Async Storage. AsyncStorage can also be used instead of Local Storage as its quite more efficient in terms of data storage mechanisms like the database systems. In this article, we will go through some of the examples on how to achieve local storage for our applications and a description.
Storing Data in React Native Local Storage with Examples
Storing data with examples are given below:
1. Simple React Native Local Storage
Components inside src folder:
- index.js
- styles.css
a) index.js
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const initialState = { color: "red", showEmoji: false };
class App extends React.Component { state = { ...initialState };
componentDidMount() { try {
const deserialisedState = JSON.parse( window.localStorage.getItem("state")
);
this.setState({ ...deserialisedState });
} catch (err) {}
}
componentDidUpdate() {
const serialisedState = JSON.stringify(this.state); window.localStorage.setItem("state", serialisedState);
}
toggleShowEmoji = () => {
this.setState({ showEmoji: !this.state.showEmoji });
};
handleChangeColor = e => {
this.setState({ color: e.target.value });
};
handleClearLocalStorage = () => { window.localStorage.clear();
};
render() { return (
<div className={`App ${this.state.color}`}>
<div className="flex">
<button className="flex-child" onClick={() => this.toggleShowEmoji()}>
{`Click Here to ${this.state.showEmoji ? "Hide" : "Display"}
Emoji`}{" "}
</button>
<div className="emoji">{`${this.state.showEmoji ? "" : ""}`}</div>
<label> The Drop-down Menu Helps in Selecting Background Colors</label>
<select onChange={e => this.handleChangeColor(e)}>
<option value="red">RED</option>
<option value="yellow">YELLOW</option>
<option value="purple">PURPLE</option>
</select>
<button
className="flex-child"
onClick={() => this.handleClearLocalStorage()}
>
For Clearing Local Storage, Click Here!
</button>
</div>
</div>
);
}
}
const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
b) styles.css
.App {
font-family: sans-serif; text-align: center; height: 100vh;
width: 100%;
}
.App.yellow {
background-image: linear-gradient(
to right bottom, #ffef0a, #f5ee2f, #def277, #f7f381,
#fcfaa9
);
}.App.red {
background-image: linear-gradient( to top,
#f50f07, #f20b07, #e64b49,
#f29c9b, #ffd0c9
);
}.App.purple {
background-image: linear-gradient( to top,
#9000ff,
#de58ed, #be7ae6, #bc8de0, #f6b8fc
);
}
.App.yellow {
background-image: linear-gradient( to right bottom,
#ffef0a, #f5ee2f, #def277, #f7f381,
#fcfaa9
);
}
.flex {
display: flex;
flex-direction: column; width: fit-content; margin: auto;
}
.flex-child {
margin: 3vh auto 6px auto; background-color: #b9fa8e; border-radius: 6px;
}
.emoji {
font-size: 40pt;
}
button {
font-size: 19px; padding: 6px 11px 6px;
}
button:hover {
background-color: #fc83aa; color: #fcf7f9;
transition: color 201ms ease-in, background-color 301ms ease-in;
Output:
2. Counter with Local Storage
Components inside src folder:
- index.js
- styles.css
a) index.js
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
console.log("new counter-example");
class Counter extends React.Component {
constructor(props) {
super(props);
this.handleAddOne = this.handleAddOne.bind(this); this.handleMinusOne = this.handleMinusOne.bind(this); this.handleReset = this.handleReset.bind(this); this.state = {
count: 0
};
}
componentDidMount() {
const stringCount = localStorage.getItem("count"); const count = parseInt(stringCount, 10);
if (!isNaN(count)) { this.setState(() => ({ count }));
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) { localStorage.setItem("count", this.state.count);}
}
handleAddOne() { this.setState(prevState => {
return {
count: prevState.count + 1
};
});
}
handleMinusOne() { this.setState(prevState => {
return {
count: prevState.count - 1
};
});
}
handleReset() { this.setState(() => {
return { count: 0
};
});
}
render(){
return (
<div class="container">
<h1>Count Value: {this.state.count}</h1>
<button onClick={this.handleMinusOne}>-1</button>
<button onClick={this.handleAddOne}>+1</button>
<button onClick={this.handleReset}>reset</button>
<p>Count will start from the Last Number you entered.</p>
<p>This is done, by storing data in your Local Storage.</p>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
b) styles.css
.container {
margin-top: 40px; margin-left: auto; margin-right: auto; width: 55%;
border: 16px solid#c4f772; padding: 10px;
text-align: center; background-color: #fff196;
}
Output:
3. Movie List with React Native Local Storage
Main files:
- header folder
- main folder
- movie folder
- App.js
- NotFound.js
- index.css
- index.html
- index.js
Components inside header folder:
- Header.css
- Header.js
Components inside main folder:
- movies folder
- navigation folder
- Main.css
- Main.js
Components inside movies folder:
- MovieListItem.css
- MovieListItem.js
- Movies.css
- Movies.js
Components inside navigation folder:
- Button.css
- Button.js
- Navigation.css
- Navigation.js
- Selection.css
- Selection.js
- Slider.css
- Slider.js
Components inside movie folder:
- LoadingMovie.js
- Movie.css
- Movie.js
a) Header.css
header { display: flex; height: 81px;
justify-content: center; align-items: center;
border-bottom: 2px solid #98bad4;
}
header h1 {
font-size: 2rem; color: #d4f23d;
}
b) Header.js
import React from "react"; import "./Header.css";
const Header = () => (
<header>
<h1>Movie Mannia</h1>
</header>
);
export default Header;
c) Main.css
.main {
display: flex;
}
d) Main.js
import React from "react";
import "./Main.css"
import Navigation from "./navigation/Navigation"; import Movies from "./movies/Movies";
class Main extends React.Component { state = {
movies: [], total_pages: 1,
page: 1,
url:
`https://api.themoviedb.org/3/genre/movie/list?api_key=651925d45022d1ae6580 63b443c99784&language=en-US`,
moviesUrl:
`https://api.themoviedb.org/3/discover/movie?api_key=651925d45022d1ae658063 b443c99784&language=en- US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1`,
genre: "Comedy", genres: [], year: {
label: "year", min: 1991,
max: 2020,
step: 1,
value: { min: 2001, max: 2020 }
},
rating: {
label: "rating", max: 10,
min: 0,
step: 1,
value: { min: 7, max: 10 }
},
runtime: {
label: "runtime", max: 300,
min: 0,
step: 20,
value: { min: 59, max: 119 }
}
}
componentDidMount(){
const savedState = this.getStateFromLocalStorage();
if ( !savedState || (savedState && !savedState.movies.length)) { this.fetchMovies(this.state.moviesUrl);
} else {
this.setState({ ...savedState }); this.generateUrl(savedState);
}
}
componentWillUpdate(nextProps, nextState) { this.saveStateToLocalStorage();
if (this.state.moviesUrl !== nextState.moviesUrl) { this.fetchMovies(nextState.moviesUrl);
}
if (this.state.page !== nextState.page) { this.generateUrl(nextState);
}
}
onGenreChange = event => {
this.setState({ genre: event.target.value });
}
setGenres = genres => { this.setState({genres});
}
onChange = data => { this.setState({
[data.type]: {
...this.state[data.type], value: data.value
}
});
};
generateUrl = params => {
const {genres, year, rating, runtime, page } = params; const selectedGenre = genres.find( genre => genre.name ===
params.genre);
const genreId = selectedGenre.id;
const moviesUrl = `https://api.themoviedb.org/3/discover/movie?` +
`api_key=651925d45022d1ae658063b443c99784&` +
`language=en-US&sort_by=popularity.desc&` +
`with_genres=${genreId}&` +
`primary_release_date.gte=${year.value.min}-01-01&` +
`primary_release_date.lte=${year.value.max}-12-31&` +
`vote_average.gte=${rating.value.min}&` +
`vote_average.lte=${rating.value.max}&` +
`with_runtime.gte=${runtime.value.min}&` +
`with_runtime.lte=${runtime.value.max}&` +
`page=${page}`;
this.setState({ moviesUrl });
}
onSearchButtonClick = () => { this.setState({page: 1}); this.generateUrl(this.state);
}
saveStateToLocalStorage = params => { localStorage.setItem("sweetpumpkins.params",
JSON.stringify(this.state));
}
getStateFromLocalStorage = () => {
return JSON.parse(localStorage.getItem("sweetpumpkins.params"));
}
fetchMovies = (url) => { fetch(url)
.then(response => response.json())
.then(data => this.storeMovies(data))
.catch(error => console.log(error));
}
storeMovies = data => {
const movies = data.results.map(result => {
const {
vote_count, id, genre_ids, poster_path, title, vote_average, release_date
} = result;
return { vote_count, id, genre_ids, poster_path, title, vote_average, release_date };
});
this.setState({ movies, total_pages: data.total_pages });
};
onPageIncrease = () => {
const { page, total_pages } = this.state const nextPage = page + 1;
if (nextPage <= total_pages) { this.setState({ page: nextPage })
}
}
onPageDecrease = () => {
const nextPage = this.state.page - 1; if (
nextPage
> 0 ) {
this.setState({ page: nextPage })
}
}
render() { return (
<section className="main">
<Navigation onChange={this.onChange}
onGenreChange={this.onGenreChange} setGenres={this.setGenres} onSearchButtonClick={this.onSearchButtonClick}
{...this.state} />
<Movies movies={this.state.movies} page={this.state.page}
onPageIncrease={this.onPageIncrease} onPageDecrease={this.onPageDecrease}
/>
</section>
)
}
}
export default Main;
e) MovieListItem.css
.movie-item {
flex-basis: 22%; display: flex;
flex-direction: column; list-style: none;
box-sizing: border-box;
margin: 1.5%;
border: 1px solid #ffffff;
box-shadow: 0 11px 29px -6px #050505;
}
.movie-item img {
width: 100%;
}
.thumbnail {
display: flex;
flex-direction: column; flex-grow: 1;
cursor: pointer;
text-decoration: none;
}
.movie-description {
display: flex; flex: 1 0 100%;
flex-direction: column; justify-content: space-between;
padding: 11px;
}
.movie-description h2 { color: #63bdc9; font-weight: bold;
margin-bottom: 21px;
}
.movie-details {
display: flex; margin-top: auto;
justify-content: space-between;
}
.movie-details span { color: #75d1ff; font-size: 0.9rem; font-weight: bold;
}
.movie-year, .movie-rating { display: flex;
flex-direction: column;
}
.movie-year .title, .movie-rating .title { color: #70a8c4;
margin-bottom: 6px; font-size: 0.66rem; font-weight: normal;
}
.movie-rating {
align-items: flex-end;
f) MovieListItem.js
import React from "react";
import "./MovieListItem.css";
import { Link } from "react-router-dom";
const MovieListItem = ({ movie }) => {
const { id, title, poster_path, release_date, vote_average } = movie; const imgUrl = `https://image.tmdb.org/t/p/w342/${poster_path}`; const year = release_date.substring(0, 4);
return (
<li className="movie-item">
<Link to={`/movie/${id}`} className="thumbnail">
<img src={imgUrl} alt={title} />
<div className="movie-description">
<h2>{title}</h2>
<section className="movie-details">
<div cassName="movie-year">
<span className="title">YEAR</span>
<span>{year}</span>
</div>
<div className="movie-rating">
<span className="title">RATING</span>
<span>{vote_average}</span>
</div>
</section>
</div>
</Link>
</li>
);
};
export default MovieListItem;
g) Movies.css
.movies {
flex-basis: 81%; display: flex; flex-wrap: wrap;
margin: 0;
padding: 21px 0;
}
.pagination { display: flex;
justify-content: space-between; padding: 41px 21px;
h) Movies.js
import React from "react"; import "./Movies.css";
import MovieListItem from "./MovieListItem"; import Button from "../navigation/Button";
const Movies = ({ movies,
page, onPageIncrease, onPageDecrease
}) => (
<section>
<ul className="movies">
{movies.map( movie => (
<MovieListItem key={movie.id} movie={movie} />
))}
</ul>
<div className="pagination">
<Button onClick={onPageDecrease}>PREVIOUS</Button>
<span>{`Page ${page}`}</span>
<Button onClick={onPageIncrease}>NEXT</Button>
</div>
</section>
)
export default Movies;
i) Button.css
.search-button { display: flex;
justify-content: center;
}
.search-button button { padding: 11px 22px; background: #c4709a; color: #fcfafb;
font-size: 1.1rem; cursor: pointer;
transition: color 0.21s ease-out; outline: 0;
border: 0;
}
.search-button button:hover { color:#ffffff;
}
j) Button.js
import React from "react"; import "./Button.css"
const Button = ({ onClick
, children }) => (
<div className="search-button">
<button onClick={onClick}>
{children}
</button>
</div>
)
export default Button;
k) Navigation.css
.navigation {
flex-basis: 21%; min-width: 301px; padding: 41px;
}
l) Navigation.js
import React from "react"; import "./Navigation.css";
import Selection from "./Selection"; import Slider from './Slider'; import Button from './Button'
class Navigation extends React.Component {
componentDidMount() { fetch(this.props.url)
.then(response => response.json())
.then(data => this.props.setGenres(data.genres))
.catch(error => console.log(error));
}
render() {
const { genre, genres, onGenreChange, onChange, year, rating, runtime, onSearchButtonClick } = this.props;
return (
<section className="navigation">
<Selection genre={genre} genres={genres}
onGenreChange={onGenreChange}
/>
<Slider data={year} onChange={onChange} />
<Slider data={rating} onChange={onChange} />
<Slider data={runtime} onChange={onChange} />
<Button onClick={onSearchButtonClick}> Search
</Button>
</section>
)
}
}
export default Navigation;
m) Selection.css
.selection { display: flex;
flex-direction: column; margin-bottom: 60px;
}
.selection label { font-size: 1.1rem; margin-bottom: 11px; color: #bda4a4;
}
.selection select { max-width: 151px;
}
n) Selection.js
import React from "react"; import "./Selection.css";
const Selection = ({genre, genres, onGenreChange }) => (
<div className="selection">
<label>Genre</label>
<select value={genre} onChange={onGenreChange}>
{ genres.map( genre => (
<option value={genre.name} key={genre.id}>{genre.name}</option>
))}
</select>
</div>
);
export default Selection;
o) Slider.css
.slider {
margin-bottom: 41px;
}
.slider label { color: #948890; font-size: 1.1rem;
margin-bottom: 21px; display: block;
text-transform: capitalize;
}
.input-range slider { background: #c466a2; border: none;
}
.input-range track { background: #f0e9e9;
}
.input-range track--active { background: #c466a2;
}
.input-range label--value .input-range label-container { background: #c466a2;
color: #ffffff; font-size: 0.66rem; padding: 3px 6px; border-radius: 3px;
}
.input-range label--min .input-range label-container,
.input-range label--max .input-range label-container
{
font-size: 0.68rem; color: #ada5ab; left: 0;
}
.input-range label--max .input-range label-container { left: 26%;
p) Slider.js
import React from "react";
import InputRange from "react-input-range";
import 'react-input-range/lib/css/index.css'; import "./Slider.css"
class Slider extends React.Component { onChange = range => {
this.props.onChange({
type: this.props.data.label, value: range
});
}
render() {
const { min, max, step, value, label } = this.props.data; return (
<div className="slider">
<label>{label}</label>
<InputRange minValue={min} maxValue={max} step={step}
onChange={this.onChange} value={value}
/>
</div>
)
}
}
export default Slider;
q) LoadingMovie.js
import React from "react";
const LoadingMovie = () => <h2>Movie Loading</h2>
export default LoadingMovie;
r) Movie.css
.movie-page {
display: flex;
flex-direction: column;
}
.movie-page h5 { color: #888;
font-weight: normal; line-height: 1.26rem;
}
.movie-page h5 span { font-size: inherit; font-weight: normal; color: #000000; padding-left: 1.1rem;
}
.movie-page h4 { margin: 0;
font-size: 1.26rem; line-height: 3;
}
.movie-page p { color: #bdb3b3; line-height: 1.56;
}
.movie-page .movie-image {
flex-basis: 100%;
height: 576px; background-size: cover;
background-position: center center;
}
.movie-page .movie-details { display: flex;
flex-direction: column; max-width: 801px;
margin: 21px auto 61px auto;
}
.movie-page .movie-details h1 { line-height: 1.56em;
}
.movie-page .movie-details h1 span { font-size: inherit;
font-weight: normal; padding-left: 1.1rem; color: #d9cece;
line-height: inherit;
}
.movie-page .genres { display: flex;
margin-bottom: 1.1rem;
}.movie-page .genres span { font-weight: normal;
}.movie-page .separator { color: #e8dfdf; padding: 0 11px;
}
s) Movie.js
import React from "react";
import LoadingMovie from "./LoadingMovie"; import "./Movie.css";
class Movie extends React.Component { state = {
isLoading: true, movie: {}
}
componentDidMount() {
const { movieId } = this.props.match.params; const movieUrl =
`https://api.themoviedb.org/3/movie/${movieId}?api_key=651925d45022d1ae6580 63b443c99784&language=en-US`;
fetch(movieUrl)
.then(response => response.json())
.then(data => {
this.setState({ movie: data, isLoading: false })
})
.catch(error => console.log("Error:", error));
}
render() {
const { isLoading } = this.state; const {
title, backdrop_path, release_date, genres, overview, vote_average, runtime
} = this.state.movie;
const year = release_date ? release_date.substring(0, 4) : null;
const backgroundStyle = { backgroundImage:
`url(http://image.tmdb.org/t/p/w1280/${backdrop_path})`
}
return (
<div className="movie-page">
{
isLoading
? <LoadingMovie />
: <div>
<div className="movie-image" style={backgroundStyle} />
<div className="movie-details">
<h1>
{title}
<span>({year})</span>
</h1>
<section className="genres">
{genres.map((genre, index) => (
<div key={genre.id}>
<span>{genre.name}</span>
{index < genres.length - 1 && (
<span className="separator">|</span>
)}
</div>
))}
</section>
<h5>
Ratings of Movie:
<span>{vote_average}</span>
</h5>
<h5>
Runtime of Movie:
<span>{`${runtime} min`}</span>
</h5>
<h4>Overview of Movie</h4>
<p>{overview}</p>
</div>
</div>
}
</div>
)
}
}
export default Movie;
t) App.js
import React from "react"; import { BrowserRouter
, Switch
, Route } from "react-router-dom" import Header from "./header/Header"; import Main from "./main/Main"; import Movie from "./movie/Movie"; import NotFound from "./NotFound";
const App = () => { return (
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path='/' component={Main} />
<Route path="/movie/:movieId" component={Movie} />
<Route component={NotFound} />
</ Switch>
</div>
</BrowserRouter>
);
};
export default App;
u) NotFound.js
import React from "react";
import { Link } from "react-router-dom";
const NotFound = () => (
<div>
<h3>Unable to find Movie that you are looking for :(</h3>
<Link to="/">Below is the List of Movies that you can browse</Link>
</div>
);
export default NotFound;
v) index.css
body {
margin: 0;
padding: 0;
font-family: sans-serif; box-sizing: border-box;
}
w) index.html
<div id="root"></div>
x) index.js
import React from 'react';
import ReactDOM from 'react-dom'; import App from './App';
import './index.css';
ReactDOM.render(<App />, document.getElementById("root"));
Output:
Recommended Articles
This is a guide to React Native Local Storage. Here we also discuss the introduction and storing data in react native local storage along with different examples and its code implementation. You may also have a look at the following articles to learn more –