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

Error for every fireEvent changing state in a component with a Switch #329

Closed
Pardiez opened this issue May 18, 2020 · 33 comments
Closed

Error for every fireEvent changing state in a component with a Switch #329

Pardiez opened this issue May 18, 2020 · 33 comments
Labels
upstream bug wontfix This will not be worked on

Comments

@Pardiez
Copy link

Pardiez commented May 18, 2020

Versions

"react": "16.11.0",
"react-native": "0.62.0"
"react-native-testing-library": "1.14.0",
"react-test-renderer": "16.11.0"

Description

I'm getting a console error for every fire event that changes the component state in a component with a Switch.

console.error node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js:608
  Warning: dispatchCommand was called with a ref that isn't a native component. Use React.forwardRef to get access to the underlying native component

Screenshot 2020-05-18 at 18 30 48

Reproducible Demo

https://github.com/Pardiez/rn-testing-library-329

// src/Dummy/__ tests __/Dummy.test.js

import React from 'react';
import { render, fireEvent } from 'react-native-testing-library';
import Dummy from '../';

describe('Dummy component', () => {
  test('renders correctly', () => {
    const { queryByTestId } = render(<Dummy />);

    fireEvent.changeText(queryByTestId('text-input'), 'stage');
    fireEvent.press(queryByTestId('touchable-opacity'));
    expect(queryByTestId('text-input')).toBeTruthy(); // dummy expect

  });
});
// src/Dummy/index.js

import React, { Component } from 'react';
import { View, TextInput, Switch, TouchableOpacity } from 'react-native';

class Dummy extends Component {
  constructor(props) {
    super(props);
    this.state = {
      text:'',
    };
  }

  render() {
    return (
      <View>
        <TextInput
          onChangeText={(text) => {this.setState({text})}}
          testID="text-input" />

        <TouchableOpacity
          onPress={() => {this.setState({text: ''})}}
          testID="touchable-opacity" />
          
        <Switch />
      </View>
    );
  }
}

export default Dummy;

Without setState on the fired event or without the Switch, there is no error.
This error occurs only with React Native 0.62+

@thymikee
Copy link
Member

Can you create a repro we can download?

@Pardiez
Copy link
Author

Pardiez commented May 19, 2020

Updated with repro link. Just a react-native initwith react-native-testing-library and that dummy component/test.
I have also tested it with RN0.61 without errors, so the error is logged only in 0.62+

@thymikee
Copy link
Member

Thanks! I think it's something specific to React Native then, and not to tests, but definitely worth to check and understand what's going on.

@Pardiez
Copy link
Author

Pardiez commented May 20, 2020

Maybe, but the error is logged when I run the test. I don't have any error/warning firing the same events on an emulator 🤔

@thymikee
Copy link
Member

Remember you're running your test in different environment (Node) than your emulator (JSC/Hermes). There may be slight differences, not sure if that's the case though. Let's wait for somebody to triage and investigate this. cc @mdjastrzebski @cross19xx

@mdjastrzebski
Copy link
Member

mdjastrzebski commented Jun 12, 2020

@Pardiez I was able to reproduce your issue using your code. Everything is a you diagnosed it, removing <Switch> or setState calls prevents the message.

So I've made a little experiment and swapped setState with forceUpdate calls that simply trigger render, and the message also appears. Moreover, when I ran your code as an app (not test) the warning does not appear.

This makes me believe that warning is caused by re-render of Switch component under react-test-renderer.

I've also found relevant commit in react-test-renderer react-native-renderer source: https://github.com/facebook/react/pull/16085/files

@thymikee maybe you can see using the linked PR if we can and/or should somehow mitigate it in RNTL.

@thymikee
Copy link
Member

@mdjastrzebski is the link correct? It points to changes in react-native-renderer. I'm not sure how this PR is relevant to the problem space.

@mdjastrzebski
Copy link
Member

@thymikee You're right, that this is from react-native-renderer. The warning message in the PR looks like the one @Pardiez is getting. His logs stack reference mentions node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js so looks like it's somehow connected.

@Merlier
Copy link

Merlier commented Jun 19, 2020

@Pardiez I got the same error:

  console.error node_modules/react-native/Libraries/LogBox/LogBox.js:33
    Warning: dispatchCommand was called with a ref that isn't a native component. Use React.forwardRef to get access to the underlying native component

Effectively it seems to be link with the Switch component. When I removed it, there is no more error.
Maybe it could be link with native stuff of the Switch component.
There is a old post (maybe link) about this: https://stackoverflow.com/questions/47058905/using-react-test-renderer-with-switch-causes-stateless-function-components-ca

To pass through this warning, I mock the Switch component.
I add at my root dir this file __mocks__/react-native.js with the code below (the Platform was for another purpose):

import React, {useState} from 'react';
import * as ReactNative from 'react-native';

export const Platform = {
  ...ReactNative.Platform,
  OS: 'android',
  Version: 123,
  select: objs => objs['android'],
};

export const Switch = props => {
  const [value, setValue] = useState(props.value);
  return (
    <ReactNative.TouchableOpacity
      onPress={() => {
        props.onValueChange(!value);
        setValue(!value);
      }}
      testID={props.testID}>
      <ReactNative.Text>{value.toString()}</ReactNative.Text>
    </ReactNative.TouchableOpacity>
  );
};

let newRN = Object.defineProperty(ReactNative, 'Platform', {
  get: function() {
    return Platform;
  },
});
newRN = Object.defineProperty(ReactNative, 'Switch', {
  get: function() {
    return Switch;
  },
});

module.exports = newRN;

In jest I can fire the event with:

    const mySwitchElement = tree.getByTestId(
      'mySwitch',
    );
    fireEvent(mySwitchElement, 'onPress', []);

@DracotMolver
Copy link

I'm facing the same issue with the <Switch /> component making use of the onValueChange prop.

// test.js
...
const switchBtn = await findByA11yRole('switch');
fireEvent(switchBtn, 'onValueChange');
...

@hsavit1

This comment has been minimized.

@thymikee
Copy link
Member

@TheSavior ideas about why this warning triggers for a native component in test environment? I've also tried this with 0.63, and latest nightly, the warning stays the same (minus component stack). Hope you have some additional context so we don't have to dig too far :)

@elicwhite
Copy link

Probably something about the mock that is being used for Switch not forwardRef'ing to the underlying component created with requireNativeComponent, or something like that.

That warning fires when dispatchCommand is called with a ref to a component that was created in JS like a JS function or class, vs a ref to a component created like this: requireNativeComponent('RCTSwitch')

@hsavit1

This comment has been minimized.

@hsavit1
Copy link

hsavit1 commented Aug 4, 2020

I was able to solve this by using fireEvent(switch, toggleState, true)

@jkdowdle
Copy link

@hsavit1 would you mind clarifying how you're firing the event? I want make sure I was trying what you were suggesting.

I am trying like this,

fireEvent(getByA11yLabel('my switch'), 'onValueChange', true)

But still getting the warning.

console.error node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js:608
Warning: dispatchCommand was called with a ref that isn't a native component. Use React.forwardRef to get access to the underlying native component

I wonder if it is an issue we are on an older version? react-native-testing-library@1.14.0

@thymikee
Copy link
Member

thymikee commented Aug 14, 2020

It's not related to this library, rather the react-native version itself. It's a warning, so I wouldn't bother too much.

@lukewlms
Copy link

lukewlms commented Sep 8, 2020

Thanks for the info. We can ignore for now for sure. It's a ~20-line bright red warning though so it is fairly eye-catching every time running tests.

@Kjaer
Copy link

Kjaer commented Sep 21, 2020

I tried this and it works like a charm,
#329 (comment)

thank you @Merlier

however, instead of creating __mocks__/react-native.js file, I edit my jest-setup.js on my root folder. My jest config on my package.json points it as setupFiles

"jest": {
    "setupFiles": [
      "<rootDir>/jest-setup.js"
    ]
  }

here is the glimpse of my jest-setup.js

const React = require('react')
const ReactNative = require('react-native')

const Switch = function(props) {
  const [value, setValue] = React.useState(props.value)

  return (
    <ReactNative.TouchableOpacity
      onPress={() => {
        props.onValueChange(!value)
        setValue(!value)
      }}
      testID={props.testID}>
      <ReactNative.Text>{value.toString()}</ReactNative.Text>
    </ReactNative.TouchableOpacity>
  )
}

Object.defineProperty(ReactNative, 'Switch', {
  get: function() {
    return Switch
  }
})

@Kjaer
Copy link

Kjaer commented Sep 25, 2020

@thymikee what kind of action do you need for this? I am happy to offer my help

@thymikee
Copy link
Member

Based on #329 (comment) I'd say it's an upstream issue. You can investigate the source of the problem in the RN mocks, if it's not resolved on master branch already.

@thymikee thymikee added upstream bug wontfix This will not be worked on and removed needs triage labels Sep 28, 2020
@Andarius
Copy link

Andarius commented Dec 2, 2020

FYI: mocking with the following works (https://jestjs.io/docs/en/tutorial-react-native#mock-native-modules-using-jestmock)

jest.mock('react-native/Libraries/Components/Switch/Switch', () => {
    const mockComponent = require('react-native/jest/mockComponent')
    return mockComponent('react-native/Libraries/Components/Switch/Switch')
})

Edit:

As mentioned by @kptp below, since 0.66 you need to do the following instead:

jest.mock('react-native/Libraries/Components/Switch/Switch', () => {
  const mockComponent = require('react-native/jest/mockComponent')
  return {
    default: mockComponent('react-native/Libraries/Components/Switch/Switch')
  }
})

@gabimoncha
Copy link

@Andarius it works like a charm!

@nickhealy
Copy link

@Andarius thank you! This worked amazing, and is really simple and follows the pattern for mocking other modules.

@kptp
Copy link

kptp commented Nov 26, 2021

This issue popped up for me again after updating react-native to version 0.66.2. The fix was to change the mock to return a default export like so:

jest.mock('react-native/Libraries/Components/Switch/Switch', () => {
  const mockComponent = require('react-native/jest/mockComponent');
  return {
    default: mockComponent('react-native/Libraries/Components/Switch/Switch'),
  };
});

@roryf
Copy link

roryf commented Jan 28, 2022

We have tests that use getAllByRole('switch'), so the mockComponent solution resulted in error No instances found with accessibilityRole "switch". Using #329 (comment) with an additional accessibilityRole="switch" solved the issue. We also use accessibilityLabel which was added too:

const Switch = function (props) {
  const [value, setValue] = React.useState(props.value)

  return (
    <ReactNative.TouchableOpacity
      onPress={() => {
        props.onValueChange(!value)
        setValue(!value)
      }}
      testID={props.testID}
      accessibilityRole="switch"
      accessibilityLabel={props.accessibilityLabel}
    >
      <ReactNative.Text>{value.toString()}</ReactNative.Text>
    </ReactNative.TouchableOpacity>
  )
}

Object.defineProperty(ReactNative, 'Switch', {
  get: function () {
    return Switch
  },
})

This did prevent us from asserting on the value prop, but that's outside the scope of this issue.

@crjacinro
Copy link

No solution in this thread worked for me. Is there an update on this? Do I need to update React Native version or React-Test-Renderer?

@gabimoncha
Copy link

Have you tried this?
#329 (comment)

@crjacinro
Copy link

Have you tried this? #329 (comment)

Yes. I resolved it now. I had to update react-test-renderer to 17.0.2 to make the above solution work.

@PaulSzymanski
Copy link

I resolved this issue by upgrading react-native from 0.66.4 to 0.68.2. So it seems it was fixed upstream.

@mdjastrzebski
Copy link
Member

Closing as resolved upstream.

@Pardiez if you find that the problem is still present in the current version of RNTL (v11) in recommended setup as in basic example app, please create a new GH issue for it. If so, please also provide a repro repository.

@clytras
Copy link

clytras commented Oct 10, 2022

Only a default jest.fn() worked for me on RN 0.69

jest.mock('react-native/Libraries/Components/Switch/Switch', () => ({
  default: jest.fn(),
}));

@kyleshevlin
Copy link

I just mentioned this in the closed issue of #1422

I have pinned down the version that started this issue as 12.1.2. Works fine in 12.1.1, I believe something may have happened in this commit: 72e3133

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
upstream bug wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests