switch to jotai

This commit is contained in:
austinried 2021-06-28 11:12:19 +09:00
parent 17f5639aef
commit 4606586102
13 changed files with 110 additions and 151 deletions

View File

@ -1,17 +1,17 @@
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { RecoilRoot } from 'recoil';
import SplashPage from './src/components/SplashPage';
import RootNavigator from './src/components/navigation/RootNavigator';
import { Provider } from 'jotai';
const App = () => (
<RecoilRoot>
<Provider>
<SplashPage>
<NavigationContainer>
<RootNavigator />
</NavigationContainer>
</SplashPage>
</RecoilRoot>
</Provider>
);
export default App;

View File

@ -1,11 +1,6 @@
import 'react-native-gesture-handler';
import 'react-native-get-random-values';
import { AppRegistry, LogBox } from 'react-native';
// ignore recoil's timer warning on android:
// https://github.com/facebookexperimental/Recoil/issues/1030
LogBox.ignoreLogs(["timer"]);
import { enableScreens } from 'react-native-screens';
enableScreens();

86
package-lock.json generated
View File

@ -14,6 +14,7 @@
"@react-navigation/material-top-tabs": "^5.3.15",
"@react-navigation/native": "^5.9.4",
"@react-navigation/stack": "^5.14.5",
"jotai": "^1.1.0",
"md5": "^2.3.0",
"react": "17.0.1",
"react-native": "0.64.1",
@ -27,7 +28,6 @@
"react-native-screens": "^3.4.0",
"react-native-tab-view": "^2.16.0",
"react-native-track-player": "^1.2.7",
"recoil": "^0.3.1",
"uuid": "^8.3.2",
"xmldom": "^0.5.0"
},
@ -6116,11 +6116,6 @@
"dev": true,
"optional": true
},
"node_modules/hamt_plus": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
"integrity": "sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE="
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -8461,6 +8456,47 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/jotai": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jotai/-/jotai-1.1.0.tgz",
"integrity": "sha512-+c/bMxS/c1LgsOYVfevOYLAu+WEgn85TJhmBoNSNHjMV+mTKZY0ACSyCrX1yz8KialK4+Ggm6d7Ek3sTXy8UgA==",
"engines": {
"node": ">=12"
},
"peerDependencies": {
"@urql/core": "*",
"immer": "*",
"optics-ts": "*",
"react": ">=16.8",
"react-query": "*",
"valtio": "*",
"wonka": "*",
"xstate": "*"
},
"peerDependenciesMeta": {
"@urql/core": {
"optional": true
},
"immer": {
"optional": true
},
"optics-ts": {
"optional": true
},
"react-query": {
"optional": true
},
"valtio": {
"optional": true
},
"wonka": {
"optional": true
},
"xstate": {
"optional": true
}
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -10772,25 +10808,6 @@
"node": ">= 0.10"
}
},
"node_modules/recoil": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.3.1.tgz",
"integrity": "sha512-KNA3DRqgxX4rRC8E7fc6uIw7BACmMPuraIYy+ejhE8tsw7w32CetMm8w7AMZa34wzanKKkev3vl3H7Z4s0QSiA==",
"dependencies": {
"hamt_plus": "1.0.2"
},
"peerDependencies": {
"react": ">=16.13.1"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -17936,11 +17953,6 @@
"dev": true,
"optional": true
},
"hamt_plus": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
"integrity": "sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE="
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -19673,6 +19685,12 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"jotai": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jotai/-/jotai-1.1.0.tgz",
"integrity": "sha512-+c/bMxS/c1LgsOYVfevOYLAu+WEgn85TJhmBoNSNHjMV+mTKZY0ACSyCrX1yz8KialK4+Ggm6d7Ek3sTXy8UgA==",
"requires": {}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -21513,14 +21531,6 @@
"resolve": "^1.1.6"
}
},
"recoil": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.3.1.tgz",
"integrity": "sha512-KNA3DRqgxX4rRC8E7fc6uIw7BACmMPuraIYy+ejhE8tsw7w32CetMm8w7AMZa34wzanKKkev3vl3H7Z4s0QSiA==",
"requires": {
"hamt_plus": "1.0.2"
}
},
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",

View File

@ -16,6 +16,7 @@
"@react-navigation/material-top-tabs": "^5.3.15",
"@react-navigation/native": "^5.9.4",
"@react-navigation/stack": "^5.14.5",
"jotai": "^1.1.0",
"md5": "^2.3.0",
"react": "17.0.1",
"react-native": "0.64.1",
@ -29,7 +30,6 @@
"react-native-screens": "^3.4.0",
"react-native-tab-view": "^2.16.0",
"react-native-track-player": "^1.2.7",
"recoil": "^0.3.1",
"uuid": "^8.3.2",
"xmldom": "^0.5.0"
},

View File

@ -1,8 +1,8 @@
import React from 'react';
import { Button, FlatList, Text, View } from 'react-native';
import { useRecoilValue, useResetRecoilState } from 'recoil';
import { FlatList, Text, View } from 'react-native';
import { useAtomValue } from 'jotai/utils';
import { Artist } from '../models/music';
import { artistsState } from '../state/music';
import { artistsAtom } from '../state/music';
const ArtistItem: React.FC<{ item: Artist } > = ({ item }) => (
<View>
@ -15,7 +15,7 @@ const ArtistItem: React.FC<{ item: Artist } > = ({ item }) => (
);
const List = () => {
const artists = useRecoilValue(artistsState);
const artists = useAtomValue(artistsAtom);
const renderItem: React.FC<{ item: Artist }> = ({ item }) => (
<ArtistItem item={item} />
@ -30,24 +30,10 @@ const List = () => {
);
}
const ListPlusControls = () => {
const resetArtists = useResetRecoilState(artistsState);
return (
<View>
<Button
title='Reset to default'
onPress={resetArtists}
/>
<List />
</View>
);
}
const ArtistsList = () => (
<View>
<React.Suspense fallback={<Text>Loading...</Text>}>
<ListPlusControls />
<List />
</React.Suspense>
</View>
)

View File

@ -1,10 +1,10 @@
import { useNavigation } from '@react-navigation/core';
import { useAtom } from 'jotai';
import md5 from 'md5';
import React from 'react';
import { Button, Text, View } from 'react-native';
import { useRecoilState } from 'recoil';
import { v4 as uuidv4 } from 'uuid';
import { appSettingsState } from '../state/settings';
import { appSettingsAtom } from '../state/settings';
const TestControls = () => {
const navigation = useNavigation();
@ -19,7 +19,7 @@ const TestControls = () => {
}
const ServerSettingsView = () => {
const [appSettings, setAppSettings] = useRecoilState(appSettingsState);
const [appSettings, setAppSettings] = useAtom(appSettingsAtom);
const bootstrapServer = () => {
if (appSettings.servers.length !== 0) {

View File

@ -1,10 +1,10 @@
import { useAtomValue } from 'jotai/utils';
import React, { useEffect } from 'react';
import { FlatList, Text, View } from 'react-native';
import FastImage from 'react-native-fast-image';
import LinearGradient from 'react-native-linear-gradient';
import { useRecoilValue } from 'recoil';
import { Album } from '../../models/music';
import { albumsState, albumsUpdatingState, useUpdateAlbums } from '../../state/music';
import { albumsAtom, albumsUpdatingAtom, useUpdateAlbums } from '../../state/music';
import colors from '../../styles/colors';
import textStyles from '../../styles/text';
import TopTabContainer from '../common/TopTabContainer';
@ -89,8 +89,8 @@ const AlbumListRenderItem: React.FC<{ item: Album }> = ({ item }) => (
);
const AlbumsList = () => {
const albums = useRecoilValue(albumsState);
const updating = useRecoilValue(albumsUpdatingState);
const albums = useAtomValue(albumsAtom);
const updating = useAtomValue(albumsUpdatingAtom);
const updateAlbums = useUpdateAlbums();
useEffect(() => {

View File

@ -1,8 +1,8 @@
import { useAtomValue } from 'jotai/utils';
import React, { useEffect } from 'react';
import { FlatList, Image, Text, View } from 'react-native';
import { useRecoilValue } from 'recoil';
import { Artist } from '../../models/music';
import { artistsState, artistsUpdatingState, useUpdateArtists } from '../../state/music';
import { artistsAtom, artistsUpdatingAtom, useUpdateArtists } from '../../state/music';
import textStyles from '../../styles/text';
import TopTabContainer from '../common/TopTabContainer';
@ -28,8 +28,8 @@ const ArtistItem: React.FC<{ item: Artist } > = ({ item }) => (
);
const ArtistsList = () => {
const artists = useRecoilValue(artistsState);
const updating = useRecoilValue(artistsUpdatingState);
const artists = useAtomValue(artistsAtom);
const updating = useAtomValue(artistsUpdatingAtom);
const updateArtists = useUpdateArtists();
useEffect(() => {

View File

@ -1,27 +1,21 @@
import { atom, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { atom, useAtom } from 'jotai';
import { useAtomValue, useUpdateAtom } from 'jotai/utils';
import { Album, Artist } from '../models/music';
import { SubsonicApiClient } from '../subsonic/api';
import { activeServer } from './settings';
import { activeServerAtom } from './settings';
export const artistsState = atom<Artist[]>({
key: 'artistsState',
default: [],
});
export const artistsUpdatingState = atom<boolean>({
key: 'artistsUpdatingState',
default: false,
});
export const artistsAtom = atom<Artist[]>([]);
export const artistsUpdatingAtom = atom(false);
export const useUpdateArtists = () => {
const server = useRecoilValue(activeServer);
const server = useAtomValue(activeServerAtom);
const [updating, setUpdating] = useAtom(artistsUpdatingAtom);
const setArtists = useUpdateAtom(artistsAtom);
if (!server) {
return () => Promise.resolve();
}
const [updating, setUpdating] = useRecoilState(artistsUpdatingState);
const setArtists = useSetRecoilState(artistsState);
return async () => {
if (updating) {
return;
@ -42,25 +36,18 @@ export const useUpdateArtists = () => {
}
}
export const albumsState = atom<Album[]>({
key: 'albumsState',
default: [],
});
export const albumsUpdatingState = atom<boolean>({
key: 'albumsUpdatingState',
default: false,
});
export const albumsAtom = atom<Album[]>([]);
export const albumsUpdatingAtom = atom(false);
export const useUpdateAlbums = () => {
const server = useRecoilValue(activeServer);
const server = useAtomValue(activeServerAtom);
const [updating, setUpdating] = useAtom(albumsUpdatingAtom);
const setAlbums = useUpdateAtom(albumsAtom);
if (!server) {
return () => Promise.resolve();
}
const [updating, setUpdating] = useRecoilState(albumsUpdatingState);
const setAlbums = useSetRecoilState(albumsState);
return async () => {
if (updating) {
return;

View File

@ -1,28 +1,12 @@
import { atom, DefaultValue, selector } from 'recoil';
import { AppSettings, Server } from '../models/settings';
import { getAppSettings, setAppSettings } from '../storage/settings';
import { atom } from 'jotai';
import { AppSettings } from '../models/settings';
import atomWithAsyncStorage from '../storage/atomWithAsyncStorage';
export const appSettingsState = atom<AppSettings>({
key: 'appSettingsState',
default: selector({
key: 'appSettingsState/default',
get: () => getAppSettings(),
}),
effects_UNSTABLE: [
({ onSet }) => {
onSet((newValue) => {
if (!(newValue instanceof DefaultValue)) {
setAppSettings(newValue);
}
});
}
],
export const appSettingsAtom = atomWithAsyncStorage<AppSettings>('@appSettings', {
servers: [],
});
export const activeServer = selector<Server | undefined>({
key: 'activeServer',
get: ({get}) => {
const appSettings = get(appSettingsState);
return appSettings.servers.find(x => x.id == appSettings.activeServer);
}
export const activeServerAtom = atom((get) => {
const appSettings = get(appSettingsAtom);
return appSettings.servers.find(x => x.id == appSettings.activeServer);
});

View File

@ -1,26 +1,28 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
export async function getItem(key: string): Promise<string | null> {
export async function getItem(key: string): Promise<any | null> {
try {
return await AsyncStorage.getItem(key);
const item = await AsyncStorage.getItem(key);
return item ? JSON.parse(item) : null;
} catch (e) {
console.error(`getItem error (key: ${key})`, e);
return null;
}
}
export async function multiGet(keys: string[]): Promise<[string, string | null][]> {
export async function multiGet(keys: string[]): Promise<[string, any | null][]> {
try {
return await AsyncStorage.multiGet(keys);
const items = await AsyncStorage.multiGet(keys);
return items.map(x => [x[0], x[1] ? JSON.parse(x[1]) : null]);
} catch (e) {
console.error(`multiGet error`, e);
return [];
}
}
export async function setItem(key: string, item: string): Promise<void> {
export async function setItem(key: string, item: any): Promise<void> {
try {
await AsyncStorage.setItem(key, item);
await AsyncStorage.setItem(key, JSON.stringify(item));
} catch (e) {
console.error(`setItem error (key: ${key})`, e);
}
@ -28,7 +30,7 @@ export async function setItem(key: string, item: string): Promise<void> {
export async function multiSet(items: string[][]): Promise<void> {
try {
await AsyncStorage.multiSet(items);
await AsyncStorage.multiSet(items.map(x => [x[0], JSON.stringify(x[1])]));
} catch (e) {
console.error(`multiSet error`, e);
}

View File

@ -0,0 +1,10 @@
import { atomWithStorage } from 'jotai/utils';
import { getItem, setItem } from './asyncstorage';
export default <T>(key: string, defaultValue: T) => {
return atomWithStorage<T>(key, defaultValue, {
getItem: async () => await getItem(key) || defaultValue,
setItem: setItem,
delayInit: true,
});
}

View File

@ -1,15 +0,0 @@
import { AppSettings } from '../models/settings';
import { getItem, setItem } from './asyncstorage';
const appSettingsKey = '@appSettings';
export async function getAppSettings(): Promise<AppSettings> {
const item = await getItem(appSettingsKey);
return item ? JSON.parse(item) : {
servers: [],
};
}
export async function setAppSettings(appSettings: AppSettings): Promise<void> {
await setItem(appSettingsKey, JSON.stringify(appSettings));
}