React-native-testing-library: Error for every fireEvent changing state in a component with a Switch

Created on 18 May 2020  路  22Comments  路  Source: callstack/react-native-testing-library

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+

upstream bug wontfix

Most helpful comment

@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.

All 22 comments

Can you create a repro we can download?

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+

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.

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 馃

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

@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.

@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.

@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.

@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', []);

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

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

also ran into this on RN 62.2

@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 :)

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')

I'm seeing

    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 was able to solve this by using fireEvent(switch, toggleState, true)

@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? [email protected]

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

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.

I tried this and it works like a charm,
https://github.com/callstack/react-native-testing-library/issues/329#issuecomment-646610078

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
  }
})

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

Based on https://github.com/callstack/react-native-testing-library/issues/329#issuecomment-665367487 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.

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')
})
Was this page helpful?
0 / 5 - 0 ratings