JIrv
JIrv

Reputation: 71

Warning: React.createElement: type is invalid -- expected a string or a class/function but got: undefined

The error:

console.error node_modules/react/cjs/react.development.js:172

Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

console.log __tests__/Dashboard-test.js:278

I have created a custom button for my dashboard and I am trying to test its functionality using Jest and Enzyme. From what I've read, this warning is generated because of mixing up imports, but I don't believe this is the case for my component.

Here is the test case (which passes but produces the warning as shown in the title):

// __tests__/Dashboard-test.js
import React from 'react';
import {shallow, configure} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import {Provider} from 'react-redux';
import Dashboard from '../src/components/pages/Dashboard';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
configure({adapter: new Adapter()});
const props = {
  navigation: {
    navigate: jest.fn(),
  },
};

it('navigates to diary page when diary button is pressed', () => {
  const initialState = {
    authorisationReducer: {
      loggedIn: true,
    },
  };
  const store = mockStore(initialState);
  const wrapper = shallow(<Dashboard {...props} store={store} />).dive();
  const instance = wrapper.instance();

  instance.forceUpdate();

  const button = wrapper.findWhere(
    n => n.prop('accessibilityLabel') === 'Diary button',
  );

  button
    .props()
    .customClick();
  expect(props.navigation.navigate).toHaveBeenCalledWith('Diary');

  console.log(button.debug());
});

The dashboard component:

/* Dashboard with custom buttons to navigate between pages */
import React, {Component} from 'react';
import {View, StyleSheet} from 'react-native';
import firebase from 'react-native-firebase';
import SplashScreen from 'react-native-splash-screen';
import {DashboardButton} from '../layout/DashboardButton';
import {connect} from 'react-redux';
import Auth0 from 'react-native-auth0';
import base64 from 'react-native-base64';
import * as actions from '../../actions/index';
import {NavigationEvents} from 'react-navigation';

const auth0 = new Auth0({
  domain: 'xxx',
  clientId: 'xxx',
});

export class Dashboard extends Component {
  constructor(props) {
    super(props);

    // present log in page if user is not logged in
    if (props.loggedIn !== true) {
      this.login();
    }

    // show dashboard
    SplashScreen.hide();
  }

  login() {
    auth0.webAuth
      .authorize({scope: 'openid profile'})
      .then(credentials => {
        // successfully authenticated - set userId
        let userId = JSON.parse(
          base64
            .decode(this.unescape(credentials.idToken), 'base64')
            .toString(),
        ).sub;

        firebase
          .messaging()
          .getToken()
          .then(token => {
            this.props.addDevice(userId, token);
            this.props.loginUser(userId, token);
            this.props.loadInitialReminders();
            this.props.loadInitialDiaryEntries();
          });
      })
      .catch(error => {
        console.log(error);
      });
  }

  // converts base64 to base64url
  unescape(str) {
    // get the correct part of the token
    str = str.split('.')[1];
    return (str + '==='.slice((str.length + 3) % 4))
      .replace(/-/g, '+')
      .replace(/_/g, '/');
  }

  render() {
    return (
      <View accessible={true} style={styles.mainContainer}>
        <NavigationEvents
          onDidFocus={() => {
            if (this.props.loggedIn !== true) {
              this.login();
            }
          }}
        />

        <DashboardButton
          accessibilityLabel={'Physiotherapy button'}
          accessibilityHint={
            'Navigates to the Physiotherapy exercise categories screen'
          }
          disabled={!this.props.loggedIn}
          title="PHYSIOTHERAPY"
          customClick={() =>
            this.props.navigation.navigate('PhysiotherapyExerciseCategories')
          }
        />
        <DashboardButton
          accessibilityLabel={'Reminders button'}
          accessibilityHint={'Navigates to the Reminders screen'}
          disabled={!this.props.loggedIn}
          title="REMINDERS"
          customClick={() => this.props.navigation.navigate('Reminders')}
        />
        <DashboardButton
          accessibilityLabel={'Diary button'}
          accessibilityHint={'Navigates to the Diary screen'}
          disabled={!this.props.loggedIn}
          title="DIARY"
          customClick={() => this.props.navigation.navigate('Diary')}
        />
      </View>
    );
  }
}

const mapStateToProps = state => {
  return {
    loggedIn: state.authorisationReducer.loggedIn,
    reminders: state.remindersReducer.reminders,
    notificationsSet: state.remindersReducer.notificationsSet,
  };
};

export default connect(
  mapStateToProps,
  actions,
)(Dashboard);

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    backgroundColor: 'white',
    flexDirection: 'column',
  },
});

The dashboard button:

/* Custom button on Dashboard */
import React from 'react';
import {TouchableOpacity, Text, StyleSheet} from 'react-native';
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome';
import {connect} from 'react-redux';
import * as actions from '../../actions/index';
import {
  faDumbbell,
  faBook,
  faCalendarCheck,
} from '@fortawesome/free-solid-svg-icons';
import {
  REGULAR_FONT,
  TULIP_DARK_MID_THEME_COLOUR,
  TULIP_LIGHT_MID_THEME_COLOUR,
  TULIP_LIGHT_THEME_COLOUR,
} from '../../constants';

const physiotherapyIcon = faDumbbell;
const diaryIcon = faBook;
const remindersIcon = faCalendarCheck;

export const DashboardButton = props => {
  let buttonStyle;
  let buttonIcon;

  if (props.title.toUpperCase() === 'PHYSIOTHERAPY') {
    buttonStyle = styles.physiotherapyButton;
    buttonIcon = physiotherapyIcon;
  } else if (props.title.toUpperCase() === 'DIARY') {
    buttonStyle = styles.diaryButton;
    buttonIcon = diaryIcon;
  } else if (props.title.toUpperCase() === 'REMINDERS') {
    buttonStyle = styles.remindersButton;
    buttonIcon = remindersIcon;
  }

  return (
    <TouchableOpacity
      accessibilityLabel={props.accessibilityLabel}
      accessibilityHint={props.accessibilityHint}
      disabled={props.disabled}
      style={buttonStyle}
      onPress={() => {
        if (props.enabledLongPress === false) {
          props.customClick();
        }
      }}
      onLongPress={() => {
        props.customClick();
      }}>
      <FontAwesomeIcon
        icon={buttonIcon}
        color={'white'}
        size={60}
        marginRight={25}
      />
      <Text style={styles.text}>{props.title}</Text>
    </TouchableOpacity>
  );
};

const mapStateToProps = state => {
  return {
    enabledLongPress: state.settingsReducer.enabledLongPress,
  };
};

export default connect(
  mapStateToProps,
  actions,
)(DashboardButton);

const styles = StyleSheet.create({
  physiotherapyButton: {
    backgroundColor: TULIP_DARK_MID_THEME_COLOUR,
    color: 'white',
    flex: 1,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'flex-start',
    paddingLeft: 50,
  },
  remindersButton: {
    backgroundColor: TULIP_LIGHT_MID_THEME_COLOUR,
    color: 'white',
    flex: 1,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'flex-start',
    paddingLeft: 50,
  },
  diaryButton: {
    backgroundColor: TULIP_LIGHT_THEME_COLOUR,
    color: 'white',
    flex: 1,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'flex-start',
    paddingLeft: 50,
  },
  text: {
    color: 'white',
    fontFamily: REGULAR_FONT,
    fontSize: 25,
  },
});

I have tried changing the Dashboard button to a class that extends Component and changing the import statements to include {} (not at the same time), but the warning persists.

Upvotes: 2

Views: 9948

Answers (1)

Giwan
Giwan

Reputation: 1590

I encountered the same error message. In my case the problem was to be found in a warning from "react-helmet". I had updated it and now needed to import it as a named component instead

// import Helmet from "react-helmet"; old
import { Helmet } from "react-helmet"; 

Your Dashboard component or one of it's sub-components is probably failing during the test because it needs a prop to render the component but is not getting it.

I suggest breaking up the tests. Write tests for the sub-components first. That should tell you which properties you need to pass down.

Or you can comment out some of the sub components until you can get the test to pass.

Upvotes: 1

Related Questions