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

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' />
)}
/>
);
}

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

Related

render in react router dom v6

I am following an example of how you render a function and pass properties, to get some data from the dom.
--- App.jsx ---
import { Routes, Route } from "react-router-dom";
// Components
import Listado from "./components/Listado";
import Detalle from "./components/Detalle";
function App() {
const addOrRemoveFromFavs = e => {
const btn = e.currentTarget; // console.log(btn);
const parent = btn.parentElement; // console.log(parent)
const imgURL = parent.querySelector('img').getAttribute('src');
const title = parent.querySelector('h5').innerText;
const overview = parent.querySelector('p').innerText;
const movieData = {
imgURL, title, overview
}
console.log(movieData);
}
return (
<>
<Header />
<div className="container mt-3">
<Routes>
<Route path="/" element={<Login />} />
<Route path="/listado" render={ (props) => <Lisstado addOrRemoveFromFavs={addOrRemoveFromFavs} {...props}/>} />
<Route path="/detalle" element={<Detalle />} />
</Routes>
</div>
<Footer />
</>
);
}
According to the documentation in react v6, the render or component is no longer used, so I don't know how to pass the information, here I show another fraction of code where I want to share the function and from here obtain data from the DOM. When making a console of the props following the example, you should get an object with information but it is not like that, besides it stops loading the information and is now blank.
-- listado.jsx ---
import { useEffect, useState } from "react";
import { Link, Navigate } from "react-router-dom";
import axios from "axios";
import swAlert from "#sweetalert/with-react";
const Listado = (props) => {
let token = sessionStorage.getItem('token');
console.log(props);
onst [ moviesList, setMoviesList ] = useState([]);
useEffect(() => {
const endpoint = 'https://api.themoviedb.org/3/discover/movie?api_key=8764e6417ae516fb86bc820ee865a6d8&language=es-US&page=1'
axios.get(endpoint)
.then(response => {
const apiData = response.data;
setMoviesList(apiData.results);
})
.catch(error => {
swAlert(<h2>Hubo errores, intenta más tarde</h2>)
})
}, [setMoviesList]);
return (
<>
{ !token && <Navigate to="/" /> }
<div className="row">
{
moviesList.map((oneMovie, index) => {
return (
<div className="col-3" key={index}>
<div className="card my-4">
<img src={ `https://image.tmdb.org/t/p/w500/${oneMovie.poster_path}` } className="card-img-top" alt="..." />
<button className="favourite-btn">
🖤
</button>
<div className="card-body">
<h5 className="card-title">{ oneMovie.title.substring(0, 20) }...</h5>
<p className="card-text">{ oneMovie.overview.substring(0, 100) }...</p>
<Link to={`/detalle?peliculitaID=${oneMovie.id}`} className="btn btn-primary">View Details</Link>
</div>
</div>
</div>
)
})
}
</div>
</>
)
}
export default Listado
I appreciate any suggestion
If you are just trying to render the Lisstado component on the "/listado" path and pass a addOrRemoveFromFavs prop, you do this just like any other time you are passing props to a component. Keep in mind the the Route components take a ReactNode, a.k.a. JSX, on the element prop, so passing props is just the same is it is anywhere else in React.
Example:
<Routes>
<Route path="/" element={<Login />} />
<Route
path="/listado"
element={<Lisstado addOrRemoveFromFavs={addOrRemoveFromFavs} />}
/>
<Route path="/detalle" element={<Detalle />} />
</Routes>
Additionally, you shouldn't be querying the DOM for the values in the passed callback, pass them to the callback directly in the child component.
Example:
App
const addOrRemoveFromFavs = movieData => e => {
console.log(movieData);
}
Listado
const Listado = ({ addOrRemoveFromFavs }) => {
const token = sessionStorage.getItem('token');
const [ moviesList, setMoviesList ] = useState([]);
useEffect(() => {
...
}, [setMoviesList]);
if (!token) {
return <Navigate to="/" />;
}
return (
<div className="row">
{moviesList.map((oneMovie, index) => {
// create movieData object
const movieData = {
imgURL: `https://image.tmdb.org/t/p/w500/${oneMovie.poster_path}`,
overview: oneMovie.overview,
title: oneMovie.title
};
return (
<div className="col-3" key={index}>
<div className="card my-4">
<img
src={ `https://image.tmdb.org/t/p/w500/${oneMovie.poster_path}`}
className="card-img-top"
alt="..."
/>
<button
className="favourite-btn"
onClick={addOrRemoveFromFavs(movieData)} // <-- pass to callback
>
🖤
</button>
<div className="card-body">
<h5 className="card-title">
{oneMovie.title.substring(0, 20) }...
</h5>
<p className="card-text">
{oneMovie.overview.substring(0, 100) }...
</p>
<Link
to={`/detalle?peliculitaID=${oneMovie.id}`}
className="btn btn-primary"
>
View Details
</Link>
</div>
</div>
</div>
)}
)}
</div>
)
};

How do I display Logut button once I login to the main UI?

I am trying to implement this feature in my React JS app where if I login successfully, the UI would display the Logout button in the navbar. But if I click on the Logout, I would be logged out from the main UI and redirected to the Login Page where the navbar won't have any Logout Button. Currently, when I log in to the main UI successfully, then I have to refresh the browser tab to show the logout button. It does not appear automatically once I log in successfully.
This is my header Component:
import React, { Component } from "react";
import { BrowserRouter as Router, Link } from "react-router-dom";
import AuthenticationService from "../Login/AuthenticationService";
class HeaderComponent extends Component {
render() {
const isUserLoggedIn = sessionStorage.getItem("authenticatedUser");
console.log(isUserLoggedIn);
return (
<nav className="navbar navbar-expand-lg ">
<Router>
<div className="container-fluid">
<a className="navbar-brand" href="#">
<span className="header-title">TODO APP</span>
</a>
<ul className="navbar-nav d-flex flex-row ms-auto me-3">
{isUserLoggedIn && (
<Link
to="/login"
onClick={() => {
window.location.href = "/login";
}}
>
{" "}
<button
className="btn btn-success logout-button"
onClick={AuthenticationService.logout}
>
Logout
</button>
</Link>
)}
</ul>
</div>
</Router>
{" "}
</nav>
);
}
}
export default HeaderComponent;
The isUserLoggedIn is a boolean value that is retrieved from the session storage. If isUserLoggedIn is true then the Logout button would be displayed on the Navbar.
const isUserLoggedIn = sessionStorage.getItem("authenticatedUser");
The AuthenticationService.js file is:
import React, { Component } from "react";
class AuthenticationService extends Component {
registerSuccessfulLogin(username, password) {
console.log("Login Successful");
sessionStorage.setItem("authenticatedUser", username);
console.log(sessionStorage.getItem("authenticatedUser"));
}
isUserLoggedIn() {
let user = sessionStorage.getItem("authenticatedUser");
if (user === null) {
return false;
} else {
return true;
}
// return true;
}
logout() {
sessionStorage.removeItem("authenticatedUser");
}
getLoggedInUserName() {
let user = sessionStorage.getItem("authenticatedUser");
if (user === null) return "";
return user;
}
}
export default new AuthenticationService();
The App.js component is:
import "./App.css";
import DropDownMenu from "./components/DropdownMenu/DropDownMenu";
import HeaderComponent from "./components/Header/HeaderComponent";
import LoginComponent from "./components/Login/LoginComponent";
import {
BrowserRouter as Router,
Routes,
Route,
useParams,
Link,
} from "react-router-dom";
import "./styles.css";
import "./loginStyle.css";
import "./Header.css";
import ErrorComponent from "./components/search-options/ErrorComponent";
import AuthenticatedRoute from "./components/DropdownMenu/AuthenticatedRoute";
function App() {
return (
<div className="App">
<HeaderComponent />
<Router>
<Routes>
<Route path="/" element={<LoginComponent />}></Route>
{/* <Route path="/login" element={<Navigate to="/LoginComponent" />} /> */}
<Route path="/login" default element={<LoginComponent />}></Route>
<Route
path="/pst"
element={
<AuthenticatedRoute>
<DropDownMenu />
</AuthenticatedRoute>
}
></Route>
<Route path="*" element={<ErrorComponent />}></Route>
</Routes>
</Router>
</div>
);
}
export default App;
I am not sure why do I have to refresh the Tab to show the Logout button on to the NavBar.
EDIT
I have added the Login component for further reference:
import axios from "axios";
import { data } from "jquery";
import React, { Component } from "react";
import { useNavigate } from "react-router";
import App from "../../App";
import HeaderComponent from "../Header/HeaderComponent";
import GetUserIp from "../tracking/GetUserIp";
import TrackLoginActivity from "../tracking/TrackLoginActivity";
import AuthenticationService from "./AuthenticationService";
class LoginComponent extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
ipAddress: "",
hasLoginFailed: false,
isUserLoggedIn: false,
};
this.handleCredentialChange = this.handleCredentialChange.bind(this);
this.submitLogin = this.submitLogin.bind(this);
}
handleCredentialChange(event) {
console.log(event.target.name);
this.setState({ [event.target.name]: event.target.value });
}
submitLogin(event) {
event.preventDefault();
var session_url =
"login check api url";
GetUserIp.retrieveIpAddress().then((response) => {
this.setState({ ipAddress: response.data.ip });
});
console.log(this.state.ipAddress);
axios
.post(
session_url,
{},
{
auth: {
username: this.state.username,
password: this.state.password,
},
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
}
)
.then((response) => {
console.log("Authenticated");
console.log(this.props);
AuthenticationService.registerSuccessfulLogin(
this.state.username,
this.state.password
);
this.props.navigate(`/pst`);
//track user login activity into the DB Entity, smart_tool_login_logs
TrackLoginActivity.trackSuccessfulLogin(
this.state.username,
this.state.ipAddress
);
})
.catch((error) => {
console.log("Error on autheication");
this.setState({ hasLoginFailed: true });
});
}
render() {
return (
<div className="login-app">
<form action="" className="login-form" onSubmit={this.submitLogin}>
<div className="container">
<input
type="text"
placeholder="username"
name="username"
className="username-value"
maxLength={30}
autoComplete="on"
value={this.state.username}
onChange={this.handleCredentialChange}
/>
<input
type="password"
placeholder="password"
name="password"
className="password-value"
maxLength={30}
autoComplete="on"
value={this.state.password}
onChange={this.handleCredentialChange}
/>
{this.state.hasLoginFailed && (
<div className="login-fail-message">
Invalid username/password
</div>
)}
<div className="login-btn"></div>
<button className="btn btn-success login-btn" type="submit">
Login
</button>
</div>
</form>
</div>
);
}
}
function WithNavigate(props) {
let navigate = useNavigate();
return <LoginComponent {...props} navigate={navigate} />;
}
export default WithNavigate;
Your header component is a stateless component, it has neither props nor internal state, so it is only rendered once with the initial value of isUserLoggedIn.
If you want the header component to react to the login status, it needs to be notified in some way that the value has changed. The easiest way to do so is to store this information in the state of your App component, then passing it down as a prop to the HeaderComponent.
// App.js
import "./App.css";
import DropDownMenu from "./components/DropdownMenu/DropDownMenu";
import HeaderComponent from "./components/Header/HeaderComponent";
import LoginComponent from "./components/Login/LoginComponent";
import {
BrowserRouter as Router,
Routes,
Route,
useParams,
Link,
} from "react-router-dom";
import "./styles.css";
import "./loginStyle.css";
import "./Header.css";
import ErrorComponent from "./components/search-options/ErrorComponent";
import AuthenticatedRoute from "./components/DropdownMenu/AuthenticatedRoute";
import AuthenticationService from "../Login/AuthenticationService";
function App() {
const [isUserLoggedIn, setIsUserLoggedIn] = useState(AuthenticationService.isUserLoggedIn()) // use the session storage to get the initial state's value
function onLoginSuccessful() {
setIsUserLoggedIn(true); // update the state to notify React the value has changed
}
return (
<div className="App">
<HeaderComponent isUserLoggedIn={isUserLoggedIn} /> {/* pass down the state value as a prop */}
<Router>
<Routes>
<Route path="/" element={<LoginComponent onLoginSuccessful={onLoginSuccessful}/>}></Route>
{/* <Route path="/login" element={<Navigate to="/LoginComponent" />} /> */}
<Route path="/login" default element={<LoginComponent onLoginSuccessful={onLoginSuccessful} />}></Route>
<Route
path="/pst"
element={
<AuthenticatedRoute>
<DropDownMenu />
</AuthenticatedRoute>
}
></Route>
<Route path="*" element={<ErrorComponent />}></Route>
</Routes>
</Router>
</div>
);
}
export default App;
// HeaderComponent.js
import React, { Component } from "react";
import { BrowserRouter as Router, Link } from "react-router-dom";
import AuthenticationService from "../Login/AuthenticationService";
class HeaderComponent extends Component {
render() {
const isUserLoggedIn = this.props.isUserLoggedIn; // retrieve the value from the props
console.log(isUserLoggedIn);
return (
<nav className="navbar navbar-expand-lg ">
<Router>
<div className="container-fluid">
<a className="navbar-brand" href="#">
<span className="header-title">TODO APP</span>
</a>
<ul className="navbar-nav d-flex flex-row ms-auto me-3">
{isUserLoggedIn && (
<Link
to="/login"
onClick={() => {
window.location.href = "/login";
}}
>
{" "}
<button
className="btn btn-success logout-button"
onClick={AuthenticationService.logout}
>
Logout
</button>
</Link>
)}
</ul>
</div>
</Router>
{" "}
</nav>
);
}
}
export default HeaderComponent;
Note that you also have to pass a callback to LoginComponent to be called once the user successfully logs in. Using state updates, React gets notified that the value of isUserLoggedIn has changed and will re-render the components that depend on this value, including HeaderComponent.
EDIT: I updated the implementation of LoginComponent with the callback I mentionned
// LoginComponent.js
import axios from "axios";
import { data } from "jquery";
import React, { Component } from "react";
import { useNavigate } from "react-router";
import App from "../../App";
import HeaderComponent from "../Header/HeaderComponent";
import GetUserIp from "../tracking/GetUserIp";
import TrackLoginActivity from "../tracking/TrackLoginActivity";
import AuthenticationService from "./AuthenticationService";
class LoginComponent extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
ipAddress: "",
hasLoginFailed: false,
isUserLoggedIn: false,
};
this.handleCredentialChange = this.handleCredentialChange.bind(this);
this.submitLogin = this.submitLogin.bind(this);
}
handleCredentialChange(event) {
console.log(event.target.name);
this.setState({ [event.target.name]: event.target.value });
}
submitLogin(event) {
event.preventDefault();
var session_url =
"login check api url";
GetUserIp.retrieveIpAddress().then((response) => {
this.setState({ ipAddress: response.data.ip });
});
console.log(this.state.ipAddress);
axios
.post(
session_url,
{},
{
auth: {
username: this.state.username,
password: this.state.password,
},
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
}
)
.then((response) => {
console.log("Authenticated");
console.log(this.props);
AuthenticationService.registerSuccessfulLogin(
this.state.username,
this.state.password
);
// add this line to your `LoginComponent` so that
// onLoginSuccessul is called when user is logged in
this.props.onLoginSuccessful();
this.props.navigate(`/pst`);
//track user login activity into the DB Entity, smart_tool_login_logs
TrackLoginActivity.trackSuccessfulLogin(
this.state.username,
this.state.ipAddress
);
})
.catch((error) => {
console.log("Error on autheication");
this.setState({ hasLoginFailed: true });
});
}
render() {
return (
<div className="login-app">
<form action="" className="login-form" onSubmit={this.submitLogin}>
<div className="container">
<input
type="text"
placeholder="username"
name="username"
className="username-value"
maxLength={30}
autoComplete="on"
value={this.state.username}
onChange={this.handleCredentialChange}
/>
<input
type="password"
placeholder="password"
name="password"
className="password-value"
maxLength={30}
autoComplete="on"
value={this.state.password}
onChange={this.handleCredentialChange}
/>
{this.state.hasLoginFailed && (
<div className="login-fail-message">
Invalid username/password
</div>
)}
<div className="login-btn"></div>
<button className="btn btn-success login-btn" type="submit">
Login
</button>
</div>
</form>
</div>
);
}
}
function WithNavigate(props) {
let navigate = useNavigate();
return <LoginComponent {...props} navigate={navigate} />;
}
export default WithNavigate;

Component is missing after rerendering

I want to create an SQL Editor. For that I need to display to which database I am currently connected to.
If I select some Database from the Dropdown Menu the Dropdown component will disappear completely.
Btw: I use the bootstrap library for Dropdown Menus
Here is what I got so far:
App.js
import Header from "./components/Header";
import Switch from "react-bootstrap/Switch";
import {Route, useParams} from "react-router";
import Navbar from "./components/Navbar";
import {useState} from "react";
import QueryPage from "./components/QueryPage";
import ChangeDB from "./components/essentials/ChangeDB";
function App() {
const [connections, setConnections] = useState([
{
id: 1,
dbname: "db1"
},
{
id: 2,
dbname: "db2"
},
{
id: 3,
dbname: "db3"
}]
);
const [db, setDB] = useState(1);
const onDBChange = ({id}) => {
setDB(() => id)
}
return (
<div className="App">
<Header database={db} connections={connections}/>
<Switch>
<Route exact path={["/", "/home","db/:id" ]}>
<QueryPage />
</Route>
<Route path="/db/:id"><ChangeDB callback={onDBChange} /></Route>
</Switch>
</div>
);
}
export default App;
Header.js
import {Button, Form, FormControl, Nav, Navbar, NavDropdown} from "react-bootstrap";
import "./Header.css";
import { Link } from 'react-router-dom';
function Header({ database , connections}) {
alert(database);
return (
<Navbar bg="light" expand="lg">
<Logo />
<Navbar.Brand href="#home">fnmSQL Client</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto">
<Nav.Link href="#home">Home</Nav.Link>
<Nav.Link href="#link">Link</Nav.Link>
{connections.map(con => {
if(con.id === database) {
return (
<NavDropdown title={con.dbname} id="basic-nav-dropdown">
{connections.length !== 0 ?
connections.filter(id => (database !== id)).map(con => (
<NavDropdown.Item className={"bootstrap-overrides"} href={"#db/" + con.id}>{con.dbname}</NavDropdown.Item>
))
:
<NavDropdown.Item disabled="true">No Database Connections</NavDropdown.Item>
}
</NavDropdown>
)
}
})}
</Nav>
<Form inline>
<FormControl type="text" placeholder="Search" className="mr-sm-2" />
<Button variant="outline-success">Search</Button>
</Form>
</Navbar.Collapse>
</Navbar>
);
}
const Logo = () => {
return (
<Link to="./">
<img src={LogoWhite} alt={"ADVA Optical Networking SE"} className={"img"}/>
</Link>
)
}
export default Header;
I havent found another solution to read from HashRouter than putting it in some extra Component:
ChangeDB.js
function ChangeDB({callback}) {
let id = useParams();
callback(id);
return null;
}
export default ChangeDB;
Found a solution. It was because of the strict equality operator.
It seems that either database or con.id is parsed as a string instead of an integer.
I don't know why maybe somebody got an explanation...
Cheers guys

TypeError: Cannot read property 'image' of null

I have connected my ProfileComponent.js with a redux store and then using that state
to render user image but when I refresh the page I get this TypeError and when I did a console check it seems that my component is using user state before it is available as props(it's basically null in Profile props) but that shouldn't happen right?
error
And when I omitted the <img /> element it seems that my Profile is being rendered twice for the first time my user is null and for second my user is updated in props. Can anyone explain to me what's happening?
console
ProfileComponent.js
import React, { Component } from 'react';
import { Container, Row, Col, Card, CardHeader, CardBody, CardFooter, Input, Form, Button } from 'reactstrap';
import {connect} from 'react-redux';
import { uploadImage, fetchUser } from '../redux/ActionCreators';
import { baseUrl } from '../config';
import { Switch, Route, withRouter, Redirect } from 'react-router-dom';
const mapStateToProps = (state) => {
return {
user: state.auth.user,
}
}
const mapDispatchToProps = (dispatch) => ({
uploadImage: (userId, imageData) => {dispatch(uploadImage(userId, imageData))},
fetchUser: (userId) => {dispatch(fetchUser(userId))}
})
class Profile extends Component {
constructor(props){
super(props);
this.state = {
selectedFile: null
}
this.handleUpload = this.handleUpload.bind(this);
this.onChangeHandler = this.onChangeHandler.bind(this);
}
handleUpload(event){
const data = new FormData()
data.append('image', this.state.selectedFile)
this.props.uploadImage(this.props.match.params.userId, data);
event.preventDefault();
}
onChangeHandler=event=>{
this.setState({selectedFile: event.target.files[0]});
}
componentDidMount() {
console.log('user',this.props.user);
}
render() {
//
return(
<Container className="form-container" >
<Row>
<Col md={{size:6}}>
<Col md={{size:6,offset:3}}>
<div style={{marginBottom:30}}>
<img className="profile-image" src= {`${baseUrl}${this.props.user.image}`}/>
<Form >
<Input type="file" name="image" id="file" onChange={this.onChangeHandler} />
<Button type="submit" color="primary" className="form-control" onClick = {this.handleUpload}>Upload</Button>
</Form>
</div>
</Col>
</Col>
<Col md={6}>
<div className="shadow-container" style={{marginTop:0}}>
<Card>
<CardHeader>
<h2>Profile details</h2>
</CardHeader>
<CardBody>
<h4 style={{color:'grey'}}>{this.props.match.params.userId}</h4>
<h5 style={{color:'grey'}}></h5>
<small><i></i></small>
</CardBody>
<CardFooter>
<Row>
<Col>
<a href="/editprofile"><span className="fa fa-pencil fa-2x ml-auto"
style={{color:'blue', display:'flex',
flexDirection:'row',justifyContent:'flex-end'}}></span></a>
</Col>
<Col>
<span className="fa fa-trash fa-2x" style={{color:'red'}}></span>
</Col>
</Row>
</CardFooter>
</Card>
</div>
</Col>
</Row>
</Container>
);
}
}
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Profile));
MainComponent.js
import React, {Component} from 'react';
import Menu from './MenuComponent';
import Home from './HomeComponent';
import Users from './UsersComponent';
import Register from './RegisterComponent';
import Signin from './SigninComponent';
import Profile from './ProfileComponent';
import Edit from './EditComponent';
import { signin, signout, fetchUser } from '../redux/ActionCreators';
import {connect} from 'react-redux';
import { Switch, Route, withRouter, Redirect } from 'react-router-dom';
import { CSSTransition, TransitionGroup } from 'react-transition-group'
const mapStateToProps = (state) => {
return {
auth: state.auth
}
}
const mapDispatchToProps = (dispatch) => ({
signin: (credentials) => {dispatch(signin(credentials))},
signout: () => {dispatch(signout())},
fetchUser: (userId) => {dispatch(fetchUser(userId))}
})
class Main extends Component {
componentDidMount() {
this.props.fetchUser(this.props.auth.userId);
console.log('main',this.props.auth);
}
render() {
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
this.props.auth.authenticated
? <Component {...props} />
: <Redirect to={{
pathname: '/signin',
state: { from: props.location }
}} authenticated={this.props.auth.authenticated} />
)} />
)
return (
<div>
<Menu authenticated={this.props.auth.authenticated} signout={this.props.signout} userId={this.props.auth.userId}/>
<TransitionGroup>
<CSSTransition key={this.props.location.key} classNames="page" timeout={300}>
<Switch>
<Route path="/home" component={Home} />
<PrivateRoute path="/users" component={Users} />
<Route path="/signup" component={Register} />
<Route path="/signin" component={() => <Signin signin={this.props.signin} authenticated={this.props.auth.authenticated}/>} />} />
<PrivateRoute path="/profile/:userId" component={() => <Profile />} />
<Route path="/editprofile" component={Edit} />
<Redirect to="/home" />
</Switch>
</CSSTransition>
</TransitionGroup>
</div>
);
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Main));
First, check if the value is available or not,
You can do something like this to solve the error :
{this.props.user && <img className="profile-image" src= {`${baseUrl}${this.props.user.image}`}/> }
Issue :
user props might be initialized from some API or after some process, so it's not available for the first render, but as soon as props changes ( your user initialized ), it forces to re-render, so you get the data in the second time.
In the error received, while receiving the data from the API with Fetch, it receives the data as null and cannot read it because it cannot receive the data. To prevent this, the data to be listed is checked with a condition (&&).
{user && <div style={{display : 'flex', flex-direction : 'row', justify-content :'center', width : '100%'}}>
<div style={{width :'60%'}}>
<img style={{width : '%90',}} src={user.images[1]} alt={user.title}/>
</div>
<div>
<h1 style={{fontSize :'48px' , font-weight : 'bold'}} >{user.title} </h1>
<h2 style={{fontSize :'36px'}}> {user.brand}</h2>
<h2 style={{fontSize :'36px'}}>Discount: {user.discountPercentage}</h2>
<h2 style={{fontSize :'36px'}}>Price: {user.price}</h2>
<h6 style={{fontSize :'24px'}}>Stock: {user.stock}</h6>
</div>
</div>}
In the form you should give the name="Image"
and the same name should be passed in router.post

Render nav after login

I am working on the react project. Currently, I am struggling to render the nav again after sign in to show the links that are only visible for logged in users. The following code works fine, and also the redirect goes to "/".
The main issue is that the links for logged-in users only visible after a reload of the page.
Nav.js
import React, {Component} from "react";
import {Nav, Navbar} from 'react-bootstrap';
import { Auth } from 'aws-amplify'
import SignIn from "./Authentication/SignIn";
import SignUp from "./Authentication/SignUp";
import SignOut from "./Authentication/SignOut";
import {
BrowserRouter as Router,
withRouter,
Redirect,
Switch,
Route,
Link
} from "react-router-dom";
import Share from "./Share";
import Subscribe from "./Subscribe";
import Home from "./Home"
class PrivateRoute extends React.Component {
constructor (props) {
super(props);
this.state = {
loaded: false,
isAuthenticated: false
}
}
componentDidMount() {
this.authenticate()
this.unlisten = this.props.history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log('user: ', user))
.catch(() => {
if (this.state.isAuthenticated) this.setState({ isAuthenticated: false })
})
});
}
componentWillUnmount() {
this.unlisten()
}
authenticate() {
Auth.currentAuthenticatedUser()
.then(() => {
this.setState({ loaded: true, isAuthenticated: true});
})
.catch(() => this.props.history.push('/signup'))
}
render() {
const { component: Component, ...rest } = this.props
const { loaded , isAuthenticated} = this.state
if (!loaded) return null
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/signup",
}}
/>
)
}}
/>
)
}
}
PrivateRoute = withRouter(PrivateRoute)
class Routes extends React.Component {
constructor (props) {
super(props);
this.state = {
showItemInMenu: false
}
}
componentDidMount() {
Auth.currentAuthenticatedUser()
.then(() => { this.setState({showItemInMenu: true })})
.catch(() => { this.setState({showItemInMenu: false})});
}
render() {
const showItemInMenu = this.state.showItemInMenu
return (
<Router>
<Navbar bg="dark" variant="dark">
<Navbar.Brand as={Link} to="/">Todo</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav"/>
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
<Nav.Link as={Link} to="/">Home</Nav.Link>
{showItemInMenu && <Nav.Link as={Link} to="/share" >Share</Nav.Link>}
{showItemInMenu && <Nav.Link as={Link} to="/subscribe" >Subscribe</Nav.Link> }
{showItemInMenu && <Nav.Link as={Link} to="/signout" >Logout</Nav.Link> }
<Nav.Link as={Link} to="/signup" >Registration</Nav.Link>
<Nav.Link as={Link} to="/signin" >Login</Nav.Link>
</Nav>
</Navbar.Collapse>
</Navbar>
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/signup' component={SignUp}/>
<Route path='/signin' component={SignIn}/>
<PrivateRoute path='/share' component={Share}/>
<PrivateRoute path='/subscribe' component={Subscribe}/>
<PrivateRoute path='/signout' component={SignOut}/>
</Switch>
</Router>
)
}
}
export default Routes
Signin.js
import React, { Component } from "react";
import { Form, Row, Col,Button, Alert,Container } from 'react-bootstrap';
import { Auth } from "aws-amplify";
import styled from 'styled-components';
import { Redirect } from 'react-router-dom'
class SignIn extends Component {
constructor (props) {
super(props);
this.state = {
username: '',
password: '',
message: '',
redirect: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSignIn = this.handleSignIn.bind(this);
}
handleChange (event) {
this.setState({ [event.target.name]: event.target.value });
}
handleSignIn (event){
event.preventDefault();
const username = this.state.username;
const password = this.state.password;
// You can pass an object which has the username, password and validationData which is sent to a PreAuthentication Lambda trigger
Auth.signIn({
username,
password
})
.then(user => console.log(user))
.then(() => { this.setState({ redirect: true })
})
.catch(err =>
this.setState({message: err.message})
);
};
renderRedirect = () => {
if (this.state.redirect) {
return <Redirect
to={{
pathname: "/",
state: { fromlogin: true }
}}
/>
}
}
render () {
const Title = styled.div`
margin-top: 10px;
margin-bottom: 15px;
`;
return (
<Container>
<Row>
<Col md={6}>
<Title>
<h3>Login Form</h3>
</Title>
<Form onSubmit={this.handleSignIn}>
<Form.Group>
<Form.Control type="text" name="username" placeholder="Username" onChange={this.handleChange} />
</Form.Group>
<Form.Group>
<Form.Control type="password" name="password" placeholder="Password" onChange={this.handleChange} />
</Form.Group>
<Form.Group>
<Button
variant="primary"
type="submit"
value="SIGN IN"
>LOGIN</Button>
</Form.Group>
</Form>
{this.state.message ?
<Alert variant="danger">{this.state.message}</Alert>
: <></>}
</Col>
</Row>
{this.renderRedirect()}
</Container>
);
}
}
export default SignIn
I was able to solve this using React JS context API and local storage for storing the required state globally so that it does not revert on refresh.
Scenario: A top navbar containing site title is to visible every time regardless of user being signed in or not. The username and a logout button to appear in the top navbar only for logged in users. A side menu bar visible only for signed in users.
Issue: The username only appears after a reload to the main page just like the links in the above question.
Solution:
Create a file to store user context
UserContext.js
let reducer = (user, newUser) => {
if (newUser === null) {
localStorage.removeItem("user");
return newUser;
}
return { ...user, ...newUser };
};
const userState = {
name: "",
email: "",
};
const localState = JSON.parse(localStorage.getItem("user"));
const AuthContext = createContext();
const AuthProvider = (props) =>{
const [user, setUser] = useReducer(reducer, localState || userState);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(user));
}, [user]);
return(
<AuthContext.Provider value={{ user, setUser }}>
{props.children}
</AuthContext.Provider>
)
}
export { AuthContext, AuthProvider };
Now in your App.js file wrap this AuthProvider over the components where user context is intended to be accessed.
App.js
function App() {
return (
<AuthProvider>
<Router>
<NavBar />
<Switch>
<Route path="/signin" exact component={SignIn} />
<Route path="/signup" component={SignUp} />
<ProtectedRoute path="/home" component={Home} />
</Switch>
</Router>
</AuthProvider>
);
}
Now consume the context in the components that were wrapped with the context like the Home and Navbar component.
NavBar.js
export default function MiniDrawer() {
const {user, setUser} = useContext(AuthContext)
//set user context and local/global storage respective value to null on signout
const handleSignOut = async () =>
{
try
{
await Auth.signOut()
localStorage.removeItem("user");
setUser(null)
history.push("/signin")
}catch(err)
{
console.log(err.message)
alert('Could not sign out due to: ', err.message)
}
}
return (
<div className={classes.root}>
<CssBaseline />
<AppBar
position="fixed"
className={clsx(classes.appBar, {
[classes.appBarShift]: open,
})}
>
<Toolbar>
{ user && user.name &&
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
className={clsx(classes.menuButton, {
[classes.hide]: open,
})}
>
<MenuIcon />
</IconButton>
}
<Typography variant="h6" noWrap>
The Video Site
</Typography>
{ user && user.name &&
<div className={classes.userArea}>
<Typography >
Welcome, {user && user.name}
</Typography>
<ListItem button onClick={handleSignOut}>
<ListItemIcon>{<ExitToAppIcon /> }</ListItemIcon>
<ListItemText primary="Log out" />
</ListItem>
</div>
}
</Toolbar>
</AppBar>
{ user && user.name &&
<Drawer
variant="permanent"
className={clsx(classes.drawer, {
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
})}
classes={{
paper: clsx({
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
}),
}}
>
<div className={classes.toolbar}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'rtl' ? <ChevronRightIcon /> : <ChevronLeftIcon />}
</IconButton>
</div>
<Divider />
<List>
{
SideBarData.map( (item) => {
return(
<Link to={item.path}>
<Tooltip title={item.title} aria-label="add">
<ListItem button key={item.title} >
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.title} />
</ListItem>
</Tooltip>
</Link>
)
}
)
}
</List>
<Divider />
</Drawer>
}
<main className={classes.content}>
<div className={classes.toolbar} />
{/* <MediaUpload /> */}
</main>
</div>
);
}
Now, you have implemented context with local storage in order to maintain state even after refreshing.
Guidance source

Resources