| name | state-management |
| description | Redux Toolkit ile global state yönetimi kurulumu, slice oluşturma, typed hooks tanımlama ve RTK Query işlemlerinde devreye girer. Redux store, slice, selector, useAppSelector, useAppDispatch, createSlice, createSelector, createApi anahtar kelimeleri ile tetiklenir. |
| argument-hint | Hangi domain için state yönetimi oluşturulacak? (örn: cart, user, product) |
state-management Skill
React 18 + TypeScript + Vite projesinde Redux Toolkit (RTK) kullanarak kurumsal standartlara uygun global state yönetimi kurallarını tanımlar.
Bu skill tetiklendiğinde aşağıdaki tüm kurallar zorunlu olarak uygulanır.
1. BAĞIMLILIKLAR
npm install @reduxjs/toolkit react-redux
2. GENEL PRENSİPLER
| Kural | Açıklama |
|---|
| Redux Toolkit Only | Vanilla Redux yasak. Yalnızca @reduxjs/toolkit kullanılır. |
| Immutability | Immer entegrasyonu sayesinde slice içinde doğrudan state mutasyonu yazılabilir; RTK bunu immutable hale getirir. |
| Typed Hooks | useSelector / useDispatch doğrudan kullanımı yasak. useAppSelector / useAppDispatch zorunludur. |
| Single Source of Truth | Her domain için tek bir slice; cross-domain state paylaşımı yoktur. |
| Selector Separation | Selector fonksiyonları slice dışında *.selectors.ts dosyasına çıkarılır. |
| RTK Query for API | Sunucu state için RTK Query kullanılır; manuel fetch yasak. |
| Named Export | Her dosya export ile dışa aktarılır; export default yalnızca reducer için kullanılır. |
No any | TypeScript any tipi kesinlikle yasak. |
3. DOSYA & KLASÖR YAPISI
src/
└── store/
├── index.ts ← Store kurulumu + RootState / AppDispatch
├── hooks.ts ← useAppSelector, useAppDispatch
└── features/
└── cart/
├── cartSlice.ts ← Slice (reducers + actions)
├── cart.selectors.ts ← Memoized selector'lar
├── cart.types.ts ← Domain tipleri
└── index.ts ← Barrel export
4. STORE KURULUM ŞABLONU
src/store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from './features/cart/cartSlice';
export const store = configureStore({
reducer: {
cart: cartReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Kurallar:
RootState ve AppDispatch her zaman bu dosyadan export edilir.
- Yeni slice eklendiğinde yalnızca
reducer nesnesine eklenir; combineReducers manuel kullanımı yasak.
5. TYPED HOOKS ŞABLONU
src/store/hooks.ts
import { useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './index';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector = <T>(selector: (state: RootState) => T): T =>
useSelector<RootState, T>(selector);
6. SLICE ŞABLONU
src/store/features/cart/cartSlice.ts
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { CartItem, CartState } from './cart.types';
const initialState: CartState = {
items: [],
isLoading: false,
error: null,
};
export const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addItem(state, action: PayloadAction<CartItem>) {
const existing = state.items.find((i) => i.id === action.payload.id);
if (existing) {
existing.quantity += 1;
} else {
state.items.push({ ...action.payload, quantity: 1 });
}
},
removeItem(state, action: PayloadAction<string>) {
state.items = state.items.filter((i) => i.id !== action.payload);
},
clearCart(state) {
state.items = [];
},
},
});
export const { addItem, removeItem, clearCart } = cartSlice.actions;
export default cartSlice.reducer;
Kurallar:
initialState tipi her zaman explicit olarak tanımlanır.
PayloadAction<T> generic parametresi zorunludur.
- Slice ismi (
name) domain adıyla birebir aynı olur (camelCase).
7. TİP TANIMLARI ŞABLONU
src/store/features/cart/cart.types.ts
export interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
export interface CartState {
items: CartItem[];
isLoading: boolean;
error: string | null;
}
Kurallar:
- Slice state tipi
<Domain>State adlandırmasını izler.
- Entity tipi
<EntityName> adlandırmasını izler.
- Her prop için JSDoc comment zorunludur.
interface tercih edilir; yalnızca union/intersection gerektiğinde type kullanılır.
8. SELECTOR ŞABLONU
src/store/features/cart/cart.selectors.ts
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from '../../index';
export const selectCartItems = (state: RootState) => state.cart.items;
export const selectCartIsLoading = (state: RootState) => state.cart.isLoading;
export const selectCartError = (state: RootState) => state.cart.error;
export const selectCartTotal = createSelector(
selectCartItems,
(items) => items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
export const selectCartCount = createSelector(
selectCartItems,
(items) => items.reduce((sum, item) => sum + item.quantity, 0)
);
export const selectCartIsEmpty = createSelector(
selectCartItems,
(items) => items.length === 0
);
Kurallar:
- Temel (primitive) selector'lar plain fonksiyon olarak yazılır.
- Hesaplama gerektiren selector'lar
createSelector ile memoize edilir.
- Selector adları
select prefix'i ile başlar.
9. BARREL EXPORT ŞABLONU
src/store/features/cart/index.ts
export { addItem, removeItem, clearCart } from './cartSlice';
export {
selectCartItems,
selectCartTotal,
selectCartCount,
selectCartIsEmpty,
selectCartIsLoading,
selectCartError,
} from './cart.selectors';
export type { CartItem, CartState } from './cart.types';
10. PROVIDER KURULUMU
src/main.tsx güncellemesi
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>
);
11. COMPONENT İÇİNDE KULLANIM
import type { FC } from 'react';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { addItem, selectCartCount } from '../../store/features/cart';
import type { CartItem } from '../../store/features/cart';
interface AddToCartButtonProps {
item: CartItem;
}
export const AddToCartButton: FC<AddToCartButtonProps> = ({ item }) => {
const dispatch = useAppDispatch();
const count = useAppSelector(selectCartCount);
return (
<button type="button" onClick={() => dispatch(addItem(item))}>
Sepete Ekle ({count})
</button>
);
};
12. RTK QUERY API SLICE (Sunucu State)
Sunucu verisi için RTK Query kullanılır. Manuel fetch yasak.
src/store/api/productsApi.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import type { Product } from '../../types/product.types';
export const productsApi = createApi({
reducerPath: 'productsApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['Product'],
endpoints: (builder) => ({
getProducts: builder.query<Product[], void>({
query: () => '/products',
providesTags: ['Product'],
}),
getProductById: builder.query<Product, string>({
query: (id) => `/products/${id}`,
providesTags: (_result, _error, id) => [{ type: 'Product', id }],
}),
}),
});
export const { useGetProductsQuery, useGetProductByIdQuery } = productsApi;
Store'a Ekleme
import { productsApi } from './api/productsApi';
export const store = configureStore({
reducer: {
cart: cartReducer,
[productsApi.reducerPath]: productsApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(productsApi.middleware),
});
13. UYGULAMA ADIMLARI
Yeni bir domain için state yönetimi oluştururken bu sırayı takip et:
- Bağımlılıkları kur:
npm install @reduxjs/toolkit react-redux
src/store/index.ts → configureStore ile store oluştur, RootState ve AppDispatch export et
src/store/hooks.ts → useAppDispatch ve useAppSelector tanımla
src/store/features/<domain>/ klasörü oluştur
<domain>.types.ts → State ve entity tiplerini tanımla
<domain>Slice.ts → createSlice ile reducers + actions yaz
<domain>.selectors.ts → createSelector ile memoized selector'lar yaz
index.ts → Barrel export
src/store/index.ts → Yeni reducer'ı store'a ekle
src/main.tsx → <Provider store={store}> ile uygulamayı wrap et
- Sunucu verisi varsa
src/store/api/<domain>Api.ts → RTK Query API slice oluştur
14. YASAKLAR
| Yasak | Gerekçe |
|---|
useSelector / useDispatch doğrudan kullanımı | Typed hook'lar zorunlu |
combineReducers manuel kullanımı | configureStore.reducer nesnesi yeterli |
Slice dışında state mutasyonu | Immer yalnızca slice içinde çalışır |
any tipi | TypeScript strict kuralı |
Manuel fetch / axios sunucu state için | RTK Query zorunlu |
export default (reducer hariç) | Sadece slice reducer default export kullanır |