├── .babelrc ├── .gitignore ├── .npmignore ├── .prettierrc ├── LICENSE ├── README.md ├── package.json ├── src └── index.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["expo"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | dist/ 5 | .vscode 6 | yarn-error.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | /example 4 | /node_modules 5 | /__tests__ 6 | .vscode 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "jsxBracketSameLine": true, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-present, Evan Bacon. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-pantry-storage 2 | 3 | Persist values in `AsyncStorage` with expiration dates. 4 | 5 | Just like with a real pantry all items have an expiration date, when they expire you throw them out! 6 | 7 | ## Installation 8 | 9 | ```bash 10 | yarn add react-native-pantry-storage 11 | 12 | or 13 | 14 | npm install --save react-native-pantry-storage 15 | ``` 16 | 17 | ### Usage 18 | 19 | Import the library into your JavaScript file: 20 | 21 | ```js 22 | import { 23 | setItemWithExpirationAsync, 24 | getItemWithExpirationAsync, 25 | getExpirationDateAsync, 26 | createDate, 27 | removeItemAsync, 28 | } from 'react-native-pantry-storage'; 29 | ``` 30 | 31 | #### Example 32 | 33 | Here we want to return the user's favorite song. If they're like me, this changes daily. If they use the app more than once in a 24 hour period then we don't want to ask then again. 34 | 35 | > In reality I use this API for storing API data so I can throttle how often I call remote end-points (because I'm cheap). 36 | 37 | ```js 38 | import { 39 | setItemWithExpirationAsync, 40 | getItemWithExpirationAsync, 41 | getExpirationDateAsync, 42 | createDate, 43 | removeItemAsync, 44 | } from 'react-native-pantry-storage'; 45 | 46 | const KEY = '@COOL_APP:MY_FAVORITE_SONG'; 47 | 48 | async function getFavoriteSongAsync() { 49 | // Check the cache. This is basically like invoking AsyncStorage but if the data expired then it gets thrown out and `null` is returned. 50 | const favoriteSong = await getItemWithExpirationAsync(KEY); 51 | if (favoriteSong != null) { 52 | const expiration = await getExpirationDateAsync(KEY); 53 | console.log('Seconds remaining before stale: ', new Date(expiration - new Date()).getSeconds()); 54 | return favoriteSong; 55 | } 56 | 57 | // Mock method for getting data... 58 | const newFavoriteSong = await promptUserForCurrentFavoriteSongAsync(); 59 | 60 | // Save the value for a day (I change up quickly) 61 | await setItemWithExpirationAsync(KEY, newFavoriteSong, { hours: 24 }); 62 | 63 | return newFavoriteSong; 64 | } 65 | 66 | console.log('You currently like this song:', await getFavoriteSongAsync()); 67 | ``` 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-pantry-storage", 3 | "version": "1.0.1", 4 | "sideEffects": false, 5 | "private": false, 6 | "description": "Persist values in `AsyncStorage` with expiration dates.", 7 | "author": "Evan Bacon (https://expo.io/)", 8 | "homepage": "https://github.com/evanbacon/react-native-pantry-storage", 9 | "readmeFilename": "README.md", 10 | "license": "MIT", 11 | "main": "src/index.js", 12 | "keywords": [ 13 | "expo", 14 | "react-native-web", 15 | "react-native", 16 | "asyncstorage", 17 | "storage", 18 | "utils", 19 | "react", 20 | "ssr" 21 | ], 22 | "scripts": { 23 | "jest": "jest" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/evanbacon/react-native-pantry-storage.git" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/evanbacon/react-native-pantry-storage/issues" 31 | }, 32 | "directories": { 33 | "lib": "src" 34 | }, 35 | "peerDependencies": { 36 | "react-native": "*", 37 | "prop-types": "*", 38 | "fbjs": "*" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { AsyncStorage } from 'react-native'; 2 | 3 | export async function setItemWithExpirationAsync( 4 | key, 5 | value, 6 | { milliseconds, seconds, minutes, hours, time } 7 | ) { 8 | // set expire at 9 | const _value = { value, expireAt: createDate({ milliseconds, seconds, minutes, hours, time }) }; 10 | // stringify object 11 | const objectToStore = JSON.stringify(_value); 12 | // store object 13 | return AsyncStorage.setItem(key, objectToStore); 14 | } 15 | 16 | export async function getItemWithExpirationAsync(key) { 17 | let data; 18 | await AsyncStorage.getItem(key, async (err, value) => { 19 | if (err) throw err; 20 | data = JSON.parse(value); 21 | // there is data in cache && cache is expired 22 | if (data !== null && data.expireAt && new Date(data.expireAt) < new Date()) { 23 | // clear cache 24 | AsyncStorage.removeItem(key); 25 | // update res to be null 26 | data = null; 27 | } 28 | }); 29 | if (data) { 30 | return data.value; 31 | } 32 | return null; 33 | } 34 | 35 | export async function getExpirationDateAsync(key) { 36 | let data = null; 37 | await AsyncStorage.getItem(key, async (err, value) => { 38 | if (err) throw err; 39 | data = JSON.parse(value); 40 | if (data !== null && data.expireAt && new Date(data.expireAt)) { 41 | data = new Date(data.expireAt); 42 | } 43 | }); 44 | return data; 45 | } 46 | 47 | export function createDate({ milliseconds, seconds, minutes, hours, time }) { 48 | const now = new Date(); 49 | const expireTime = new Date(now); 50 | 51 | if (typeof milliseconds !== 'undefined') { 52 | expireTime.setMilliseconds(now.getMilliseconds() + milliseconds); 53 | } 54 | if (typeof seconds !== 'undefined') { 55 | expireTime.setSeconds(now.getSeconds() + seconds); 56 | } 57 | if (typeof minutes !== 'undefined') { 58 | expireTime.setMinutes(now.getMinutes() + minutes); 59 | } 60 | if (typeof hours !== 'undefined') { 61 | expireTime.setHours(now.getHours() + hours); 62 | } 63 | if (typeof time !== 'undefined') { 64 | expireTime.setTime(time); 65 | } 66 | 67 | return expireTime; 68 | } 69 | 70 | export async function removeItemAsync(key) { 71 | return await AsyncStorage.removeItem(key); 72 | } 73 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | --------------------------------------------------------------------------------