Skip to content

Should maintain component instance when asMiddleware is used #89

@OEvgeny

Description

@OEvgeny

Version

Latest development (@main)

Module resolution

ESM: import { createChainOfResponsibility } from "react-chain-of-responsibility"

Bundler

Others or unrelated

Environment

Others or unrelated

Test case

(not tested this)

/** @jest-environment jsdom */
/// <reference types="@types/jest" />

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import React, { useState } from 'react';
import { RCoRTree } from '../components/RCoRTree'; // Adjust path if necessary

describe('React Chain of Responsibility Bug Reproduction', () => {
  test('StatefulCounter loses internal state and is recreated when RCoR request changes', async () => {
    const TestWrapper = () => {
      const [variant, setVariant] = useState<'blue' | 'green' | 'purple'>('blue');
      const [rcorCount, setRcorCount] = useState(0);

      return (
        <div>
          <button onClick={() => setVariant('green')}>Change Variant to Green</button>
          <RCoRTree variant={variant} onCountChange={setRcorCount} />
          <div data-testid="rcor-external-count">External RCoR Count: {rcorCount}</div>
        </div>
      );
    };

    render(<TestWrapper />);

    // 1. Verify initial state and increment the RCoR counter
    const initialRCoRCounterDisplay = screen.getByText(/Counter \(blue\)/i);
    const initialRCoRCounterContainer = initialRCoRCounterDisplay.closest('.p-6');
    const initialRCoRIncrementButton = initialRCoRCounterContainer?.querySelector('[aria-label="Increment"]');

    expect(initialRCoRIncrementButton).toBeInTheDocument();
    expect(initialRCoRCounterContainer).toHaveTextContent('0'); // Initial count is 0

    // Get initial instance ID
    const initialInstanceIdMatch = initialRCoRCounterContainer?.textContent?.match(/Instance #(\d+)/);
    const initialInstanceId = initialInstanceIdMatch ? parseInt(initialInstanceIdMatch[1], 10) : null;
    expect(initialInstanceId).not.toBeNull();

    // Increment the counter multiple times
    fireEvent.click(initialRCoRIncrementButton!);
    fireEvent.click(initialRCoRIncrementButton!);
    fireEvent.click(initialRCoRIncrementButton!);

    // Wait for the displayed count to update to 3
    await waitFor(() => {
      expect(initialRCoRCounterContainer).toHaveTextContent('3');
    });
    // Also check the external state update
    expect(screen.getByTestId('rcor-external-count')).toHaveTextContent('External RCoR Count: 3');


    // 2. Trigger a variant change
    const changeVariantButton = screen.getByText('Change Variant to Green');
    fireEvent.click(changeVariantButton);

    // 3. Verify state loss: The RCoR counter should reset to 0
    // The label will change to "Counter (green)", so find the new counter display
    const newRCoRCounterDisplay = screen.getByText(/Counter \(green\)/i);
    const newRCoRCounterContainer = newRCoRCounterDisplay.closest('.p-6');

    await waitFor(() => {
      // The count should be 0 again, indicating state loss
      expect(newRCoRCounterContainer).toHaveTextContent('0');
    });
    // Also check the external state update
    expect(screen.getByTestId('rcor-external-count')).toHaveTextContent('External RCoR Count: 0');

    // 4. Verify instance ID changes, confirming recreation
    const newInstanceIdMatch = newRCoRCounterContainer?.textContent?.match(/Instance #(\d+)/);
    const newInstanceId = newInstanceIdMatch ? parseInt(newInstanceIdMatch[1], 10) : null;
    
    expect(newInstanceId).not.toBeNull();
    expect(newInstanceId).not.toBe(initialInstanceId); // This confirms recreation
  });
});

Coding sandbox URL

https://stackblitz.com/edit/sb1-hks9ug7t?file=src%2Fcomponents%2FReproDemo.tsx

Console errors

No errors

Screenshots

Screenshot right after variant got changed (RCoR tree counter was equal to the left tree):

Image

Additional context

It's worsen without memo. Without memo the component being re-created basically on every re-render.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions