Kristen
Kristen

Reputation: 453

React with Router v5 Error: Objects are not valid as a React child (found: object with keys {children})

I'm new to react but Im trying to create a web app that is essentially 2 apps in one. By 2 apps in one I mean I have 2 separate layouts, one when authorized and one when not. I'm running into a problem when logging in currently, when I try to redirect on successful log in I get the following error:

Error: Objects are not valid as a React child (found: object with keys {children}). If you meant to render a collection of children, use an array instead.

I'm sure this has something to do with how I have my routes set up or how Im redirecting.

Heres all my code. The error is usually pointing to the history.push line in login.js in the handlesubmit function. Note: my code is actually split into multiple js files for each function, I just combined them here so the code would be a bit more compact (I also combined the imports just for this example).

Update: I think I narrowed down my problem to my ProtectedRoute component, but I still dont totally know what the problem is. I think its how Im passing in an array of paths to that component but Im not sure how to fix it.

import React, { useState,useEffect } from "react";
import { NavLink, Route, Switch, useRouteMatch, useHistory, useLocation, useParams } from 'react-router-dom';
import MainLayout from "../layouts/MainLayout";
import AuthLayout from "../layouts/AuthLayout";
import NotFound from "../pages/NotFound";
import Login from "../pages/Login";
import Welcome from "../pages/Welcome";
import Dashboard from "../pages/Dashboard";
import Locations from "../pages/Locations";
import ProtectedRoute from "./ProtectedRoute";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";
import { LinkContainer } from "react-router-bootstrap";
import { ReactComponent as Logo } from '../images/Logo.svg';
import { useAppContext } from "../libs/contextLib";
import { LinkContainer } from "react-router-bootstrap";
import { useAppContext } from "../libs/contextLib";
import axios from 'axios';
import Form from "react-bootstrap/Form";
import LoaderButton from "../components/LoaderButton";
import LoginImage from '../images/Login-Page-Image.png';
import FloatLabelTextBox from "../components/FloatLabelTextBox.js"
import { API_BASE_URL, ACCESS_TOKEN_NAME } from '../constants/apiConstants.js';
import { onError } from "../libs/errorLib";

export default function MainRoutes() {
    return (
        <Switch>
            <Route path={['/login', '/welcome']}>
                <AuthLayout>
                    <Route path='/login' component={Login} />
                    <Route path='/welcome' component={Welcome} />
                </AuthLayout>
            </Route>            
            <ProtectedRoute exact path={['/', '/locations']}>
                <MainLayout>                    
                    <Route path='/locations' component={Locations} />
                    <Route exact path='/' component={Dashboard} />
                </MainLayout>
            </ProtectedRoute>
            {/* Finally, catch all unmatched routes */}
            <Route>
                <NotFound />
            </Route>
        </Switch>
    );
}
function AuthLayout({children}) {    
    const { isAuthenticated } = useAppContext();
    return (
        <>
            <div className="AuthLayout container py-3">
                <Navbar collapseOnSelect expand="md" className="mb-3 login-nav">
                    <LinkContainer to="/welcome">
                        <Navbar.Brand href="/welcome" className="font-weight-bold text-muted">
                            <Logo />
                        </Navbar.Brand>
                    </LinkContainer>
                    <Navbar.Toggle />
                    <Navbar.Collapse className="justify-content-end">
                        <Nav activeKey={window.location.pathname}>
                            <LinkContainer to="/welcome">
                                <Nav.Link>Home</Nav.Link>
                            </LinkContainer>
                            <LinkContainer to="/login">
                                <Nav.Link>Login</Nav.Link>
                            </LinkContainer>
                        </Nav>
                    </Navbar.Collapse>
                </Navbar>
                <div className="Auth-Layout-Body">
                    {children}
                </div>
            </div>
        </>
    );
}
export default AuthLayout;    

function MainLayout({ children }) {
  const { isAuthenticated } = useAppContext();
  const { userHasAuthenticated } = useAppContext();
  const history = useHistory();
  
  function handleLogout() {
    userHasAuthenticated(false);
    console.log("log out");
    history.push("/login");
  }
  return (
    <>
      <div className="MainLayout container py-3">
        <Navbar collapseOnSelect expand="md" className="mb-3 login-nav">
          <LinkContainer to="/">
            <Navbar.Brand href="/" className="font-weight-bold text-muted">
              Location INTEL
          </Navbar.Brand>
          </LinkContainer>
          <Navbar.Toggle />
          <Navbar.Collapse className="justify-content-end">
            <Nav activeKey={window.location.pathname}>
              <LinkContainer to="/">
                <Nav.Link>Home</Nav.Link>
              </LinkContainer>
              <LinkContainer to="/locations">
                <Nav.Link>Locations</Nav.Link>
              </LinkContainer>
              {isAuthenticated ? (
                <Nav.Link onClick={handleLogout}>Logout</Nav.Link>
              ) : (<div></div>)}
            </Nav>
          </Navbar.Collapse>
        </Navbar>
        <div className="Main-Layout-Body">
          {children}
        </div>
      </div>
    </>
  );
}
export default MainLayout;    

export default function Login() {
    const history = useHistory();
    const [state, setState] = useState({
        email: "",
        password: "",
    });
    const { userHasAuthenticated } = useAppContext();
    const [isLoading, setIsLoading] = useState(false);

    const handleChange = (e) => {
        setState({
            ...state,
            [e.target.name]: e.target.value,
        })
    }

    function validateForm() {
        return state.email.length > 0 && state.password.length > 0;
    }

    function handleSubmit(event) {
        event.preventDefault();

        setIsLoading(true);

        const payload = {
            "email": state.email,
            "password": state.password,
        }
        try {    
            axios.post('/api/user/login', payload, {
                headers: {
                    useCredentails: true,
                    'x-api-key': ACCESS_TOKEN_NAME,
                    "Access-Control-Allow-Origin": "*"
                }
            })
                .then(function (response) {
                    console.log(response);
                    //console.log('status code = ' + response.status);
                    if (response.status === 200) {
                        console.log("logged in");
                        userHasAuthenticated(true);
                        history.push("/");
                    } else {
                        console.log("not logged in");
                    }
                })
                .catch(function (error) {
                    console.log(error);
                });

        } catch (e) {
            onError(e);
            setIsLoading(false);
        }
    }

    return (
        <div className="Login-Container">
            <div className="Login-Container-Row">
                <div className="Login">
                    <p className="Login-Header">Login</p>
                    <div className="Login-Form">
                        <Form onSubmit={handleSubmit}>
                            <Form.Group size="lg" controlId="email">
                                <FloatLabelTextBox
                                    inputLabel="EMAIL"
                                    inputAutoFocus="autofocus"
                                    inputType="email"
                                    inputName="email"
                                    inputPlaceholder="Email"
                                    inputValue={state.email}
                                    handleChangeProps={handleChange}
                                />
                            </Form.Group>
                            <Form.Group size="lg" controlId="password">
                                <FloatLabelTextBox
                                    inputLabel="PASSWORD"
                                    inputAutoFocus=""
                                    inputType="password"
                                    inputName="password"
                                    inputPlaceholder="Password"
                                    inputValue={state.password}
                                    handleChangeProps={handleChange}
                                />
                            </Form.Group>
                            <LoaderButton
                                block
                                size="lg"
                                type="submit"
                                isLoading={isLoading}
                                disabled={!validateForm()}>
                                Login
                            </LoaderButton>
                            <p>Not a member? <NavLink to="/register">Get Started Here</NavLink></p>
                        </Form>
                    </div>
                </div>
                <div className="Login-Image">
                    <img src={LoginImage} />
                </div>
            </div>
        </div>
    );
}

export default function ProtectedRoute({ children, ...props }) {
    const { isAuthenticated } = useAppContext();
    return (
        <Route 
          {...props} 
          render={props => (
            isAuthenticated ?
              {children} :
              <Redirect to='/login' />
          )} 
        />
    );
}

Upvotes: 2

Views: 1480

Answers (1)

machineghost
machineghost

Reputation: 35790

Your problem is here:

    <Route 
      {...props} 
      render={props => (
        isAuthenticated ?
          {children} : // <=== HERE!
          <Redirect to='/login' />
      )}

You are using {children} as if you were in "JSX-land" ... but you're not. You're already inside a set of {} in your JSX ... which means you're in "Javascript-land".

In "Javascript-land", {children} means "make me an object with a single child property called children". When you then try to insert that object into your JSX, React doesn't know what to do with it, and you get your error.

You just want the same code, minus those curly braces:

    <Route 
      {...props} 
      render={props => (
        isAuthenticated ?
          children :
          <Redirect to='/login' />
      )}

P.S. Don't forget that route tags are just tags, so if you're doing this pattern a lot you might want to create an AuthenticatedRoute tag and use it (instead of repeating this ternary).

Upvotes: 2

Related Questions