Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to mock local state of Redux connected component #828

Closed
sirkorgan opened this issue Aug 30, 2018 · 6 comments
Closed

Unable to mock local state of Redux connected component #828

sirkorgan opened this issue Aug 30, 2018 · 6 comments

Comments

@sirkorgan
Copy link

What's wrong?

I want to mock state in a fixture for a Redux-connected component. It works when my component is not connected, but when my component is connected, the state in the fixture is not applied.

Steps to reproduce

Fixture which reproduces the issue:

// StateTester.fixture.js

import React from 'react'
import { connect } from 'react-redux'

export class StateTester extends React.Component {
  constructor(props) {
    super(props)
    this.state = {}
  }

  render() {
    console.log('rendering:', { state: this.state, props: this.props })
    return (
      <div>
        <h4>My local state is:</h4>
        <pre>{JSON.stringify(this.state, null, 2)}</pre>
      </div>
    )
  }
}

export const ConnectedStateTester = connect(state => ({}), dispatch => ({}))(
  StateTester
)

export default [
  {
    name: 'checking state injection',
    component: StateTester,
    state: {
      shouldBePresent: true
    }
  },
  {
    name: 'works with connect?',
    component: ConnectedStateTester,
    state: {
      shouldBePresent: true
    },
    reduxState: {}
  }
]

Screenshots

Observed results in cosmos ui:
normal-component

connected-component

User info

  • Cosmos version: react-cosmos@4.6.4
  • Redux proxy version: react-cosmos-redux-proxy@4.6.1
  • Cosmos config:
// cosmos.config.js

module.exports = {
  containerQuerySelector: '#root',
  webpackConfigPath: 'react-scripts/config/webpack.config.dev',
  publicPath: 'public',
  proxiesPath: 'src/cosmos.proxies'
}
  • Cosmos proxy config:
// cosmos.proxies.js

import { createStore } from 'redux'
import createReduxProxy from 'react-cosmos-redux-proxy'

import reducer from 'reducers/root'

const ReduxProxy = createReduxProxy({
  createStore: initialState => {
    return createStore(reducer)
  }
})

export default [ReduxProxy]

Additional Context

Not sure if it matters, but I also reproduced this in the headless environment:
test-output

Test code producing the above:

// StateTester.test.js
import createTestContext from 'react-cosmos-test/enzyme'
import fixture from '../__fixtures__/StateTester.fixture'

test('normal component fixture has expected state', () => {
  console.log('Testing normal component ==========')
  const { mount, getWrapper, get } = createTestContext({ fixture: fixture[0] })
  mount()
  expect(get('state').shouldBePresent).toEqual(true)
})

test('connected component fixture has expected state', () => {
  console.log('Testing connected component ==========')
  const { mount, getWrapper, get } = createTestContext({
    fixture: fixture[1]
  })
  mount()
  expect(get('state').shouldBePresent).toEqual(true)
})
@ovidiuch
Copy link
Member

Hi @wec84,

Thank you for the elaborate response!

Unfortunately this is a known limitation (for now), so maybe we add this to the docs to inform people sooner.

The problem is this: Once you wrap a component, the instance of the inner component is no longer accessible, it's a private detail of the wrapper component, which what Cosmos receives when you have a fixture for a wrapped component.

But what about forwardRef, you might ask? Yes, that is the cure. But redux-connect does not use it yet.

Here's a thread: reduxjs/react-redux#914
And here's a PR where they're working to add it in react-redux version 6.x reduxjs/react-redux#1000

@uxtechie
Copy link

uxtechie commented Sep 10, 2018

Hi,

I thing you can use this pattern to wrap your third party HOC (the sample is for the Yahoo injectIntl HOC):

// @flow
// injectIntl.js

// Intl flow definitions managed by flow-typed:
// https://github.com/flow-typed/flow-typed

import * as React from 'react';
import { injectIntl as originalInjectIntl } from "react-intl";
// noinspection ES6CheckImport
import type { IntlShape } from "react-intl";

// constant
const __DEV__ = process.env.NODE_ENV !== 'production';

// utils
function getComponentName(Component: React.ComponentType<any>): string {
    if (typeof Component === 'object') {
        // $FlowFixMe React.forwardRef does not have a flow definition at the moment
        return Component.render.displayName;
    }
    return Component.displayName || Component.name || 'Component';
}

function getComponentWithProperName<EC: React.ComponentType<any>, WC: React.ComponentType<any>>(
    EnhancedComponent: EC,
    WrappedComponent: WC,
    HigherOrderComponent: (WC, ...other: Array<any>) => EC
): EC {
    if (__DEV__) {
        // noinspection JSUndefinedPropertyAssignment
        EnhancedComponent.displayName =
            `${HigherOrderComponent.name}(${getComponentName(WrappedComponent)})`;
    }
    return EnhancedComponent;
}

// your new HOC
export default function injectIntl<PropsWC: {},
    PropsEC: $Diff<PropsWC, {|
        intl: IntlShape | void,
    |}>>(
    WrappedComponent: React.ComponentType<PropsWC>
): React.ComponentType<PropsEC> {

    class PropRefRouterComponent extends React.Component<PropsWC & { forwardedRef: * }> {
        render() {
            const {forwardedRef, ...other} = this.props;

            return (
                <WrappedComponent {...other}
                                  ref={forwardedRef}/>
            )
        }
    }

    const WithIntlComponent = originalInjectIntl(PropRefRouterComponent);

    function forwardRefRenderFn(props: PropsEC, ref: *) {
        return <WithIntlComponent {...props} forwardedRef={ref}/>;
    }

    // $FlowFixMe React.forwardRef does not have a flow definition at the moment
    return React.forwardRef(
        getComponentWithProperName(forwardRefRenderFn, WrappedComponent, injectIntl));
}

@adam8810
Copy link

@skidding just so you're aware it appears that they decided to release it in 5.1.0. I've upgraded my versions of redux and react-redux to the latest versions and it doesn't appear that this fixes the issue. Are you aware of changes that need to be made to this project in order for it to work?

@ovidiuch
Copy link
Member

@skidding just so you're aware it appears that they decided to release it in 5.1.0. I've upgraded my versions of redux and react-redux to the latest versions and it doesn't appear that this fixes the issue. Are you aware of changes that need to be made to this project in order for it to work?

Thanks for the heads-up, @adam8810!

I didn't get my hands on react-redux 5.1 yet. If you share an example repo I'll look into it and report back with my findings!

@ovidiuch
Copy link
Member

I glanced quickly over this commit and connectAdvanced.js, and it seems forwardRef is opt-in. You may need to pass forwardRef: true to connect options.

Let me know if it works.

@ovidiuch
Copy link
Member

Let me know if you have any update on this. Note that react-redux6+ isn't supported at the moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants