React Mixin Utility for ES6 Classes

raw JSON →
5.0.0 verified Sat Apr 25 auth: no javascript deprecated

react-mixin is a utility library designed to enable the use of traditional React mixins with ES6/TypeScript classes, addressing the absence of built-in support for this pattern in React's class-based components. While React itself has deprecated mixins in favor of Higher-Order Components and Hooks, this package serves as a specific migration path for legacy codebases that must integrate existing mixins with modern class syntax. The current stable version is 5.0.0, which notably adapted to React's `UNSAFE_` lifecycle methods. The library provides mechanisms to apply mixins to class prototypes for instance methods and to the classes themselves for static properties like `defaultProps` and `propTypes`. It differentiates itself by offering clear error handling for conflicting method names (instead of silent overwrites) and supporting an optional decorator syntax, allowing developers to manage mixins in a structured way within their class definitions. However, it explicitly advocates for avoiding mixins in new development.

error TypeError: Cannot read properties of undefined (reading 'setState') OR 'this' is undefined in mixin method.
cause ES6 class methods and mixin methods applied to ES6 classes are not autobound by default, unlike `React.createClass`. The `this` context is lost when the method is called as a callback (e.g., event handler).
fix
Explicitly bind the method in the class constructor: this.myMethod = this.myMethod.bind(this); or define the method using an arrow function for class fields if supported by your build setup: myMethod = () => { /* ... */ }.
error Warning: componentWillMount has been renamed, and is not recommended for use. OR Mixin lifecycle method (e.g., `componentWillMount`) is not firing in React v16.3+.
cause `react-mixin` versions prior to 5.0 used deprecated React lifecycle methods. Version 5.0+ requires the `UNSAFE_` prefix for these methods or conversion via `react-mixin/toUnsafe`.
fix
Upgrade react-mixin to ^5.0.0 and either rename the affected mixin methods (e.g., componentWillMount to UNSAFE_componentWillMount) or wrap the mixin with toUnsafe() before applying it: reactMixin(MyClass.prototype, toUnsafe(myMixin));.
error TypeError: Cannot read properties of undefined (reading 'defaultProps') OR Static properties from a mixin like `getDefaultProps` or `propTypes` are not applied to the component.
cause Static class properties (e.g., `defaultProps`, `propTypes`, or methods like `getDefaultProps`) from mixins need to be applied to the class constructor itself, not its prototype.
fix
Use reactMixin.onClass(MyClass, myMixin) to apply mixins that define static or class-level properties. This ensures the properties are correctly merged with the class definition.
deprecated React mixins are a deprecated pattern. The `react-mixin` library itself is primarily intended as a migration path for legacy code using ES6 classes with mixins. New development should strongly prefer Higher-Order Components (HOCs) or Hooks for code reuse and shared logic.
fix For new features, utilize Higher-Order Components or React Hooks instead of mixins. For legacy code, ensure `react-mixin` is used minimally and consider refactoring to modern React patterns over time.
breaking Version 5.0 of `react-mixin` aligns with React's deprecation of `componentWillMount`, `componentWillReceiveProps`, and `componentWillUpdate`. Mixins defining these methods will no longer function correctly and must be renamed to their `UNSAFE_` counterparts (e.g., `UNSAFE_componentWillMount`) or processed with the `react-mixin/toUnsafe` utility.
fix Upgrade `react-mixin` to `^5.0.0`. Rename affected mixin methods (e.g., `componentWillMount` to `UNSAFE_componentWillMount`) or wrap the mixin with `toUnsafe()` before applying it: `reactMixin(MyClass.prototype, toUnsafe(myMixin));`.
gotcha `react-mixin` does not automatically bind `this` context for methods within mixins or ES6 class methods, unlike `React.createClass`. Method callbacks (e.g., event handlers) will lose `this` context if not explicitly bound.
fix Explicitly bind methods in your class constructor (e.g., `this.myMethod = this.myMethod.bind(this);`), or use public class fields with arrow functions (`myMethod = () => {}`) if your build setup supports it.
gotcha When using `reactMixin.decorate(mixin)` as an ES7 decorator, it performs prototypical inheritance, which means it returns a *new* class rather than mutating the original class in place. This can have implications for class identity checks or direct references to the original class.
fix Be aware that the decorated class is a new constructor. If strict class identity is required, apply mixins directly to the prototype or class using `reactMixin(Foo.prototype, mixin)` or `reactMixin.onClass(Foo, mixin)`.
gotcha `react-mixin` provides errors instead of silently overwriting conflicting methods from multiple mixins, with the exception of specific whitelisted methods (like `getDefaultProps` and `getInitialState`) which are explicitly designed for merging.
fix Inspect console errors for conflicting method names when applying multiple mixins. Refactor mixins to avoid name collisions or re-evaluate the need for multiple mixins if common functionality can be abstracted differently.
npm install react-mixin
yarn add react-mixin
pnpm add react-mixin

This code demonstrates how to apply mixins to React ES6 classes using `react-mixin`. It covers applying mixins to the prototype for instance methods, using `reactMixin.onClass` for static class properties like `getDefaultProps`, and utilizing `react-mixin/toUnsafe` for v5's `UNSAFE_` lifecycle method compatibility.

import React from 'react';
import reactMixin from 'react-mixin';
import toUnsafe from 'react-mixin/toUnsafe';

// Define a simple React Component
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    // Autobinding is not provided by react-mixin; explicit binding is necessary.
    this.handleClick = this.handleClick.bind(this);
  }

  // Example method that might be part of or overridden by a mixin
  handleClick() {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  }

  render() {
    return (
      <div>
        <h1>Count: {this.state.count}</h1>
        <button onClick={this.handleClick}>Increment</button>
        <p>Props: {JSON.stringify(this.props)}</p>
      </div>
    );
  }
}

// Define example mixins
const MyStateMixin = {
  getInitialState() { // This method will be merged
    return { mixedData: 'initial value' };
  },
  componentDidMount() {
    console.log('MyStateMixin mounted!');
  },
  // Version 5.0+ requires UNSAFE_ prefix for these lifecycle methods
  UNSAFE_componentWillMount() {
    console.log('MyStateMixin UNSAFE_componentWillMount!');
  }
};

const MyPropsMixin = {
  // This will be merged at the class level using .onClass
  getDefaultProps() {
    return { defaultPropFromMixin: 'default' };
  },
  componentDidUpdate(prevProps, prevState) {
    console.log('MyPropsMixin updated!');
  }
};

// --- Apply mixins to the component's prototype ---
reactMixin(MyComponent.prototype, MyStateMixin);

// --- Apply mixins with UNSAFE_ method conversion for v5+ ---
// If MyPropsMixin had componentWillMount, we'd use toUnsafe on it.
// For demonstration, let's assume it did and apply it through toUnsafe for clarity.
const fixedPropsMixin = toUnsafe(MyPropsMixin);
reactMixin(MyComponent.prototype, fixedPropsMixin);

// --- Apply mixins to the class itself for static properties (e.g., getDefaultProps) ---
reactMixin.onClass(MyComponent, MyPropsMixin); // Note: use the original mixin here for getDefaultProps merging.

// --- Example using the decorator syntax (requires Babel with decorator support) ---
// @reactMixin.decorate(MyStateMixin)
// class DecoratedComponent extends React.Component {
//   render() { return <div>Decorated Component!</div>; }
// }

// Simulate instantiation to show applied props/state (in a real app, you'd render this to DOM)
console.log("Component created with mixins applied. Check console for lifecycle logs.");
const instance = new MyComponent({ customInput: 'test' });
console.log('Initial component state:', instance.state); // Should include mixedData
console.log('Initial component props (merged with defaults):', instance.props); // Should include defaultPropFromMixin