DEV Community

Saulo Dias
Saulo Dias

Posted on

Elevating Data Integrity in Your React Application with Converters in Storage Management

Greetings, fellow developers! Today, let's delve into the realm of storage management in React applications and explore how the concept of converters can revolutionize the way we handle data integrity. Whether you're dealing with complex objects, enumerations, or custom classes, converters offer a powerful solution to ensure seamless serialization and deserialization, thus maintaining data consistency across sessions.

For these examples we're going to use the storage hooks avaible below.

Understanding the Challenge

When working with storage in web applications, one of the common challenges developers face is preserving data integrity, especially when dealing with complex data types. Consider scenarios where we need to store objects with properties like dates, enumerations representing user roles, or instances of custom classes in local storage. Serializing and deserializing such data directly to and from storage can lead to loss of type information, potentially compromising data integrity.

Enter Converters

Converters serve as the bridge between your application's data structures and the storage layer. They allow you to define custom serialization and deserialization logic tailored to your specific data types. By encapsulating this logic within converters, you can ensure that data is stored and retrieved in a format that maintains its integrity and type information.

Leveraging Converters in Practice

Let's consider a few practical examples to illustrate the power of converters:

1. Handling Dates with Dayjs

In our application, we often need to store dates, but standard date objects can lose their type during serialization. By using converters with libraries like Dayjs, we can serialize dates to string representations and deserialize them back to Dayjs objects, ensuring type integrity throughout.

// Define the interface for user preferences
interface UserPreferences {
  theme: string;
  language: string;
  lastLogin: Dayjs; // Using Dayjs for date/time representation
}

// Define a converter for user preferences
const userPreferencesConverter: JsonConverter<UserPreferences, string> = {
  toJson: (preferences: UserPreferences) => ({
    theme: preferences.theme,
    language: preferences.language,
    lastLogin: preferences.lastLogin.toISOString(), // Serialize Dayjs to string
  }),
  fromJson: (storedValue: string) => {
    const parsedValue = JSON.parse(storedValue) as { theme: string, language: string, lastLogin: string }
    return {
      theme: parsedValue.theme,
      language: parsedValue.language,
      lastLogin: dayjs(parsedValue.lastLogin), // Deserialize string to Dayjs
    }
  },
}

// Example component using useLocalStorage with custom converter
const UserPreferencesComponent: React.FC = () => {
  // Initialize user preferences with default values
  const defaultPreferences: UserPreferences = {
    theme: 'light',
    language: 'en',
    lastLogin: dayjs(), // Current date/time
  }

  // Use useLocalStorage hook with custom converter
  const [userPreferences, setUserPreferences] = useLocalStorage(
    'userPreferences',
    defaultPreferences,
    userPreferencesConverter
  )

  // Function to update last login time
  const updateLastLogin = () => {
    setUserPreferences(prevPreferences => ({
      ...prevPreferences,
      lastLogin: dayjs(), // Update last login to current date/time
    }))
  }

  return (
    <div>
      <h1>User Preferences</h1>
      <p>Theme: {userPreferences.theme}</p>
      <p>Language: {userPreferences.language}</p>
      <p>Last Login: {userPreferences.lastLogin.format('YYYY-MM-DD HH:mm:ss')}</p>
      <button onClick={updateLastLogin}>Update Last Login</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

2. Storing Enumerations

Enumerations, such as user roles or status codes, are commonly used in applications. However, serializing enums directly to storage can be challenging. With converters, we can map enum values to string representations, enabling seamless storage and retrieval without compromising type safety.

// Define the enum for user roles
enum UserRole {
  ADMIN = 'ADMIN',
  USER = 'USER',
  GUEST = 'GUEST',
}

// Define a converter for user roles
const userRoleConverter: JsonConverter<UserRole, string> = {
  toJson: (role: UserRole) => role, // Simply return the enum value as string
  fromJson: (storedValue: string) => UserRole[storedValue as keyof typeof UserRole], // Convert string back to enum value
}

// Example component using useLocalStorage with custom converter
const UserRoleComponent: React.FC = () => {
  // Initialize user role with default value
  const defaultUserRole: UserRole = UserRole.GUEST

  // Use useLocalStorage hook with custom converter
  const [userRole, setUserRole] = useLocalStorage(
    'userRole',
    defaultUserRole,
    userRoleConverter
  )

  // Function to update user role
  const updateUserRole = (role: UserRole) => {
    setUserRole(role)
  }

  return (
    <div>
      <h1>User Role</h1>
      <p>Current Role: {userRole}</p>
      <button onClick={() => updateUserRole(UserRole.ADMIN)}>Set as Admin</button>
      <button onClick={() => updateUserRole(UserRole.USER)}>Set as User</button>
      <button onClick={() => updateUserRole(UserRole.GUEST)}>Set as Guest</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

3. Persisting Custom Classes

Custom classes often encapsulate complex data structures in our applications. Storing instances of these classes in storage requires careful handling. Converters allow us to define serialization and deserialization logic for custom classes, ensuring that objects are stored and retrieved accurately across sessions.

// Define a custom class for Person
class Person {
  constructor(public name: string, public age: number) {}
}

// Define a converter for Person class
const personConverter: JsonConverter<Person, { name: string, age: number }> = {
  toJson: (person: Person) => ({
    name: person.name,
    age: person.age,
  }),
  fromJson: (storedValue: { name: string, age: number }) => new Person(storedValue.name, storedValue.age),
}

// Example component using useLocalStorage with custom converter
const PersonComponent: React.FC = () => {
  // Initialize person with default value
  const defaultPerson: Person = new Person('John Doe', 30)

  // Use useLocalStorage hook with custom converter
  const [person, setPerson] = useLocalStorage(
    'person',
    defaultPerson,
    personConverter
  )

  // Function to update person's name
  const updateName = (newName: string) => {
    setPerson(prevPerson => {
      return { ...prevPerson, name: newName }
    })
  }

  // Function to update person's age
  const updateAge = (newAge: number) => {
    setPerson(prevPerson => {
      return { ...prevPerson, age: newAge }
    })
  }

  return (
    <div>
      <h1>Person Information</h1>
      <p>Name: {person.name}</p>
      <p>Age: {person.age}</p>
      <div>
        <input type="text" placeholder="Enter new name" onChange={(e) => updateName(e.target.value)} />
        <input type="number" placeholder="Enter new age" onChange={(e) => updateAge(parseInt(e.target.value))} />
      </div>
    </div>
  )
}

Enter fullscreen mode Exit fullscreen mode

Conclusion

In the ever-evolving landscape of web development, maintaining data integrity is paramount. By harnessing the power of converters in storage management, we can elevate our applications to new heights of robustness and reliability. Whether it's handling dates, enumerations, or custom classes, converters provide the key to unlocking data integrity in our React applications.

So, the next time you find yourself grappling with storage complexities, remember the transformative potential of converters. With a little creativity and ingenuity, you can ensure that your application's data remains consistent, reliable, and ready to meet the challenges of tomorrow.

Happy coding! 🚀

Top comments (0)