State Management Architecture
The Production Inspection Form uses React Hooks for state management, specifically useState for component state and useEffect for side effects and data loading.
The application does not use external state management libraries (Redux, Zustand, etc.). All state is managed locally within components using React hooks.
Component State Organization
Main Form Component State
The inspection form component manages 14 separate pieces of state:
function InspectionForm() {
// 1. Mounting state - ensures client-side only rendering
const [mounted, setMounted] = useState(false);
// 2. API URL configuration
const [apiUrl, setApiUrl] = useState(null);
// 3. Form data - main data structure
const [formData, setFormData] = useState({
fecha: "",
horaInicio: "",
horaFin: "",
division: "",
area: "",
zona: "",
equipo: "",
observaciones: "",
tecnicos: {}
});
// 4. Current user
const [usuario, setUsuario] = useState("");
// 5-8. Catalog data (dropdowns)
const [divisiones, setDivisiones] = useState([]);
const [areas, setAreas] = useState([]);
const [zonas, setZonas] = useState([]);
const [equipos, setEquipos] = useState([]);
// 9. Equipment category
const [categoria, setCategoria] = useState("");
// 10. Physical location
const [ubicacion, setUbicacion] = useState("");
// 11. Inspection questions
const [preguntas, setPreguntas] = useState([]);
// 12. Status/feedback messages
const [statusMessage, setStatusMessage] = useState("");
// ...
}
Login Component State
function LoginPage() {
const router = useRouter();
// Form fields
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
// Error feedback
const [errorMessage, setErrorMessage] = useState("");
// ...
}
State Update Patterns
Immutable Updates with Functional setState
The application uses functional state updates to ensure immutability:
// Setting nested object properties
setFormData(prev => ({
...prev,
fecha: now.toISOString().split("T")[0],
horaInicio: timeString
}));
// Updating nested tecnicos object
setFormData(prev => ({
...prev,
tecnicos: {
...prev.tecnicos,
[pregunta]: valor
}
}));
Functional setState (prev => ({ ...prev, ... })) is used throughout to ensure updates are based on the latest state, preventing race conditions.
Direct Updates for Simple State
// Simple string state
setUsuario(nombreUsuario || usuario);
setCategoria(equipo.categoria);
setUbicacion(equipo.ubicacion);
setStatusMessage("Formulario enviado correctamente");
// Array state
setDivisiones(divisionesData);
setAreas(areasData);
setPreguntas(preguntasData);
useState Hook Patterns
Pattern 1: Simple Value State
const [mounted, setMounted] = useState(false);
const [apiUrl, setApiUrl] = useState(null);
const [usuario, setUsuario] = useState("");
const [categoria, setCategoria] = useState("");
Pattern 2: Complex Object State
const [formData, setFormData] = useState({
fecha: "",
horaInicio: "",
horaFin: "",
division: "",
area: "",
zona: "",
equipo: "",
observaciones: "",
tecnicos: {}
});
Pattern 3: Array State
const [divisiones, setDivisiones] = useState([]);
const [areas, setAreas] = useState([]);
const [zonas, setZonas] = useState([]);
const [preguntas, setPreguntas] = useState([]);
useEffect Hook Patterns
The application uses three main useEffect hooks with different purposes:
Effect 1: Component Mount Detection
useEffect(() => {
setMounted(true);
}, []);
Purpose: Prevents hydration mismatches by ensuring client-only code runs after mount.
Dependencies: Empty array [] means this runs once on mount.
Effect 2: Authentication & Configuration
useEffect(() => {
// Check authentication
const usuario = localStorage.getItem("usuario_inspeccion");
const nombreUsuario = localStorage.getItem("usuario_nombre");
if (usuario) {
setUsuario(nombreUsuario || usuario);
} else {
// Redirect to login
const currentUrl = window.location.href;
localStorage.setItem("redirect_after_login", currentUrl);
router.push("/login");
}
// Set API URL
setApiUrl("http://10.107.194.110/insp/");
}, []);
Purpose:
- Verify user authentication on page load
- Redirect to login if not authenticated
- Configure API URL
Dependencies: Empty array [] means this runs once on mount.
This effect accesses localStorage which is only available in the browser. This is why the mounted state guard is necessary.
Effect 3: Data Loading
useEffect(() => {
if (!apiUrl) return;
// Initialize form date/time
const now = new Date();
const timeString = now.toTimeString().split(" ")[0];
setFormData(prev => ({
...prev,
fecha: now.toISOString().split("T")[0],
horaInicio: timeString
}));
// Load all catalog data in parallel
Promise.all([
fetch(`${apiUrl}/api/divisiones/`).then(r => r.json()).catch(() => []),
fetch(`${apiUrl}/api/areas/`).then(r => r.json()).catch(() => []),
fetch(`${apiUrl}/api/zonas/`).then(r => r.json()).catch(() => []),
fetch(`${apiUrl}/api/equipos/`).then(r => r.json()).catch(() => [])
]).then(([divs, ars, zns, eqs]) => {
setDivisiones(divs);
setAreas(ars);
setZonas(zns);
setEquipos(eqs);
});
// Load equipment from URL parameter
const equipoId = searchParams.get("equipo");
setFormData(prev => ({ ...prev, equipo: equipoId || "" }));
if (equipoId) {
// Load equipment details
fetch(`${apiUrl}/api/equipos/`)
.then(res => res.json())
.then(equipos => {
const equipo = equipos.find(e => String(e.id) === String(equipoId));
if (equipo) {
// Populate form with equipment data
setFormData(prev => ({
...prev,
division: equipo.division_id,
area: equipo.area_id,
zona: equipo.zona_id
}));
setUbicacion(equipo.ubicacion);
setCategoria(equipo.categoria);
// Load questions for this equipment category
if (equipo.categoria) {
fetch(`${apiUrl}/api/preguntas/${equipo.categoria}/`)
.then(res => res.json())
.then(setPreguntas)
.catch(err => console.error("Error cargando preguntas", err));
}
}
})
.catch(err => console.error("Error cargando equipo", err));
}
}, [apiUrl]);
Purpose:
- Initialize form with current date/time
- Load all catalog data (divisions, areas, zones, equipment)
- Pre-populate form if equipment ID is in URL
- Load inspection questions based on equipment category
Dependencies: [apiUrl] means this runs when apiUrl changes (set in effect #2).
This creates a chain of effects: Mount → Auth/Config → Data Loading. This ensures data loading only happens after authentication is verified.
// Text input handler
<input
type="text"
value={formData.observaciones}
onChange={(e) => setFormData({
...formData,
observaciones: e.target.value
})}
/>
// Textarea handler
<textarea
value={formData.observaciones}
onChange={(e) => setFormData({
...formData,
observaciones: e.target.value
})}
rows={4}
/>
Handling Question Responses
const handleTecnicoChange = (pregunta, valor) => {
setFormData(prev => ({
...prev,
tecnicos: {
...prev.tecnicos,
[pregunta]: valor
}
}));
};
// Radio button implementation
<input
type="radio"
name={`pregunta-${pregunta.id}`}
value="OK"
checked={formData.tecnicos[pregunta.descripcion] === "OK"}
onChange={() => handleTecnicoChange(pregunta.descripcion, "OK")}
/>
<input
type="radio"
name={`pregunta-${pregunta.id}`}
value="NOK"
checked={formData.tecnicos[pregunta.descripcion] === "NOK"}
onChange={() => handleTecnicoChange(pregunta.descripcion, "NOK")}
/>
<input
type="radio"
name={`pregunta-${pregunta.id}`}
value="NA"
checked={formData.tecnicos[pregunta.descripcion] === "NA"}
onChange={() => handleTecnicoChange(pregunta.descripcion, "NA")}
/>
localStorage Integration
The application uses browser localStorage for persistent user session management.
Session Storage Schema
interface LocalStorageSchema {
"usuario_inspeccion": string; // Username
"usuario_nombre": string; // Full name ("First Last")
"redirect_after_login"?: string; // URL to redirect after login
}
Reading from localStorage
// Authentication check
const usuario = localStorage.getItem("usuario_inspeccion");
const nombreUsuario = localStorage.getItem("usuario_nombre");
if (usuario) {
// User is authenticated
setUsuario(nombreUsuario || usuario);
} else {
// User is not authenticated - redirect to login
const currentUrl = window.location.href;
localStorage.setItem("redirect_after_login", currentUrl);
router.push("/login");
}
Writing to localStorage
// After successful login
if (data.status === "ok") {
// Store user session
localStorage.setItem("usuario_inspeccion", username);
localStorage.setItem("usuario_nombre", `${data.first_name} ${data.last_name}`);
// Get redirect URL and clean up
const redirectUrl = localStorage.getItem("redirect_after_login") || "/";
localStorage.removeItem("redirect_after_login");
router.push(redirectUrl);
}
Removing from localStorage
// Logout handler
<button
onClick={() => {
localStorage.removeItem("usuario_inspeccion");
router.push("/login");
}}
>
Cerrar Sesión
</button>
Storing authentication data in localStorage has security implications:
- Vulnerable to XSS attacks
- No automatic expiration
- Accessible to all JavaScript on the page
For production, consider:
- HTTP-only cookies for session tokens
- Server-side session management
- Token expiration and refresh mechanisms
const handleSubmit = async (e) => {
e.preventDefault();
// ... submission logic ...
try {
const response = await fetch(`${apiUrl}/api/guardar/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(submissionData)
});
const result = await response.json();
setStatusMessage(result.message || "Formulario enviado correctamente");
// Reset form to initial state
setFormData({
fecha: "",
horaInicio: "",
horaFin: "",
division: "",
area: "",
zona: "",
equipo: "",
observaciones: "",
tecnicos: {}
});
// Clear equipment details
setCategoria("");
setUbicacion("");
setPreguntas([]);
} catch (error) {
console.error(error);
setStatusMessage("Error al enviar");
}
};
State Flow Diagram
Page Load
↓
[Mount Effect]
setMounted(true)
↓
[Auth Effect]
├─ Check localStorage
│ ├─ Authenticated → setUsuario()
│ └─ Not Authenticated → router.push("/login")
└─ setApiUrl()
↓
[Data Loading Effect]
├─ Initialize date/time
├─ Load catalogs (Promise.all)
│ ├─ /api/divisiones/ → setDivisiones()
│ ├─ /api/areas/ → setAreas()
│ ├─ /api/zonas/ → setZonas()
│ └─ /api/equipos/ → setEquipos()
└─ Check URL parameter
└─ If equipo parameter exists
├─ Load equipment details
├─ setFormData() with division/area/zona
├─ setUbicacion()
├─ setCategoria()
└─ Load questions → setPreguntas()
↓
[Render Form]
User interacts with form
↓
[User Changes]
├─ onChange handlers
└─ Update formData state
↓
[Form Submission]
├─ Calculate horaFin
├─ POST to /api/guardar/
└─ Reset all form state
State Management Best Practices
✅ Good Patterns Used
-
Functional setState for complex objects
setFormData(prev => ({ ...prev, fecha: newDate }))
-
State co-location - state lives close to where it’s used
-
Immutable updates - always create new objects/arrays
-
Effect dependencies - properly declared dependency arrays
-
Conditional rendering - using
mounted state to prevent hydration issues
⚠️ Areas for Improvement
-
Excessive state slices - 14 separate state variables could be consolidated
-
No loading states - no indication when data is being fetched
-
No error states - errors are logged but not shown to users
-
No state persistence - form progress is lost on page refresh
-
No optimistic updates - no immediate feedback on form submission
Suggested Improvements
Consider using a state management solution like:
- React Query / TanStack Query: For server state (catalogs, questions)
- Zustand / Jotai: For global client state (user session)
- useReducer: For complex form state with validation logic
Example: Consolidating State with useReducer
const initialState = {
formData: { /* ... */ },
catalogs: {
divisiones: [],
areas: [],
zonas: [],
equipos: []
},
equipment: {
categoria: "",
ubicacion: "",
preguntas: []
},
ui: {
mounted: false,
loading: false,
statusMessage: ""
}
};
function formReducer(state, action) {
switch (action.type) {
case 'SET_CATALOGS':
return { ...state, catalogs: action.payload };
case 'SET_FORM_FIELD':
return {
...state,
formData: { ...state.formData, [action.field]: action.value }
};
// ... more actions
}
}
const [state, dispatch] = useReducer(formReducer, initialState);
Debugging State
To debug state in development:
useEffect(() => {
console.log('FormData:', formData);
console.log('Preguntas:', preguntas);
console.log('Usuario:', usuario);
}, [formData, preguntas, usuario]);
Or use React DevTools to inspect component state in real-time.