fixed perf issue related to too many rerenders

rerenders were caused by strict equality check on object/array picks
switched artistInfo to new store
updated zustand and fixed deprecation warnings
This commit is contained in:
austinried
2022-03-19 09:52:01 +09:00
parent 47c65ea8cb
commit 13af6555d3
15 changed files with 108 additions and 142 deletions

View File

@@ -1,9 +1,7 @@
import { CacheImageSize, CacheItemTypeKey } from '@app/models/cache' import { CacheImageSize, CacheItemTypeKey } from '@app/models/cache'
import { ArtistInfo } from '@app/models/music'
import { selectCache } from '@app/state/cache' import { selectCache } from '@app/state/cache'
import { selectMusic } from '@app/state/music'
import { selectSettings } from '@app/state/settings' import { selectSettings } from '@app/state/settings'
import { useStore, Store } from '@app/state/store' import { Store, useStore, useStoreDeep } from '@app/state/store'
import { useCallback, useEffect } from 'react' import { useCallback, useEffect } from 'react'
const useFileRequest = (key: CacheItemTypeKey, id: string) => { const useFileRequest = (key: CacheItemTypeKey, id: string) => {
@@ -61,28 +59,25 @@ export const useCoverArtFile = (coverArt = '-1', size: CacheImageSize = 'thumbna
export const useArtistArtFile = (artistId: string, size: CacheImageSize = 'thumbnail') => { export const useArtistArtFile = (artistId: string, size: CacheImageSize = 'thumbnail') => {
const type: CacheItemTypeKey = size === 'original' ? 'artistArt' : 'artistArtThumb' const type: CacheItemTypeKey = size === 'original' ? 'artistArt' : 'artistArtThumb'
const fetchArtistInfo = useStore(selectMusic.fetchArtistInfo) const fetchArtistInfo = useStore(store => store.fetchLibraryArtistInfo)
const artistInfo = useStoreDeep(store => store.entities.artistInfo[artistId])
const { file, request } = useFileRequest(type, artistId) const { file, request } = useFileRequest(type, artistId)
const cacheItem = useStore(selectCache.cacheItem) const cacheItem = useStore(selectCache.cacheItem)
useEffect(() => { useEffect(() => {
if (!file) { if (!artistInfo) {
cacheItem(type, artistId, async () => { fetchArtistInfo(artistId)
let artistInfo: ArtistInfo | undefined return
const cachedArtistInfo = useStore.getState().artistInfo[artistId]
if (cachedArtistInfo) {
artistInfo = cachedArtistInfo
} else {
artistInfo = await fetchArtistInfo(artistId)
} }
if (!file) {
cacheItem(type, artistId, async () => {
return type === 'artistArtThumb' ? artistInfo?.smallImageUrl : artistInfo?.largeImageUrl return type === 'artistArtThumb' ? artistInfo?.smallImageUrl : artistInfo?.largeImageUrl
}) })
} }
// intentionally leaving file out so it doesn't re-render if the request fails // intentionally leaving file out so it doesn't re-render if the request fails
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [artistId, cacheItem, fetchArtistInfo, type]) }, [artistId, cacheItem, fetchArtistInfo, type, artistInfo])
return { file, request } return { file, request }
} }

View File

@@ -1,10 +1,10 @@
import { selectMusic } from '@app/state/music' import { selectMusic } from '@app/state/music'
import { Store, useStore } from '@app/state/store' import { Store, useStore, useStoreDeep } from '@app/state/store'
import { useCallback, useEffect } from 'react' import { useCallback, useEffect } from 'react'
export const useArtistInfo = (id: string) => { export const useArtistInfo = (id: string) => {
const artistInfo = useStore(useCallback((state: Store) => state.artistInfo[id], [id])) const artistInfo = useStoreDeep(useCallback(store => store.entities.artistInfo[id], [id]))
const fetchArtistInfo = useStore(selectMusic.fetchArtistInfo) const fetchArtistInfo = useStore(store => store.fetchLibraryArtistInfo)
useEffect(() => { useEffect(() => {
if (!artistInfo) { if (!artistInfo) {

View File

@@ -1,4 +1,4 @@
import { getCurrentTrack, getPlayerState, TrackExt, trackPlayerCommands } from '@app/state/trackplayer' import { getCurrentTrack, getPlayerState, trackPlayerCommands } from '@app/state/trackplayer'
import TrackPlayer, { Event, State } from 'react-native-track-player' import TrackPlayer, { Event, State } from 'react-native-track-player'
import { useStore } from './state/store' import { useStore } from './state/store'
import { unstable_batchedUpdates } from 'react-native' import { unstable_batchedUpdates } from 'react-native'
@@ -44,13 +44,12 @@ let serviceCreated = false
const createService = async () => { const createService = async () => {
useStore.subscribe( useStore.subscribe(
(currentTrack?: TrackExt) => { state => state.currentTrack?.id,
if (currentTrack) { (currentTrackId?: string) => {
useStore.getState().scrobbleTrack(currentTrack.id) if (currentTrackId) {
useStore.getState().scrobbleTrack(currentTrackId)
} }
}, },
state => state.currentTrack,
(prev, next) => prev?.id === next?.id,
) )
NetInfo.fetch().then(state => { NetInfo.fetch().then(state => {

View File

@@ -6,7 +6,7 @@ import Header from '@app/components/Header'
import HeaderBar from '@app/components/HeaderBar' import HeaderBar from '@app/components/HeaderBar'
import ListItem from '@app/components/ListItem' import ListItem from '@app/components/ListItem'
import { Album, Song } from '@app/models/music' import { Album, Song } from '@app/models/music'
import { useStore } from '@app/state/store' import { useStore, useStoreDeep } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer' import { selectTrackPlayer } from '@app/state/trackplayer'
import colors from '@app/styles/colors' import colors from '@app/styles/colors'
import dimensions from '@app/styles/dimensions' import dimensions from '@app/styles/dimensions'
@@ -70,7 +70,7 @@ const TopSongs = React.memo<{
const ArtistAlbums = React.memo<{ const ArtistAlbums = React.memo<{
id: string id: string
}>(({ id }) => { }>(({ id }) => {
const albums = useStore( const albums = useStoreDeep(
useCallback( useCallback(
store => { store => {
const ids = store.entities.artistAlbums[id] const ids = store.entities.artistAlbums[id]
@@ -114,8 +114,8 @@ const ArtistViewFallback = React.memo(() => (
)) ))
const ArtistView = React.memo<{ id: string; title: string }>(({ id, title }) => { const ArtistView = React.memo<{ id: string; title: string }>(({ id, title }) => {
const artist = useStore(useCallback(store => store.entities.artists[id], [id])) const artist = useStoreDeep(useCallback(store => store.entities.artists[id], [id]))
const artistInfo = useStore(useCallback(store => store.entities.artistInfo[id], [id])) const artistInfo = useStoreDeep(useCallback(store => store.entities.artistInfo[id], [id]))
const fetchArtist = useStore(store => store.fetchLibraryArtist) const fetchArtist = useStore(store => store.fetchLibraryArtist)
const fetchArtistInfo = useStore(store => store.fetchLibraryArtistInfo) const fetchArtistInfo = useStore(store => store.fetchLibraryArtistInfo)

View File

@@ -3,21 +3,20 @@ import CoverArt from '@app/components/CoverArt'
import GradientScrollView from '@app/components/GradientScrollView' import GradientScrollView from '@app/components/GradientScrollView'
import Header from '@app/components/Header' import Header from '@app/components/Header'
import NothingHere from '@app/components/NothingHere' import NothingHere from '@app/components/NothingHere'
import { useFetchPaginatedList } from '@app/hooks/list'
import { useActiveServerRefresh } from '@app/hooks/server' import { useActiveServerRefresh } from '@app/hooks/server'
import { AlbumListItem } from '@app/models/music' import { AlbumListItem } from '@app/models/music'
import { Album, mapById } from '@app/state/library' import { mapById } from '@app/state/library'
import { selectMusic } from '@app/state/music'
import { selectSettings } from '@app/state/settings' import { selectSettings } from '@app/state/settings'
import { Store, useStore } from '@app/state/store' import { useStore, useStoreDeep } from '@app/state/store'
import colors from '@app/styles/colors' import colors from '@app/styles/colors'
import font from '@app/styles/font' import font from '@app/styles/font'
import { GetAlbumList2Params, GetAlbumList2TypeBase, GetAlbumListType } from '@app/subsonic/params' import { GetAlbumList2TypeBase, GetAlbumListType } from '@app/subsonic/params'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import equal from 'fast-deep-equal/es6/react'
import produce from 'immer' import produce from 'immer'
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react' import React, { useCallback, useState } from 'react'
import { RefreshControl, ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native' import { RefreshControl, ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native'
import create from 'zustand' import create, { StateSelector } from 'zustand'
const titles: { [key in GetAlbumListType]?: string } = { const titles: { [key in GetAlbumListType]?: string } = {
recent: 'Recently Played', recent: 'Recently Played',
@@ -55,8 +54,8 @@ const AlbumItem = React.memo<{
const Category = React.memo<{ const Category = React.memo<{
type: string type: string
}>(({ type }) => { }>(({ type }) => {
const list = useHomeStore(useCallback(store => store.lists[type] || [], [type])) const list = useHomeStoreDeep(useCallback(store => store.lists[type] || [], [type]))
const albums = useStore(useCallback(store => mapById(store.entities.albums, list), [list])) const albums = useStoreDeep(useCallback(store => mapById(store.entities.albums, list), [list]))
const Albums = () => ( const Albums = () => (
<ScrollView <ScrollView
@@ -90,7 +89,7 @@ interface HomeState {
setList: (type: string, list: string[]) => void setList: (type: string, list: string[]) => void
} }
const useHomeStore = create<HomeState>((set, get) => ({ const useHomeStore = create<HomeState>(set => ({
lists: {}, lists: {},
setList: (type, list) => { setList: (type, list) => {
@@ -102,6 +101,10 @@ const useHomeStore = create<HomeState>((set, get) => ({
}, },
})) }))
function useHomeStoreDeep<U>(stateSelector: StateSelector<HomeState, U>) {
return useHomeStore(stateSelector, equal)
}
const Home = () => { const Home = () => {
const [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
const types = useStore(selectSettings.homeLists) const types = useStore(selectSettings.homeLists)
@@ -113,9 +116,7 @@ const Home = () => {
await Promise.all( await Promise.all(
types.map(async type => { types.map(async type => {
console.log('fetch', type)
const ids = await fetchAlbumList({ type: type as GetAlbumList2TypeBase, size: 20, offset: 0 }) const ids = await fetchAlbumList({ type: type as GetAlbumList2TypeBase, size: 20, offset: 0 })
console.log('set', type)
setList(type, ids) setList(type, ids)
}), }),
) )

View File

@@ -6,13 +6,12 @@ import { useFetchPaginatedList } from '@app/hooks/list'
import { Album, AlbumListItem } from '@app/models/music' import { Album, AlbumListItem } from '@app/models/music'
import { mapById } from '@app/state/library' import { mapById } from '@app/state/library'
import { selectSettings } from '@app/state/settings' import { selectSettings } from '@app/state/settings'
import { Store, useStore } from '@app/state/store' import { useStore, useStoreDeep } from '@app/state/store'
import colors from '@app/styles/colors' import colors from '@app/styles/colors'
import font from '@app/styles/font' import font from '@app/styles/font'
import { GetAlbumList2Params, GetAlbumList2Type } from '@app/subsonic/params' import { GetAlbumList2Params, GetAlbumList2Type } from '@app/subsonic/params'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import pick from 'lodash.pick' import React, { useCallback } from 'react'
import React, { useCallback, useEffect } from 'react'
import { StyleSheet, Text, useWindowDimensions, View } from 'react-native' import { StyleSheet, Text, useWindowDimensions, View } from 'react-native'
const AlbumItem = React.memo<{ const AlbumItem = React.memo<{
@@ -97,7 +96,7 @@ const AlbumsList = () => {
) )
const { list, refreshing, refresh, fetchNextPage } = useFetchPaginatedList(fetchPage, 300) const { list, refreshing, refresh, fetchNextPage } = useFetchPaginatedList(fetchPage, 300)
const albums = useStore(useCallback(store => mapById(store.entities.albums, list), [list])) const albums = useStoreDeep(useCallback(store => mapById(store.entities.albums, list), [list]))
const layout = useWindowDimensions() const layout = useWindowDimensions()

View File

@@ -5,7 +5,7 @@ import { useFetchList2 } from '@app/hooks/list'
import { Artist } from '@app/models/music' import { Artist } from '@app/models/music'
import { ArtistFilterType } from '@app/models/settings' import { ArtistFilterType } from '@app/models/settings'
import { selectSettings } from '@app/state/settings' import { selectSettings } from '@app/state/settings'
import { Store, useStore } from '@app/state/store' import { useStore, useStoreDeep } from '@app/state/store'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { StyleSheet, View } from 'react-native' import { StyleSheet, View } from 'react-native'
@@ -19,12 +19,10 @@ const filterOptions: OptionData[] = [
{ text: 'Random', value: 'random' }, { text: 'Random', value: 'random' },
] ]
const selectArtists = (store: Store) => store.entities.artists
const ArtistsList = () => { const ArtistsList = () => {
const fetchArtists = useStore(store => store.fetchLibraryArtists) const fetchArtists = useStore(store => store.fetchLibraryArtists)
const { refreshing, refresh } = useFetchList2(fetchArtists) const { refreshing, refresh } = useFetchList2(fetchArtists)
const artists = useStore(selectArtists) const artists = useStoreDeep(store => store.entities.artists)
const filter = useStore(selectSettings.libraryArtistFilter) const filter = useStore(selectSettings.libraryArtistFilter)
const setFilter = useStore(selectSettings.setLibraryArtistFiler) const setFilter = useStore(selectSettings.setLibraryArtistFiler)

View File

@@ -1,10 +1,9 @@
import GradientFlatList from '@app/components/GradientFlatList' import GradientFlatList from '@app/components/GradientFlatList'
import ListItem from '@app/components/ListItem' import ListItem from '@app/components/ListItem'
import { useFetchList, useFetchList2 } from '@app/hooks/list' import { useFetchList2 } from '@app/hooks/list'
import { PlaylistListItem } from '@app/models/music' import { PlaylistListItem } from '@app/models/music'
import { selectMusic } from '@app/state/music' import { useStore, useStoreDeep } from '@app/state/store'
import { useStore } from '@app/state/store' import React from 'react'
import React, { useCallback, useState } from 'react'
import { StyleSheet } from 'react-native' import { StyleSheet } from 'react-native'
const PlaylistRenderItem: React.FC<{ item: PlaylistListItem }> = ({ item }) => ( const PlaylistRenderItem: React.FC<{ item: PlaylistListItem }> = ({ item }) => (
@@ -14,7 +13,7 @@ const PlaylistRenderItem: React.FC<{ item: PlaylistListItem }> = ({ item }) => (
const PlaylistsList = () => { const PlaylistsList = () => {
const fetchPlaylists = useStore(store => store.fetchLibraryPlaylists) const fetchPlaylists = useStore(store => store.fetchLibraryPlaylists)
const { refreshing, refresh } = useFetchList2(fetchPlaylists) const { refreshing, refresh } = useFetchList2(fetchPlaylists)
const playlists = useStore(store => store.entities.playlists) const playlists = useStoreDeep(store => store.entities.playlists)
return ( return (
<GradientFlatList <GradientFlatList

View File

@@ -5,13 +5,12 @@ import ImageGradientFlatList from '@app/components/ImageGradientFlatList'
import ListItem from '@app/components/ListItem' import ListItem from '@app/components/ListItem'
import ListPlayerControls from '@app/components/ListPlayerControls' import ListPlayerControls from '@app/components/ListPlayerControls'
import { useCoverArtFile } from '@app/hooks/cache' import { useCoverArtFile } from '@app/hooks/cache'
import { useAlbumWithSongs, usePlaylistWithSongs } from '@app/hooks/music' import { usePlaylistWithSongs } from '@app/hooks/music'
import { Album, AlbumWithSongs, PlaylistListItem, PlaylistWithSongs, Song } from '@app/models/music' import { Album, PlaylistListItem, Song } from '@app/models/music'
import { useStore } from '@app/state/store' import { useStore, useStoreDeep } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer' import { selectTrackPlayer } from '@app/state/trackplayer'
import colors from '@app/styles/colors' import colors from '@app/styles/colors'
import font from '@app/styles/font' import font from '@app/styles/font'
import pick from 'lodash.pick'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native' import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
@@ -139,8 +138,8 @@ const AlbumView = React.memo<{
}>(({ id, title }) => { }>(({ id, title }) => {
// const album = useAlbumWithSongs(id) // const album = useAlbumWithSongs(id)
const album = useStore(useCallback(store => store.entities.albums[id], [id])) const album = useStoreDeep(useCallback(store => store.entities.albums[id], [id]))
const songs = useStore( const songs = useStoreDeep(
useCallback( useCallback(
store => { store => {
const ids = store.entities.albumSongs[id] const ids = store.entities.albumSongs[id]

View File

@@ -20,16 +20,6 @@ export type CacheDirsByServer = Record<string, Record<CacheItemTypeKey, string>>
export type CacheFilesByServer = Record<string, Record<CacheItemTypeKey, Record<string, CacheFile>>> export type CacheFilesByServer = Record<string, Record<CacheItemTypeKey, Record<string, CacheFile>>>
export type CacheRequestsByServer = Record<string, Record<CacheItemTypeKey, Record<string, CacheRequest>>> export type CacheRequestsByServer = Record<string, Record<CacheItemTypeKey, Record<string, CacheRequest>>>
// export type DownloadedItemsByServer = Record<
// string,
// {
// songs: { [songId: string]: DownloadedSong }
// albums: { [albumId: string]: DownloadedAlbum }
// artists: { [songId: string]: DownloadedArtist }
// playlists: { [playlistId: string]: DownloadedPlaylist }
// }
// >
export type CacheSlice = { export type CacheSlice = {
cacheItem: ( cacheItem: (
key: CacheItemTypeKey, key: CacheItemTypeKey,

View File

@@ -166,10 +166,6 @@ export type LibrarySlice = {
albums: ById<Album> albums: ById<Album>
albumSongs: OneToMany albumSongs: OneToMany
// todo: remove these and store in component state
albumsList: PaginatedList
albumsListSize: number
playlists: ById<Playlist> playlists: ById<Playlist>
playlistSongs: OneToMany playlistSongs: OneToMany
@@ -216,8 +212,6 @@ const defaultEntities = () => ({
artistNameTopSongs: {}, artistNameTopSongs: {},
albums: {}, albums: {},
albumsList: {},
albumsListSize: 300,
albumSongs: {}, albumSongs: {},
playlists: {}, playlists: {},

View File

@@ -1,11 +1,12 @@
import { createMusicSlice, MusicSlice } from '@app/state/music' import { createMusicSlice, MusicSlice } from '@app/state/music'
import { createSettingsSlice, SettingsSlice } from '@app/state/settings' import { createSettingsSlice, SettingsSlice } from '@app/state/settings'
import AsyncStorage from '@react-native-async-storage/async-storage' import AsyncStorage from '@react-native-async-storage/async-storage'
import create from 'zustand' import equal from 'fast-deep-equal/es6/react'
import { persist, StateStorage } from 'zustand/middleware' import create, { GetState, Mutate, SetState, StateSelector, StoreApi } from 'zustand'
import { persist, subscribeWithSelector } from 'zustand/middleware'
import { CacheSlice, createCacheSlice } from './cache' import { CacheSlice, createCacheSlice } from './cache'
import migrations from './migrations'
import { createLibrarySlice, LibrarySlice } from './library' import { createLibrarySlice, LibrarySlice } from './library'
import migrations from './migrations'
import { createMusicMapSlice, MusicMapSlice } from './musicmap' import { createMusicMapSlice, MusicMapSlice } from './musicmap'
import { createTrackPlayerSlice, TrackPlayerSlice } from './trackplayer' import { createTrackPlayerSlice, TrackPlayerSlice } from './trackplayer'
import { createTrackPlayerMapSlice, TrackPlayerMapSlice } from './trackplayermap' import { createTrackPlayerMapSlice, TrackPlayerMapSlice } from './trackplayermap'
@@ -23,25 +24,13 @@ export type Store = SettingsSlice &
setHydrated: (hydrated: boolean) => void setHydrated: (hydrated: boolean) => void
} }
const storage: StateStorage = { export const useStore = create<
getItem: async name => { Store,
try { SetState<Store>,
return await AsyncStorage.getItem(name) GetState<Store>,
} catch (err) { Mutate<StoreApi<Store>, [['zustand/subscribeWithSelector', never], ['zustand/persist', Partial<Store>]]>
console.error(`getItem error (key: ${name})`, err) >(
return null subscribeWithSelector(
}
},
setItem: async (name, item) => {
try {
await AsyncStorage.setItem(name, item)
} catch (err) {
console.error(`setItem error (key: ${name})`, err)
}
},
}
export const useStore = create<Store>(
persist( persist(
(set, get) => ({ (set, get) => ({
...createSettingsSlice(set, get), ...createSettingsSlice(set, get),
@@ -58,8 +47,9 @@ export const useStore = create<Store>(
{ {
name: '@appStore', name: '@appStore',
version: DB_VERSION, version: DB_VERSION,
getStorage: () => storage, getStorage: () => AsyncStorage,
whitelist: ['settings', 'cacheFiles'], // whitelist: ['settings', 'cacheFiles'],
partialize: state => ({ settings: state.settings, cacheFiles: state.cacheFiles }),
onRehydrateStorage: _preState => { onRehydrateStorage: _preState => {
return async (postState, _error) => { return async (postState, _error) => {
await postState?.setActiveServer(postState.settings.activeServer, true) await postState?.setActiveServer(postState.settings.activeServer, true)
@@ -79,4 +69,7 @@ export const useStore = create<Store>(
}, },
}, },
), ),
),
) )
export const useStoreDeep = <U>(stateSelector: StateSelector<Store, U>) => useStore(stateSelector, equal)

View File

@@ -17,14 +17,12 @@ export const selectTrackPlayerMap = {
export const createTrackPlayerMapSlice = (set: SetState<Store>, get: GetState<Store>): TrackPlayerMapSlice => ({ export const createTrackPlayerMapSlice = (set: SetState<Store>, get: GetState<Store>): TrackPlayerMapSlice => ({
mapSongtoTrackExt: async song => { mapSongtoTrackExt: async song => {
let artwork = require('@res/fallback.png') let artwork = require('@res/fallback.png')
// if (song.coverArt) { if (song.coverArt) {
// const filePath = await get().fetchCoverArtFilePath(song.coverArt) const filePath = await get().fetchCoverArtFilePath(song.coverArt)
// if (filePath) { if (filePath) {
// artwork = filePath artwork = filePath
// } }
// } }
console.log('mapping', song.title)
return { return {
id: song.id, id: song.id,

View File

@@ -29,6 +29,7 @@
"@react-navigation/native": "^5.9.4", "@react-navigation/native": "^5.9.4",
"@types/react": "^17", "@types/react": "^17",
"@xmldom/xmldom": "^0.7.0", "@xmldom/xmldom": "^0.7.0",
"fast-deep-equal": "^3.1.3",
"immer": "^9.0.6", "immer": "^9.0.6",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
@@ -50,7 +51,7 @@
"react-native-vector-icons": "^8.1.0", "react-native-vector-icons": "^8.1.0",
"react-native-webview": "^11.13.0", "react-native-webview": "^11.13.0",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"zustand": "^3.5.7" "zustand": "^3.7.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.9", "@babel/core": "^7.12.9",

View File

@@ -7629,7 +7629,7 @@ yargs@^16.1.1:
y18n "^5.0.5" y18n "^5.0.5"
yargs-parser "^20.2.2" yargs-parser "^20.2.2"
zustand@^3.5.7: zustand@^3.7.1:
version "3.6.9" version "3.7.1"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.6.9.tgz#f61a756ddea9f95c7ee7cfd3af2f88c10078afbc" resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.7.1.tgz#7388f0a7175a6c2fd9a2880b383a4bf6cdf6b7c6"
integrity sha512-OvDNu/jEWpRnEC7k8xh8GKjqYog7td6FZrLMuHs/IeI8WhrCwV+FngVuwMIFhp5kysZXr6emaeReMqjLGaldAQ== integrity sha512-wHBCZlKj+bg03/hP+Tzv24YhnqqP8MCeN9ECPDXoF01062SIbnfl3j9O0znkDw1lNTY0a8WN3F///a0UhhaEqg==