I have Rails Application with React On Rails, recently upgraded webpack 3 to webpack 4
Rails : 4 Ruby: 2+ React: 16.9 Webpack: 4.41.5
everything works well except below code which works well with webpack 3
In the console I see below error
Uncaught Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
An error occurred during a React Lifecycle function
import Error from '@material-ui/icons/Error';
import MoreHoriz from '@material-ui/icons/MoreHoriz';
import { Button, FixedFooterModal, SafeAnchor, Util } from 'lib-components';
import moment from 'moment-timezone';
import prettyBytes from 'pretty-bytes';
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import Helpers from '../../../lib/lib-components/src/js/services/Helpers'; // eslint-disable-line
import styles from './ExportForm.css';
interface Export {
  created_at: string;
  filename: string;
  filesize: number;
  id: number;
  project_id: number;
  status: string;
  updated_at: string;
  options: string;
}
function parseOptions(options: string) {
  return options
    .split(',')
    .map(option => {
      switch (option) {
        case 'include_done_stories':
          return 'Done';
        case 'include_current_backlog_stories':
          return 'Current/Backlog';
        case 'include_icebox_stories':
          return 'Icebox';
        case 'include_epics':
          return 'Epics';
        case 'include_attachments':
          return 'Attachments';
        case 'include_project_history':
          return 'Project History';
      }
    })
    .join(', ');
}
const ExportStatus: React.FunctionComponent<{
  status: string;
  projectId: number;
  exportId: number;
  csrfToken: string;
  setModalOpen: (a: boolean) => void;
}> = ({ status, projectId, exportId, csrfToken, setModalOpen }) => {
  const handleRetryClick = useCallback(() => {
    Helpers.update(csrfToken, {
      method: 'GET',
      url: `/projects/${projectId}/export/${exportId}/retry`,
    });
    setModalOpen(true);
  }, [projectId, exportId, csrfToken, setModalOpen]);
  switch (status) {
    case 'failed':
      return (
        <div data-aid='ExportForm__failed' className={styles['ExportForm__failed']}>
          <div className={styles['ExportForm__failedIcon']}>
            <Error fontSize='small' style={{ marginRight: '4px', color: '#CB2B1E' }} />
            <span>Export failed</span>
          </div>
          <div data-aid='ExportForm__retryLink' className={styles['ExportForm__retryLink']}>
            <SafeAnchor onClick={handleRetryClick}>retry</SafeAnchor>
          </div>
        </div>
      );
    case 'completed':
      return (
        <div data-aid='ExportForm__completed' className={styles['ExportForm__completed']}>
          <div className={styles['ExportForm__completedIcon']}>
            <CheckCircle fontSize='small' style={{ marginRight: '4px', color: '#6D902A' }} />
            <span>Complete</span>
          </div>
          <div className={styles['ExportForm__completedDownloadLink']}>
            <SafeAnchor href={`/projects/${projectId}/export/${exportId}/download`}>download</SafeAnchor>
          </div>
        </div>
      );
    default:
    case 'in progress':
      return (
        <div className={styles['ExportForm__inProgressIcon']}>
          <div className={styles['ExportForm__inProgressIconBg']}>
            <MoreHoriz fontSize='small' />
          </div>
          <span data-aid='ExportForm__inProgress' className={styles['ExportForm__inProgress']}>
            In Progress
          </span>
        </div>
      );
  }
};
const ExportForm: React.FunctionComponent<{
  csrfToken: string;
  id: number;
  exports: Export[];
  timezone: string;
}> = ({ csrfToken, id, exports, timezone }) => {
  const [modalOpen, setModalOpen] = useState(false);
  const [includeDoneStories, setIncludeDoneStories] = useState(true);
  const [includeCurrentBacklogStories, setIncludeCurrentBacklogStories] = useState(true);
  const [includeIceboxStories, setIncludeIceboxStories] = useState(true);
  const [includeEpics, setIncludeEpics] = useState(true);
  const [includeAttachments, setIncludeAttachments] = useState(true);
  const [includeProjectHistory, setIncludeProjectHistory] = useState(true);
  const setDoneStoriesFromEvent = useCallback(e => setIncludeDoneStories(e.target.checked), []);
  const setCurrentBacklogStoriesFromEvent = useCallback(e => setIncludeCurrentBacklogStories(e.target.checked), []);
  const setIceboxStoriesFromEvent = useCallback(e => setIncludeIceboxStories(e.target.checked), []);
  const setEpicsFromEvent = useCallback(e => setIncludeEpics(e.target.checked), []);
  const setAttachmentsFromEvent = useCallback(e => setIncludeAttachments(e.target.checked), []);
  const setProjectHistoryFromEvent = useCallback(e => setIncludeProjectHistory(e.target.checked), []);
  const handleExportClicked = useCallback(() => {
    Helpers.update(
      csrfToken,
      {
        method: 'POST',
        url: `/projects/${id}/export`,
      },
      {
        options: {
          include_done_stories: includeDoneStories,
          include_current_backlog_stories: includeCurrentBacklogStories,
          include_icebox_stories: includeIceboxStories,
          include_epics: includeEpics,
          include_attachments: includeAttachments,
          include_project_history: includeProjectHistory,
        },
      }
    );
    setModalOpen(true);
  }, [
    csrfToken,
    id,
    includeDoneStories,
    includeCurrentBacklogStories,
    includeIceboxStories,
    includeEpics,
    includeAttachments,
    includeProjectHistory,
  ]);
  const handleCloseModal = useCallback(() => {
    setModalOpen(false);
    Util.windowLocation().assign(`/projects/${id}/export`);
  }, [id]);
  const justRefresh = useMemo(() => exports.some(e => e.status === 'in progress'), [exports]);
  useEffect(() => {
    let timer: ReturnType<typeof setTimeout>;
    if (justRefresh) {
      timer = setTimeout(() => Util.windowLocation().reload(), 30000);
    }
    return () => {
      clearTimeout(timer);
    };
  }, [justRefresh]);
  return (
    <div className={styles['ExportForm']}>
      <h2>Create New Export</h2>
      <p>
        Stories, Epics, and Project History will be exported as a CSV. All files will be available to download from the
        exports section below. Files are available for two weeks.
      </p>
      <div className={styles['ExportForm__options']}>
        <div className={styles['ExportForm__option']}>
          <label>
            <input type='checkbox' checked={includeDoneStories} onChange={setDoneStoriesFromEvent} />
            All Done Stories
          </label>
        </div>
        <div className={styles['ExportForm__option']}>
          <label>
            <input
              type='checkbox'
              checked={includeCurrentBacklogStories}
              onChange={setCurrentBacklogStoriesFromEvent}
            />
            All Current/Backlog Stories
          </label>
        </div>
        <div className={styles['ExportForm__option']}>
          <label>
            <input type='checkbox' checked={includeIceboxStories} onChange={setIceboxStoriesFromEvent} />
            All Icebox Stories
          </label>
        </div>
        <div className={styles['ExportForm__option']}>
          <label>
            <input type='checkbox' checked={includeEpics} onChange={setEpicsFromEvent} />
            All Epics
          </label>
        </div>
        <div className={styles['ExportForm__option']}>
          <label>
            <input
              data-aid='ExportForm__attachments'
              type='checkbox'
              checked={includeAttachments}
              onChange={setAttachmentsFromEvent}
            />
            All Attachments
          </label>
        </div>
        <div className={styles['ExportForm__option']}>
          <label>
            <input
              data-aid='ExportForm__attachments'
              type='checkbox'
              checked={includeProjectHistory}
              onChange={setProjectHistoryFromEvent}
            />
            Project History
          </label>
        </div>
      </div>
      <Button
        label={<span>Export</span>}
        size='large'
        type='primary'
        data-aid='ExportForm__exportButton'
        onClick={handleExportClicked}
      />
      {modalOpen && (
        <FixedFooterModal
          buttons={[
            {
              props: {
                label: 'Close',
                type: 'lined',
                onClick: handleCloseModal,
                align: 'right',
                'data-aid': 'ExportForm__closeModal',
              },
            },
          ]}
          title='Export in progress'
          onClose={handleCloseModal}
          noScroll={true}
        >
          <div className={styles['ExportForm__inProgressModal']}>
            <p>We're on it! We will send you an email once your data is available to download.</p>
            <p>
              Check back here to find all of your available exports. You can access your exported data here for two
              weeks.
            </p>
          </div>
        </FixedFooterModal>
      )}
      <h2>Exports</h2>
      {exports.length > 0 ? (
        exports.map(exp => (
          <div key={exp.id} className={styles['ExportForm__export']} data-aid='ExportForm__export'>
            <div className={styles['ExportForm__exportInfo']}>
              <div className={styles['ExportForm__exportTimes']}>
                <span data-aid='ExportForm__created'>
                  {moment.tz(exp.created_at, timezone).format('MMM Do, YYYY [at] h:mm A z')}
                </span>
                {exp.filesize ? ` (${prettyBytes(exp.filesize)}) • ` : ` • `}
                <span data-aid='ExportForm__expires'>
                  Expires on {moment.tz(exp.created_at, timezone).add(14, 'days').format('MMM Do, YYYY')}
                </span>
              </div>
              <div className={styles['ExportForm__exportOptions']}>
                <span data-aid='ExportForm__exportOptions'>({parseOptions(exp.options)})</span>
              </div>
            </div>
            <div className={styles['ExportForm__download']}>
              <ExportStatus
                status={exp.status}
                projectId={exp.project_id}
                exportId={exp.id}
                csrfToken={csrfToken}
                setModalOpen={setModalOpen}
              />
            </div>
          </div>
        ))
      ) : (
        <div className={styles['ExportForm__emptyMessage']} data-aid='ExportForm__emptyMessage'>
          <p>No exports have been created.</p>
        </div>
      )}
    </div>
  );
};
export default ExportForm;
I tried following solutions for similar error but of no help
- Checked for multiple versions of react
- disabling react hot loader and setConfig pureSFC to true
- Used React.FunctionComponent
Also this componenet is rendered by React.lazy() later I figured all the components rendered by lazy loading have inavlid hook call