Skip to Content

State Management

NRP repositories use multiple state management approaches depending on the complexity and scope of your component. Understanding when to use each approach is key to building maintainable React components.

Choosing the Right Approach

ApproachUse CaseComplexity
React Built-in StateSimple component state (toggles, form inputs)Low
FormikForm handling with validationMedium
React Context APICross-component data sharingMedium
ReduxComplex app state managed across many componentsHigh

React Built-in State

For simple local state within a component, use the useState hook:

import { useState } from "react"; export const MyComponent = () => { const [isOpen, setIsOpen] = useState(false); const [count, setCount] = useState(0); return ( <button onClick={() => setIsOpen(!isOpen)}> {isOpen ? "Close" : "Open"} </button> ); };

Use for:

  • Simple toggles and switches
  • Form input values
  • Local component state that doesn’t need to be shared

Formik

Formik  handles complex form state, validation, and submission logic. It’s used with react-invenio-forms  for deposit forms in NRP repositories.

Use for:

  • Deposit forms
  • Multi-step workflows
  • Forms with complex validation rules
  • Any form with multiple fields

Validation with Yup

Yup  is a JavaScript schema builder for value parsing and validation. It’s commonly used with Formik to define validation schemas for deposit forms.

import { Formik, Form, Field } from "formik"; import { TextField } from "@js/oarepo_ui/forms"; import * as yup from "yup"; const validationSchema = yup.object().shape({ title: yup.string().required("Title is required").min(3, "Title must be at least 3 characters"), description: yup.string().required("Description is required"), year: yup.number().required().min(1900).max(new Date().getFullYear()), }); export const MyForm = () => { return ( <Formik initialValues={{ title: "", description: "", year: "" }} validationSchema={validationSchema} onSubmit={(values) => console.log(values)} > <Form> <TextField fieldPath="title" /> <TextField fieldPath="description" required /> <TextField fieldPath="year" dataType="integer" /> <button type="submit">Submit</button> </Form> </Formik> ); };

Formik Hooks

Accessing Form Context

Use useFormikContext() to access the current form’s state and helpers:

import { useFormikContext, getIn } from "formik"; const MyComponent = () => { const { values, errors, touched, isValid, isSubmitting } = useFormikContext(); // Get a field value const titleValue = values.title; // Check if there's an error on a field const hasTitleError = errors.title && touched.title; // Set a field value programmatically const handleChange = () => { // You'd typically call this in response to some event // setFieldValue('fieldName', newValue) }; return <div>{/* ... */}</div>; };

Available properties:

PropertyDescription
valuesObject containing all field values
errorsObject containing validation errors
touchedObject tracking which fields have been touched
isValidBoolean indicating if the form is valid
isSubmittingBoolean indicating if form is currently submitting
resetForm()Function to reset the form to initial values
setFieldValue(name, value)Function to update a specific field value
setFieldTouched(name, isTouched)Function to mark a field as touched
setErrors(errors)Function to set validation errors

Common Patterns

Get and set form field value:

const MyComponent = () => { const { values, setFieldValue } = useFormikContext(); const currentValue = values.myField; const updateValue = () => setFieldValue("myField", "new value"); };

Check for errors and display:

const { errors, touched } = useFormikContext(); const myError = touched.myField ? errors.myField : undefined;

Reset form to initial values:

const { resetForm } = useFormikContext(); const handleReset = () => resetForm();

React Context API

Context API shares state across component trees without prop drilling. Use it for moderately complex state that needs to be accessed by multiple components.

import { createContext, useContext, useState } from "react"; const MyContext = createContext(); export const MyProvider = ({ children }) => { const [sharedState, setSharedState] = useState(null); return ( <MyContext.Provider value={{ sharedState, setSharedState }}> {children} </MyContext.Provider> ); }; export const useMyContext = () => useContext(MyContext);

Use for:

  • User preferences (theme, language)
  • Authentication state
  • Data shared between sibling components
  • State that doesn’t warrant Redux

Redux

Redux provides a predictable state container for complex application state. It’s used extensively in NRP repositories for:

  • Search applications - Managing search state, filters, and results
  • Deposit forms - Handling form submission and validation flows

Basic Redux Setup

import { configureStore, createSlice } from "@reduxjs/toolkit"; import { Provider, useSelector, useDispatch } from "react-redux"; // Create a slice const searchSlice = createSlice({ name: "search", initialState: { query: "", results: [] }, reducers: { setQuery: (state, action) => { state.query = action.payload; }, }, }); // Configure store const store = configureStore({ reducer: { search: searchSlice.reducer, }, }); // Use in components export const SearchComponent = () => { const query = useSelector((state) => state.search.query); const dispatch = useDispatch(); return ( <input value={query} onChange={(e) => dispatch(searchSlice.actions.setQuery(e.target.value))} /> ); };

When to Use Redux

  • State is used by many components across the application
  • State needs to be updated from many different places
  • App state is complex and has nested or related data
  • You need to track state changes over time (time-travel debugging)

Start with simpler approaches. Only introduce Redux when you’ve identified that your state management needs exceed what hooks or Context API can handle reasonably.

Last updated on