Search code examples
reactjsreact-routerreact-router-dommobxmobx-react

Higher Order Function - Element Type is invalid: expected string or class/function but got object


I am working through a tutorial (https://www.johnstewart.dev/firebase-auth-react-mobx/) playing with Mobx, React and Auth. However I am getting this error message

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. Check the render method of 'Context.Consumer'.

which I suspect is due to the following code. This is meant as a higher order function that wraps the components within the router to test for authentication state.

import React from "react";
import authStore from "../../store/authStore";
import { Redirect } from "react-router-dom";

const protectedRoute = (RouteComponent) => {
  if (authStore.loggedIn) {
    return RouteComponent;
  }
  return <Redirect to="/login" />;
};

export default protectedRoute;

The wrapped component in app.js looks like;

<Route path="/register" component={protectedRoute(Register)} />

My reasoning is that when I manually change my authStore.loggedIn to true then it all passes perfectly. However when I set authStore.loggedIn to false the error appears.

No doubt it is something insanely simple with the Redirect but searching stackoverflow wasn't super helpful, nor was the web at large. Any suggestions??

register.js file as requested, though this renders correctly via the protected route. So I don't believe that this is where the issue lies.

import React, { useEffect, useState } from "react";
import { inject, observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlusSquare } from "@fortawesome/free-solid-svg-icons";
import BootstrapTable from "react-bootstrap-table-next";
import filterFactory, {
  textFilter,
  selectFilter,
} from "react-bootstrap-table2-filter";
import rp from "request-promise";

import Spinner from "../layout/Spinner";

const Register = (props) => {
  const [dataLoaded, setdataLoaded] = useState(false);

  useEffect(() => {
    rp.get({
      uri: "http://localhost:5050/api/risk",
      json: true,
      headers: {
        "User-Agent": "Request-Promise",
      },
    })
      .then((risks) => {
        props.riskStore.riskList = risks;
        console.log("Risks successfully loaded.");
        setdataLoaded(true);
      })
      .catch((error) => {
        console.log(error);
      });
  }, []);

  const rowEvents = {
    onClick: (e, row, rowIndex) => {
      props.history.push(`/risk/${row._id}`);
    },
  };

  return dataLoaded ? (
    <div>
      <br />
      <h1 className="register-heading">Risk Register</h1>
      <div className="row">
        <div className="col-md-4">
          <p className="register-text">
            Please use the column headings to sort the register. To add a new
            task, click the button to the right.
          </p>
        </div>
        <div className="col-md-6"></div>
        <div className="col-md-2">
          <button
            className="btn btn-primary"
            onClick={() => props.history.push("/risk/new")}
          >
            <FontAwesomeIcon icon={faPlusSquare} /> New Task
          </button>
        </div>
      </div>

      <BootstrapTable
        keyField="id"
        data={props.riskStore.riskList}
        columns={risksColumns}
        filter={filterFactory()}
        filterPosition="top"
        striped={true}
        hover={true}
        condensed={true}
        rowEvents={rowEvents}
        bootstrap4={true}
      />
    </div>
  ) : (
    <Spinner />
  );
};

const selectOptions = {
  true: "Archived",
  false: "Active",
};

const archiveFormatter = (cell, row, rowIndex, colIndex) => {
  if (cell === true) {
    return <span>Archived</span>;
  } else {
    return <span>Active</span>;
  }
};

const risksColumns = [
  {
    dataField: "_id",
    text: "",
    hidden: true,
  },
  {
    dataField: "riskName",
    text: "Risk",
    filter: textFilter(),
    sort: true,
    headerStyle: (column, colIndex) => {
      return { width: "31%" };
    },
  },
  {
    dataField: "riskCategoryBroad.value",
    text: "Broad Risk Category",
    filter: textFilter(),
    sort: true,
    headerStyle: (column, colIndex) => {
      return { textAlign: "center" };
    },
    align: "center",
  },
  {
    dataField: "riskCategoryNarrow.value",
    text: "Narrow Risk Category",
    filter: textFilter(),
    sort: true,
    headerStyle: (column, colIndex) => {
      return { textAlign: "center" };
    },
    align: "center",
  },
  {
    dataField: "riskOfficerPrimary.value",
    text: "Responsible Officer (Primary)",
    filter: textFilter(),
    sort: true,
    headerStyle: (column, colIndex) => {
      return { width: "20%", textAlign: "center" };
    },
    align: "center",
  },
  {
    dataField: "grossRiskAssessment.grossRiskRating.label",
    text: "Gross Risk Rating",
    filter: textFilter(),
    sort: true,
    headerStyle: (column, colIndex) => {
      return { textAlign: "center" };
    },
    align: "center",
  },
  {
    dataField: "netRiskAssessment.netRiskRating.label",
    text: "Net Risk Rating",
    filter: textFilter(),
    sort: true,
    headerStyle: (column, colIndex) => {
      return { textAlign: "center" };
    },
    align: "center",
  },
  {
    dataField: "archived",
    text: "Archived",
    sort: true,
    filter: selectFilter({ options: selectOptions }),
    headerStyle: (column, colIndex) => {
      return { textAlign: "center", width: "8%" };
    },
    formatter: archiveFormatter,
    align: "center",
  },
];

export default inject("riskStore")(observer(Register));

Solution

  • The function rpotectedRoute return different types between wether you are login.

    1. When authStore.loggedIn is true, it returns a React Component, it's something like (props) => <jsx/>
    2. when auth.loggedIn is false, it returns a React Element, it's something like <jsx/>

    So in this case you should do

    import React from "react";
    import authStore from "../../store/authStore";
    import { Redirect } from "react-router-dom";
    
    const protectedRoute = (RouteComponent) => {
      if (authStore.loggedIn) {
        return RouteComponent;
      }
      return () => <Redirect to="/login" />;
    };
    
    export default protectedRoute;
    

    If you are new to react, I recommand you to read React components, elements and instances