first interactions with db (state cache)

now we're cookin' with custom hooks
This commit is contained in:
austinried 2021-06-17 09:25:02 +09:00
parent 6fb4b30294
commit 2a6821c25d
6 changed files with 221 additions and 19 deletions

21
App.tsx
View File

@ -1,24 +1,11 @@
import React from 'react';
import { RecoilRoot } from 'recoil';
import Layout from './src/components/NowPlayingLayout';
import ArtistsList from './src/components/ArtistsList';
const App = () => {
return (
const App = () => (
<RecoilRoot>
<Layout />
<ArtistsList />
</RecoilRoot>
);
}
);
export default App;
import { SubsonicApiClient } from './src/subsonic/client';
import md5 from 'md5';
const password = 'test';
const salt = 'salty';
const token = md5(password + salt);
const client = new SubsonicApiClient('http://navidrome.home', 'austin', token, salt);
client.getIndexes();

28
package-lock.json generated
View File

@ -11,6 +11,7 @@
"md5": "^2.3.0",
"react": "17.0.1",
"react-native": "0.64.1",
"react-native-sqlite-storage": "^5.0.0",
"recoil": "^0.3.1",
"xmldom": "^0.5.0"
},
@ -21,6 +22,7 @@
"@types/jest": "^26.0.23",
"@types/md5": "^2.3.0",
"@types/react-native": "^0.64.5",
"@types/react-native-sqlite-storage": "^5.0.0",
"@types/react-test-renderer": "^16.9.2",
"@types/xmldom": "^0.1.30",
"babel-jest": "^26.6.3",
@ -3127,6 +3129,12 @@
"@types/react": "*"
}
},
"node_modules/@types/react-native-sqlite-storage": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/react-native-sqlite-storage/-/react-native-sqlite-storage-5.0.0.tgz",
"integrity": "sha512-kn5J+oLU//Jk9YshL9bcrm1Pxy4fp/7Pk8+yGZIeu/aEG8SmG75nxDzpfTopEluThDtjPDxFUWVDD0Ij/eCNJg==",
"dev": true
},
"node_modules/@types/react-test-renderer": {
"version": "16.9.5",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.5.tgz",
@ -10640,6 +10648,14 @@
"nullthrows": "^1.1.1"
}
},
"node_modules/react-native-sqlite-storage": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-native-sqlite-storage/-/react-native-sqlite-storage-5.0.0.tgz",
"integrity": "sha512-c1Joq3/tO1nmIcP8SkRZNolPSbfvY8uZg5lXse0TmjIPC0qHVbk96IMvWGyly1TmYCIpxpuDRc0/xCffDbYIvg==",
"peerDependencies": {
"react-native": ">=0.14.0"
}
},
"node_modules/react-native/node_modules/ws": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
@ -15673,6 +15689,12 @@
"@types/react": "*"
}
},
"@types/react-native-sqlite-storage": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/react-native-sqlite-storage/-/react-native-sqlite-storage-5.0.0.tgz",
"integrity": "sha512-kn5J+oLU//Jk9YshL9bcrm1Pxy4fp/7Pk8+yGZIeu/aEG8SmG75nxDzpfTopEluThDtjPDxFUWVDD0Ij/eCNJg==",
"dev": true
},
"@types/react-test-renderer": {
"version": "16.9.5",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.5.tgz",
@ -21418,6 +21440,12 @@
"nullthrows": "^1.1.1"
}
},
"react-native-sqlite-storage": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-native-sqlite-storage/-/react-native-sqlite-storage-5.0.0.tgz",
"integrity": "sha512-c1Joq3/tO1nmIcP8SkRZNolPSbfvY8uZg5lXse0TmjIPC0qHVbk96IMvWGyly1TmYCIpxpuDRc0/xCffDbYIvg==",
"requires": {}
},
"react-refresh": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",

View File

@ -13,6 +13,7 @@
"md5": "^2.3.0",
"react": "17.0.1",
"react-native": "0.64.1",
"react-native-sqlite-storage": "^5.0.0",
"recoil": "^0.3.1",
"xmldom": "^0.5.0"
},
@ -23,6 +24,7 @@
"@types/jest": "^26.0.23",
"@types/md5": "^2.3.0",
"@types/react-native": "^0.64.5",
"@types/react-native-sqlite-storage": "^5.0.0",
"@types/react-test-renderer": "^16.9.2",
"@types/xmldom": "^0.1.30",
"babel-jest": "^26.6.3",

View File

@ -0,0 +1,53 @@
import React from 'react';
import { Button, FlatList, Text, View } from 'react-native';
import { useRecoilValue, useResetRecoilState } from 'recoil';
import { artistsState, ArtistState, useUpdateArtists } from '../state/artists';
const ArtistItem: React.FC<{ item: ArtistState } > = ({ item }) => (
<View>
<Text>{item.id}</Text>
<Text style={{
fontSize: 60,
paddingBottom: 400,
}}>{item.name}</Text>
</View>
);
const List = () => {
const artists = useRecoilValue(artistsState);
const renderItem: React.FC<{ item: ArtistState }> = ({ item }) => (
<ArtistItem item={item} />
);
return (
<FlatList
data={artists}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
);
}
const ArtistsList = () => {
const resetArtists = useResetRecoilState(artistsState);
const updateArtists = useUpdateArtists();
return (
<View>
<Button
title='Reset to default'
onPress={resetArtists}
/>
<Button
title='Update from API'
onPress={updateArtists}
/>
<React.Suspense fallback={<Text>Loading...</Text>}>
<List />
</React.Suspense>
</View>
);
}
export default ArtistsList;

67
src/db/client.ts Normal file
View File

@ -0,0 +1,67 @@
import SQLite, { DatabaseParams, ResultSet, SQLiteDatabase } from 'react-native-sqlite-storage';
SQLite.enablePromise(true);
abstract class DbStorage {
private dbParams: DatabaseParams;
constructor(dbParams: DatabaseParams) {
this.dbParams = dbParams;
}
abstract createDb(): Promise<void>
private async openDb(): Promise<SQLiteDatabase> {
return await SQLite.openDatabase({ ...this.dbParams });
}
async deleteDb(): Promise<void> {
await SQLite.deleteDatabase({ ...this.dbParams });
}
async executeSql(sql: string, params?: any[]): Promise<[ResultSet]> {
const db = await this.openDb();
try {
// https://github.com/andpor/react-native-sqlite-storage/issues/410
// if (params) {
// for (const p of params) {
// if (Array.isArray(p)) {
// throw new Error('param value cannot be an array');
// }
// }
// }
return await db.executeSql(sql, params);
} catch (err) {
try { await db.close(); } catch {}
throw err;
} finally {
try { await db.close(); } catch {}
}
}
}
export class MusicDb extends DbStorage {
constructor() {
super({ name: 'music.db', location: 'default' });
}
async createDb(): Promise<void> {
await this.executeSql(`
CREATE TABLE artists (
id PRIMARY KEY NOT NULL,
name NOT NULL,
starred
);
`);
}
}
class SettingsDb extends DbStorage {
constructor() {
super({ name: 'settings.db', location: 'Library' });
}
async createDb(): Promise<void> {
return;
}
}

65
src/state/artists.ts Normal file
View File

@ -0,0 +1,65 @@
import md5 from 'md5';
import { atom, selector, useSetRecoilState } from 'recoil';
import { SubsonicApiClient } from '../subsonic/client';
import { MusicDb } from '../db/client';
const db = new MusicDb();
const password = 'test';
const salt = 'salty';
const token = md5(password + salt);
const client = new SubsonicApiClient('http://navidrome.home', 'austin', token, salt);
export interface ArtistState {
id: string;
name: string;
}
export const artistsState = atom<ArtistState[]>({
key: 'artistsState',
default: selector({
key: 'artistsState/default',
get: async () => {
await prepopulate();
const results = await db.executeSql(`
SELECT * FROM artists;
`);
return results[0].rows.raw().map(i => ({
id: i.id,
name: i.name,
}));
},
}),
});
export const useUpdateArtists = () => {
const setArtists = useSetRecoilState(artistsState);
return async () => {
const response = await client.getArtists();
setArtists(response.data.artists.map(i => ({
id: i.id,
name: i.name,
})));
};
};
async function prepopulate() {
try { await db.deleteDb() } catch {}
await db.createDb();
await db.executeSql(`
INSERT INTO artists (id, name, starred)
VALUES (?, ?, ?);
`,
[1, 'good guy', true]
);
await db.executeSql(`
INSERT INTO artists (id, name, starred)
VALUES (?, ?, ?);
`,
[2, 'bad guy', false]
);
}