本文へ移動
cccskills
無料GitHub で公開

rn-state-and-storage

State management and storage choice in React Native. State libs work mostly the same as web (Zustand, Jotai, Redux Toolkit, TanStack Query, Context); storage choice is RN-specific — AsyncStorage, MMKV, SecureStore, Keychain. Hydration patterns, splash screen handling, secrets discipline. Use this skill to: - Pick state management lib (matches web React intuition). - Pick storage by sensitivity (AsyncStorage / MMKV / SecureStore / Keychain). - Hydrate state on app start without UI flicker. - Persist Zustand store across app restarts. - Avoid storing secrets in plaintext storage. Do NOT use this skill for: - General project conventions (see rn-conventions). - Platform-specific code (see rn-platform-specific). - Navigation (see rn-navigation). - Testing state/storage (see rn-testing).

インストール方法を見る

含まれるファイル(1)

  • SKILL.md9.9 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

State Management and Storage in RN

State management is largely identical to web React. Storage is the genuinely RN-specific concern.

State management — same as react-plugin

NeedTool
Local component stateuseState, useReducer
Shared between siblingsLift to common parent OR Context
App-wide UI state (theme, modals, sidebars)Context, Zustand, or Jotai
Server data with cachingTanStack Query, SWR
Complex domain stateRedux Toolkit (or Zustand for simpler cases)
Form statereact-hook-form

Same decision tree as web — see react-plugin:react-state-management skill for deep dive on each tool. RN-specific notes below.

Context in RN

Context survives Fast Refresh ONLY if values are not held in module-level closure. Store provider state in useState / useReducer inside the provider component, not in a top-level let.

// ✅ Survives Fast Refresh
export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>('light');
  return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
}

// ❌ Lost on Fast Refresh
let theme: Theme = 'light';
export const ThemeContext = createContext({ theme, setTheme: (t: Theme) => { theme = t; } });

Zustand with persistence

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';

export const useUserStore = create(
  persist(
    (set) => ({ user: null, setUser: (user: User | null) => set({ user }) }),
    {
      name: 'user-storage',
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

For sensitive data, swap AsyncStorage for SecureStore wrapper:

import * as SecureStore from 'expo-secure-store';

const secureStorage = {
  getItem: (key: string) => SecureStore.getItemAsync(key),
  setItem: (key: string, value: string) => SecureStore.setItemAsync(key, value),
  removeItem: (key: string) => SecureStore.deleteItemAsync(key),
};

storage: createJSONStorage(() => secureStorage),

Note: SecureStore values are limited to ~2KB on iOS. For larger sensitive data, encrypt with expo-crypto and store ciphertext in MMKV.

TanStack Query in RN

Works identically to web, but consider:

  • Network detection: @react-native-community/netinfo to gate queries when offline.
  • Persistence: @tanstack/react-query-persist-client + AsyncStorage/MMKV for offline-first apps.
  • Refetch on app foreground: TanStack Query has refetchOnWindowFocus (web); for RN, manually trigger via AppState listener.
import { AppState } from 'react-native';
import { focusManager } from '@tanstack/react-query';

useEffect(() => {
  const sub = AppState.addEventListener('change', (state) => {
    focusManager.setFocused(state === 'active');
  });
  return () => sub.remove();
}, []);

Storage choices

The RN-specific decision. Each has a place.

AsyncStorage (@react-native-async-storage/async-storage)

Async key-value, plaintext on disk. Most common, but NOT secure.

import AsyncStorage from '@react-native-async-storage/async-storage';

await AsyncStorage.setItem('@theme', 'dark');
const theme = await AsyncStorage.getItem('@theme');                 // string | null
await AsyncStorage.removeItem('@theme');
await AsyncStorage.multiGet(['@a', '@b']);                          // [[key, value], ...]
await AsyncStorage.multiSet([['@a', '1'], ['@b', '2']]);

Use for:

  • User preferences (theme, locale, sort orders).
  • Onboarding flags ("user has seen welcome").
  • Cached non-sensitive responses.

NEVER:

  • Auth tokens.
  • PII / health / financial data.
  • Anything regulated (GDPR-sensitive).

MMKV (react-native-mmkv)

Synchronous, fast (~30x faster than AsyncStorage), encrypted optional. Modern preferred for non-secrets.

import { MMKV } from 'react-native-mmkv';

const storage = new MMKV();
storage.set('theme', 'dark');                   // sync, no await needed
const theme = storage.getString('theme');        // 'dark' or undefined
storage.delete('theme');
storage.contains('theme');                       // boolean
storage.getAllKeys();                            // string[]

Optional encryption:

const storage = new MMKV({
  id: 'user-storage',
  encryptionKey: 'a-secret-key-from-secure-store',
});

Use for:

  • User preferences (faster than AsyncStorage).
  • Frequently-read state (synchronous).
  • Encrypted-at-rest data (when paired with key from SecureStore).

Caveat: requires JSI / native build — works in dev-client, EAS Build, bare. NOT in Expo Go (managed without dev client).

SecureStore (expo-secure-store)

Keychain (iOS) / EncryptedSharedPreferences (Android). For sensitive data.

import * as SecureStore from 'expo-secure-store';

await SecureStore.setItemAsync('refresh-token', token);
const token = await SecureStore.getItemAsync('refresh-token');
await SecureStore.deleteItemAsync('refresh-token');

Use for:

  • Auth tokens (JWT, refresh tokens, OAuth).
  • API keys that must persist across app launches (rare — prefer not persisting).
  • Biometric-protected secrets.

Limits:

  • ~2KB per value on iOS (Keychain limit).
  • Async API only.
  • Expo SDK package — works in managed and bare via Expo modules.

Keychain (react-native-keychain)

Bare alternative to SecureStore. Same purpose, more configuration options (access groups, biometric prompts).

import * as Keychain from 'react-native-keychain';

await Keychain.setGenericPassword('username', 'password');
const credentials = await Keychain.getGenericPassword();
if (credentials) console.log(credentials.username, credentials.password);
await Keychain.resetGenericPassword();

For finer control:

await Keychain.setInternetCredentials('api.example.com', 'username', 'token', {
  accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET,
  accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});

Use in bare projects or when SecureStore's API is too limited.

Decision matrix

DataStorage
Auth tokens (JWT, refresh)SecureStore / Keychain
OAuth tokensSecureStore / Keychain
User credentials (rare — prefer not storing)Keychain with biometric
Theme preferenceMMKV or AsyncStorage
LocaleMMKV or AsyncStorage
Onboarding seen flagMMKV or AsyncStorage
Cached server data (offline-first)TanStack Query persist + MMKV/AsyncStorage
Large blobs (images, files)expo-file-system (managed) or react-native-fs (bare)
Database-like queriesexpo-sqlite, WatermelonDB, op-sqlite

Hydration on app start

Read storage, populate state, hide splash screen — in that order.

Expo + expo-splash-screen

// app/_layout.tsx (Expo Router) or App.tsx
import * as SplashScreen from 'expo-splash-screen';
import { useEffect, useState } from 'react';
import { useUserStore } from '@/stores/userStore';

SplashScreen.preventAutoHideAsync();

export default function RootLayout() {
  const [hydrated, setHydrated] = useState(false);
  const restoreUser = useUserStore((s) => s.restoreUser);

  useEffect(() => {
    (async () => {
      await restoreUser();
      setHydrated(true);
    })();
  }, [restoreUser]);

  useEffect(() => {
    if (hydrated) SplashScreen.hideAsync();
  }, [hydrated]);

  if (!hydrated) return null;
  return <App />;
}

Zustand persist hydration

Zustand persist middleware exposes hasHydrated:

const { isHydrated } = useUserStore.persist;
if (!isHydrated()) return <SplashScreen />;

Or subscribe via useStore selector:

const hasHydrated = useUserStore((s) => s._hasHydrated);

Bare RN

No Expo splash. Use react-native-bootsplash or implement custom splash UI.

Sensitive data handling rules

  1. JWT and refresh tokens: SecureStore / Keychain only. Never AsyncStorage.
  2. Don't log secrets: redact tokens before any console.log / analytics call.
  3. Clear on logout: explicitly delete all sensitive keys.
  4. Don't sync sensitive data via Settings.app / Cloud Backup: configure SecureStore with keychainService and access group flags.
  5. Encrypt large sensitive blobs: MMKV with encryption key from SecureStore (because SecureStore has size limits).

Anti-patterns

  • ❌ Storing JWT in AsyncStorage — readable by malicious apps with root/jailbreak access.
  • ❌ Persisting entire Redux store including server data — bloated AsyncStorage, slow restore.
  • ❌ Calling AsyncStorage in loops — batch with multiGet / multiSet.
  • ❌ Forgetting to await AsyncStorage operations — silent races.
  • ❌ Module-level mutable state — breaks Fast Refresh and SSR (RN Web).
  • ❌ Logging secrets via console.log for "debugging".
  • ❌ Mixing storage libs without a clear rationale (e.g., MMKV for some, AsyncStorage for others on the same app).
  • ❌ Showing UI before hydration completes — flicker or wrong-state render.
  • ❌ Storing user-modifiable data in Constants.expoConfig.extra — that's read-only build-time config, not runtime state.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Test NgRx Effects with provideMockActions. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing patterns broadly (see angular-routing — covers testing routes briefly). - Form patterns broadly (see angular-forms).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

AratKruglik のスキルをすべて見る

このスキルの問題を報告する