Writing Reducers

This documentation support developers to get started with generating custom document models.

Short Recap: Document Models

Document models in Powerhouse are structured formats that define the data and operations for specific business logic or domain models within the Powerhouse ecosystem. These models are crucial for capturing and managing the complex interactions and data transformations within Powerhouse applications. Read more about document models here or explore our FAQ on document models.

Implementing reducers

Inside the directory document-models/"DOCUMENT_MODEL"/src/reducers there will be a file for each module of the document model with the reducer functions to be implemented for each operation. You'll need to write your custom reducer logic to handle the document model operations, actions and state changes as needed for your application or envisioned document model editor.

Defining Operations

Basic Operations: Typically, your document model will include operations for creating, updating, and deleting elements within the model. These operations form the core of your document model's functionality.

The strict ts-eslint rules set by the core developers at Powerhouse will help you maintain code quality, consistency, and adherence to best practices across the project.

What will be produced for you:

To help you along your way in document model generation we've set you up with a couple of tools that help you write your reducers and make your life easier.

Introducing Zod

The reducer's input, output, and possibly internal state are defined and enforced using the Zod library. Zod is a TypeScript-first schema declaration and validation library that allows developers to create schemas that describe the shape and constraints of data. By using Zod to strongly type a reducer, you leverage TypeScript's static type checking along with Zod's runtime validation to ensure that the data flowing through the reducer conforms to expected types and structures at both compile-time and runtime.

Introducing Immer

Immer is a library designed to simplify the process of working with immutable state in JavaScript applications, particularly in contexts like Redux reducers, where immutability is a key principle. Immer allows you to write code that appears to mutate state directly while actually producing new immutable states behind the scenes. This approach significantly simplifies the reducer logic, especially when dealing with complex state shapes or deep updates.

Suite of unit tests

The GenerateMock function is designed to simplify the creation of mock data for testing purposes. By taking a Zod schema as its input, it leverages the capabilities of Faker.js to automatically generate fake, yet plausible, values for each element defined within the schema. This allows developers to efficiently produce mock data corresponding to the structure and constraints specified in their Zod schemas, facilitating thorough testing and validation of application functionality without the need for manually crafting mock data sets for each schema.

Efficient Reducer Design through Utility Functions

Embedding detailed business logic directly within reducers can lead to bulky, hard-to-test code. To mitigate this, we recommend abstracting business logic into discrete utility functions. Reducers become straightforward maps between action types and state transformations, with the heavy lifting offloaded to utility functions. This strategy offers several advantages:

  1. Modularity: Utility functions encapsulate specific pieces of business logic, making your codebase more organized and modular.

  2. Granular Testing: By isolating business logic in utility functions, you can write more focused and granular tests.

  3. Reuse and Composition: Utility functions can be reused across different parts of your application, including multiple reducers.

  4. Reducer Simplicity: Keeping reducers lean by focusing on state transition mechanisms rather than detailed logic makes them more predictable and easier to maintain.

For the majority of your tests you want atleast:

Create a document; transform it to create an updated doc, use a reducers to do that.

Reducers and derived fields

Understand that inputs to reducers don't have to directly map to the schema's state fields. This is particularly important for derived fields, which are computed based on other data or transactions within the model.

  • Derived Fields: Certain fields within a state object, such as for example 'an asset', do not hold values provided directly by user input but are calculated from other data, like a series of transactions. These fields are known as derived fields.

  • Reducer Inputs and State Updates: The input to a reducer function responsible for updating the state of such an asset doesn't need to include these derived fields explicitly. Instead, the reducer needs sufficient information to identify the asset and perform calculations to update these derived fields based on new transactions and the latest state.

  • Example of Updating Derived Fields: When a new 'transaction' occurs, such as the sale of 'an asset', the relevant reducer function computes the necessary updates to the derived fields (e.g., "purchase price") of the associated asset. This computation is based on the details of the transaction and the existing data of the asset within the state.

Even in scenarios involving intricate relationships between different parts of the state (such as our example above, transactions affecting assets), the reducer treats the state as a single cohesive object. It performs all necessary computations and updates within the context of a single operation, returning a new, updated state object that reflects all changes.

Developers are encouraged to:

  • Design state schemas with clear distinctions between directly inputted data and derived/computed fields.

  • Implement reducers that can handle complex operations by computing necessary updates based on actions without requiring explicit inputs for derived fields.

  • Maintain the purity of reducer functions by ensuring they do not mutate the existing state but instead return new instances of the state with the required updates.

  • Asynchronous operations should be handled outside of the document model, ensuring that reducers remain pure functions of the state.

  • Validation and Leniency: While some validation is necessary, aim to be as lenient as possible without allowing unrecoverable states.

Last updated