Documentation Index
Fetch the complete documentation index at: https://mintlify.com/BhushanBadhe39/SkinFirts/llms.txt
Use this file to discover all available pages before exploring further.
SkinFirts manages all shared application state through a single Redux store built with @reduxjs/toolkit. The store is configured in src/redux/Store.js and contains two slices: user (authentication state) and doctors (the doctor catalogue with async loading support). Components interact with the store using useSelector to read state and useDispatch to trigger actions or thunks.
Store Configuration
src/redux/Store.js wires the two reducers together into a single configureStore call. This store is wrapped around the app’s component tree using a <Provider> at the root.
import { configureStore } from "@reduxjs/toolkit";
import userReducer from './Slices/UserSlice';
import doctorsReducer from './Slices/DoctorSlice';
export const store = configureStore({
reducer: {
user: userReducer,
doctors: doctorsReducer,
}
});
| Reducer key | Slice file | Responsibility |
|---|
user | UserSlice.js | Authenticated user object and login flag |
doctors | DoctorSlice.js | Doctor list, loading state, and favourite toggling |
UserSlice
src/redux/Slices/UserSlice.js handles all state related to the currently authenticated user. It exposes two synchronous actions: setUser (called after a successful login or sign-up) and clearUser (called on logout).
src/redux/Slices/UserSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
user: null,
isLoggedIn: false,
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUser(state, action) {
state.user = action.payload;
state.isLoggedIn = true;
},
clearUser(state) {
state.user = null;
state.isLoggedIn = false;
},
},
});
export const { setUser, clearUser } = userSlice.actions;
export default userSlice.reducer;
State Shape
{
user: UserObject | null, // full user record from MockAPI, or null when logged out
isLoggedIn: boolean // true after setUser, false after clearUser
}
Actions
setUser(user: UserObject)
Sets state.user to the provided user object and flips state.isLoggedIn to true. This action is dispatched by signUpUser, loginUser, and editUserDetails in authService.js.
clearUser()
Resets the slice to its initialState, setting state.user to null and state.isLoggedIn to false. Dispatched by logoutUser in authService.js.
Selectors
// Read the authenticated user object
const user = useSelector(state => state.user.user);
// Check whether a user session is active
const isLoggedIn = useSelector(state => state.user.isLoggedIn);
Dispatch Examples
import { useDispatch } from 'react-redux';
import { setUser, clearUser } from '../redux/Slices/UserSlice';
const dispatch = useDispatch();
// After a successful login / sign-up
dispatch(setUser(userData));
// On logout
dispatch(clearUser());
You should not call setUser or clearUser directly from components. Use the authService functions (signUpUser, loginUser, editUserDetails, logoutUser) instead — they handle the AsyncStorage sync automatically before dispatching.
DoctorSlice
src/redux/Slices/DoctorSlice.js manages the list of doctors fetched from MockAPI along with async loading status. It uses two createAsyncThunk thunks for data fetching and favourite toggling, and relies entirely on extraReducers to handle the resulting lifecycle actions.
src/redux/Slices/DoctorSlice.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { fetchDoctors, updateDoctors } from '../../api/doctorsApi';
export const loadDoctors = createAsyncThunk(
'doctors/load',
async () => {
const data = await fetchDoctors();
return data;
}
);
export const toggleFavorite = createAsyncThunk(
'doctors/toggleFavorite',
async (doctor) => {
const updated = await updateDoctors(doctor.id, { isFavorite: !doctor.isFavorite });
return updated;
}
);
const doctorsSlice = createSlice({
name: 'doctors',
initialState: {
data: [],
loading: false,
error: null,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(loadDoctors.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(loadDoctors.fulfilled, (state, action) => {
state.data = action.payload;
state.loading = false;
})
.addCase(loadDoctors.rejected, (state, action) => {
state.error = action.error.message;
state.loading = false;
})
.addCase(toggleFavorite.fulfilled, (state, action) => {
const updated = action.payload;
const index = state.data.findIndex(d => d.id === updated.id);
if (index !== -1) {
state.data[index] = { ...state.data[index], ...updated };
}
})
.addCase(toggleFavorite.rejected, (state, action) => {
state.error = action.error.message;
});
},
});
export default doctorsSlice.reducer;
State Shape
{
data: DoctorObject[], // array of all doctor records from MockAPI
loading: boolean, // true while loadDoctors is in flight
error: string | null // error message from the last failed thunk, or null
}
Async Thunks
loadDoctors
Fetches the full list of doctors from the API by calling fetchDoctors() (GET ./doctorData). Dispatched once on the doctor-list screen mount. The data array in state is replaced with the returned payload on success.
toggleFavorite(doctor)
Patches a single doctor’s isFavorite field to its inverse (!doctor.isFavorite) by calling updateDoctors(doctor.id, { isFavorite: !doctor.isFavorite }). On success the matching entry in state.data is merged with the returned updated object, ensuring the UI reflects the new favourite status without a full re-fetch.
| Action | State change |
|---|
loadDoctors.pending | loading = true, error = null |
loadDoctors.fulfilled | data = action.payload, loading = false |
loadDoctors.rejected | error = action.error.message, loading = false |
toggleFavorite.fulfilled | Finds doctor by id in data and merges updated fields in place |
toggleFavorite.rejected | error = action.error.message |
Selectors
// Destructure all three fields from the doctors slice
const { data: doctors, loading, error } = useSelector(state => state.doctors);
// Access just the doctor list
const doctors = useSelector(state => state.doctors.data);
// Check loading state
const isLoading = useSelector(state => state.doctors.loading);
Dispatch Examples
import { useDispatch } from 'react-redux';
import { loadDoctors, toggleFavorite } from '../redux/Slices/DoctorSlice';
const dispatch = useDispatch();
// Load all doctors (e.g. on screen mount)
dispatch(loadDoctors());
// Toggle the favourite status for a doctor object
dispatch(toggleFavorite(doctorObject));
Full component example:
import React, { useEffect } from 'react';
import { View, Text, TouchableOpacity, ActivityIndicator } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { loadDoctors, toggleFavorite } from '../redux/Slices/DoctorSlice';
export default function DoctorsScreen() {
const dispatch = useDispatch();
const { data: doctors, loading, error } = useSelector(state => state.doctors);
useEffect(() => {
dispatch(loadDoctors());
}, [dispatch]);
if (loading) return <ActivityIndicator size="large" />;
if (error) return <Text>Something went wrong: {error}</Text>;
return (
<View>
{doctors.map(doctor => (
<View key={doctor.id}>
<Text>{doctor.name}</Text>
<TouchableOpacity onPress={() => dispatch(toggleFavorite(doctor))}>
<Text>{doctor.isFavorite ? 'Unfavourite' : 'Favourite'}</Text>
</TouchableOpacity>
</View>
))}
</View>
);
}