OverReact Documentation

repository·master·Indexed 19 days ago

https://github.com/workiva/over_react

A library for building statically-typed React UI components using the Dart language. OverReact provides a bridge between Dart and React JS, featuring a code generation builder to reduce boilerplate for UiFactory, UiProps, and UiComponent2. It supports both function and class components, mixin-based props and state declarations, and integrates with Redux for state management.

Tokens
46.3K
Snippets
129
Records
164
Agent score
65%

What's inside OverReact

  1. What is Influx architecture and when to use it

    master

    Influx is a transitional architecture that allows a project to coexist with both Flux and Redux state management systems. It enables an incremental migration by allowing Flux components and Redux (Connected) components to communicate with the same store.

    When to use Influx

    Use Influx if you are performing a large-scale refactor and are not confident in your ability to transition directly from Flux to Redux. It allows you to split the effort into tangible subtasks, such as updating stores to Redux without immediately updating all UI components.

    Pros and Cons

    • Pros: Enables incremental updates; allows updating state systems without being blocked by UI component refactors (like moving to UiComponent2); reduces complexity of massive merges/code reviews.
    • Cons: Requires two refactors instead of one; provides no performance gains until the transition is complete; adds boilerplate and complexity to the state architecture.
  2. Implement mixin-based Props declaration

    master

    The new boilerplate uses mixins for props declaration instead of direct subclassing. This allows for better sharing of props between components.

    Rules:

    1. Props classes must directly subclass UiProps.
    2. All other props must be inherited via mixins.
    3. Props must be declared within mixins.

    Standard Pattern: Define a mixin for your props, then create a concrete class that is an alias for UiProps mixed with your props mixins.

    Abbreviated Pattern: If no other mixins are used, you can use the mixin directly as the props type.

    // Standard Pattern
    mixin FooPropsMixin on UiProps {
      String foo;
    }
    
    class FooProps = UiProps with FooPropsMixin, BarPropsMixin;
    
    // Abbreviated Pattern (when no other mixins are used)
    mixin FooProps on UiProps {
      String foo;
    }
  3. How the OverReact Builder works

    master

    The OverReact builder is a code generation tool that reduces boilerplate when declaring components. It parses specific 'boilerplate' declarations and automatically wires up the UiFactory, UiProps (or mixins), and the component class (UiComponent2 or function components).

    Component Declaration Requirements

    To trigger code generation for a class component, the builder looks for a group of matching members:

    1. A Factory: A UiFactory variable that references a generated identifier (e.g., UiFactory<FooProps> Foo = castUiFactory(_$Foo);).
    2. Props: Either a mixin that is on UiProps with a name ending in Props or PropsMixin, or a concrete class extending UiProps with a name ending in Props.
    3. Component Class: A class with a name ending in Component that extends UiComponent2<TProps>.
    4. State (Optional): Either a mixin that is on UiState with a name ending in State or StateMixin, or a concrete class extending UiState with a name ending in State.

    The Generation Process

    1. Parsing: The builder identifies top-level declarations matching the patterns above.
    2. Grouping: It groups related members (e.g., Foo, FooProps, FooComponent) into a single component declaration.
    3. Code Generation: It generates a .over_react.g.dart file containing:
      • A generated props/state mixin with concrete getters and setters that proxy Map keys.
      • A concrete implementation class for props/state that handles JsBackedMap or plain Maps.
      • A ReactComponentFactory registration.
      • The initializer for the factory (_$Foo) which serves as the entry point for consuming the component.
    UiFactory<FooProps> Foo = castUiFactory(_$Foo);
    
    mixin FooProps on UiProps { 
      int foo;
    }
    
    class FooComponent extends UiComponent2<FooProps> {
      @override
      render() { 
        // ...
      }
    }
  4. Use getters and setters for complex prop conversions

    master

    Some props cannot be safely converted directly from JavaScript to Dart without risking runtime errors. In these cases, use a pattern involving a private JS-typed field (like JsMap) and public Dart getters/setters that use OverReact utilities to perform the translation.

    This pattern allows the component to receive raw JS data while providing a clean, typed Dart interface to the consumer.

    @Props(keyNamespace: '')
    mixin ArbitraryComponentProps on UiProps {
      Map get aMapProp => unjsifyMapProp(_aMapProp$rawJs);
    
      set aMapProp(Map value) => _aMapProp$rawJs = jsifyMapProp(value);
    
      @Accessor(key: 'aMapProp')
      JsMap _aMapProp$rawJs;
    }
  5. Compose multiple props mixins into a single component API

    master

    You can create a unified props API for a component by combining multiple UiProps mixins using the with keyword in a class definition. This allows a component to expose properties from several different sources.

    To implement this pattern effectively, you must distinguish between props that the component itself consumes and props that should be forwarded to a child component. This is achieved by controlling the consumedProps in class-based components or using getPropsToForward in functional components.

    class MyComponentProps = UiProps with MixinA, MixinB;
    
    class MyComponent extends UiComponent2<MyComponentProps> {
      @override
      get consumedProps => propsMeta.forMixins({MixinA}); // Only consumes MixinA, MixinB is forwarded
    
      @override
      render() {
        return (ChildComponent()
          ..modifyProps(addUnconsumedProps)
        )(/* ... */);
      }
    }
  6. Understand the mental model for wrapping JS components

    master

    Wrapping a JavaScript component for use in OverReact involves three distinct layers of abstraction:

    1. The Dart Level (Top Layer): Uses OverReact concepts. Components are accessed via a UiFactory which creates UiProps instances. Invoking a props instance (e.g., propsInstance()) builds a ReactElement that React can render.
    2. The Interop Level (Middle Layer): Handled by react-dart. It uses a ReactJsComponentFactoryProxy to bridge Dart and JavaScript. This proxy is responsible for converting Dart props to JavaScript and creating the JS ReactElement.
    3. The JavaScript Land (Bottom Layer): The raw JavaScript implementation. This must be accessible on the global window object (e.g., via a UMD bundle) before the Dart code attempts to reference it via JS interop.

    To bridge these, the uiJsComponent API is used to take a ReactJsComponentFactoryProxy and return a UiFactory compatible with OverReact.

  7. Configure consumedProps behavior

    master

    The consumedProps method determines which props are considered 'consumed' by a component (and thus not forwarded via addUnconsumedProps or copyUnconsumedProps).

    Default Behavior: In the new mixin-based syntax, UiComponent2 automatically consumes props from all props mixins by default if consumedProps is not overridden.

    Customizing Consumption: To change this behavior, override consumedProps. You can use propsMeta to easily include or exclude specific mixins.

    • To consume all except specific mixins: Use propsMeta.allExceptForMixins({MixinType}).
    • To consume only specific mixins: Use propsMeta.forMixins({MixinType1, MixinType2}) (or manually list them via forMixin).
    class FooComponent extends UiComponent2<FooProps> {
      @override
      get consumedProps => propsMeta.allExceptForMixins({NoConsumeProps});
    }
  8. Understand the limitations of the transitional (Dart 1 compatible) boilerplate

    master

    The transitional boilerplate (used in most existing repos) requires users to manually stub public classes to avoid issues with code generation tools. This pattern is verbose and error-prone because it requires the user to write code that looks like this:

    // User-authored
    @Props()
    class _$FooProps extends BarProps {
      String foo;
    }
    
    // Also user-authored (Stubbing the public class)
    class FooProps
        extends _$FooProps
        with
            // ignore: mixin_of_non_class, undefined_class
            _$FooPropsAccessorsMixin {
      // ignore: const_initialized_with_non_constant_value, undefined_class, undefined_identifier 
      static const PropsMeta meta = _$metaForPanelTitleProps;
    }

    Additionally, the transitional boilerplate has inheritance issues: if a consumer extends an authored props class, they may not inherit the generated accessors (getters/setters) correctly, causing them to reference the raw field instead of the intended accessor.

    // User-authored
    @Props()
    class _$FooProps extends BarProps {
      String foo;
    }
    
    // Also user-authored
    class FooProps
        extends _$FooProps
        with
            // ignore: mixin_of_non_class, undefined_class
            _$FooPropsAccessorsMixin {
      // ignore: const_initialized_with_non_constant_value, undefined_class, undefined_identifier 
      static const PropsMeta meta = _$metaForPanelTitleProps;
    }
  9. Determine prop requiredness and nullability

    master

    When migrating to null safety, you must decide if a prop should be nullable (?) or non-nullable.

    Decision Logic

    1. Is it optional? All optional props should be made nullable (e.g., String? prop;).
    2. Is it required?
      • If it is required but can be explicitly set to null, make it nullable and required (late String? prop;).
      • If it is required and should never be null, make it non-nullable and required (late String prop;).

    Warning: Using the late keyword to make a prop required can be a breaking change if consumers do not always provide that prop.

    Summary Table

    Required (late)Optional
    Nullable (?)late String? prop;String? prop;
    Non-nullablelate String prop;n/a
  10. Use ErrorBoundary to prevent unmounting on errors

    master

    With the addition of componentDidCatch and getDerivedStateFromError in Component2, you can now use Error Boundaries.

    • Default ErrorBoundary: Use the built-in ErrorBoundary component provided by OverReact to wrap a component tree. This prevents the entire application from unmounting when a child component throws an error.
    • Custom Error Boundaries: If you need custom logic, implement the ErrorBoundaryMixin to create your own boundary component.
  11. How OverReact Redux provides targeted updates

    master
    OverReact Redux is a Dart wrapper for React Redux that enables targeted state updates. Instead of re-rendering the entire component tree when state changes (the default React behavior), OverReact Redux uses the connect() function combined with mapStateToProps() to ensure a component only updates when the specific piece of information it consumes has changed. This improves performance by isolating renders to only the affected components.
  12. How OverReact components are structured

    master

    OverReact is a layer atop the react-dart package that provides a 1:1 relationship with React JS component classes and APIs. An OverReact component is composed of four core pieces wired together via a builder:

    1. UiFactory: The entry point used to consume a component.
    2. UiProps: A Map class providing statically-typed getters and setters for component props.
    3. A component, which can be either:
      • A function component defined via uiFunction.
      • A class component defined via UiComponent2 (optionally using UiState).
    // Example of the four pieces working together (Conceptual)
    // 1. UiFactory
    // 2. UiProps
    // 3. Component (UiComponent2 or uiFunction)