My react app (login form) doesn't work like a spa page - node.js

I used CRA to create simple Login form. I've set up database with mongoose and built crud with node.
I don't think it has anything to do with the backend.
My intention with this little boiler plate was:
(not logged in) -> landing page shows 'Welcome' with Home, sign in menu.
(logged in) -> landing page shows 'Welcome, name' with Home, MyPage menu.
Down below is Login.js.
import React, { useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import "../App.css";
function Login() {
const [Email, setEmail] = useState("");
const [Password, setPassword] = useState("");
const [Error, setError] = useState("");
const onEmailHandler = (e) => {
setEmail(e.currentTarget.value);
};
const onPasswordHandler = (e) => {
setPassword(e.currentTarget.value);
};
const onSubmitHandler = (e) => {
e.preventDefault();
const body = {
email: Email,
password: Password,
};
axios
.post("/api/users/login", body)
.then((response) => {
if (!response.data.token) {
setError(response.data.error);
} else {
window.location.replace("/");
//props.history.push("/");
}
})
.catch((e) => {
console.log(e);
});
};
return (
<div>
<div>
<form className="login_form">
<input
type="email"
placeholder="Email"
onChange={onEmailHandler}
value={Email}
/>
<br />
<input
type="password"
placeholder="Password"
onChange={onPasswordHandler}
value={Password}
/>
<br />
<button onClick={onSubmitHandler}>Login</button>
</form>
</div>
<div
style={{
marginTop: 14,
fontSize: 15,
color: "red",
fontFamily: "Arial",
fontWeight: "lighter",
}}
>
{Error}
</div>
<div className="register_button">
<Link to="/register">Sign Up</Link>
</div>
</div>
);
}
export default Login;
As you can see, when you are signed in properly you are thrown to the landing page.
landing page looks like this.
import React, { useState } from "react";
import "../App.css";
import axios from "axios";
function Landing() {
const [Nickname, setNickname] = useState("");
axios.get("/api/users/authenticate").then((response) => {
if (response.data.name) {
setNickname(response.data.name);
}
});
return Nickname === "" ? (
<div className="welcome_msg">
<h4>Welcome!</h4>
</div>
) : (
<div className="welcome_msg">
<h4>Welcome, {Nickname}!</h4>
</div>
);
}
export default Landing;
And most importantly, App.js looks like down below.
import React, { useEffect, useState } from "react";
import { Route, BrowserRouter, Link } from "react-router-dom";
import "./App.css";
import axios from "axios";
import Landing from "./components/Landing";
import Login from "./components/Login";
import MyPage from "./components/MyPage";
import Register from "./components/Register";
function App() {
const [IsLoggedIn, setIsLoggedIn] = useState(false);
axios.get("/api/users/authenticate").then(
(response) => {
if (response.data.email) {
setIsLoggedIn(true);
} else {
setIsLoggedIn(false);
}
console.log(IsLoggedIn);
}
//[IsLoggedIn]
);
return IsLoggedIn ? (
<BrowserRouter>
<nav className="navigate">
<Link to="/">Home</Link>
<Link to="/mypage">Mypage</Link>
<hr />
</nav>
<Route exact path="/" component={Landing} />
<Route path="/login" component={Login} />
<Route path="/mypage" component={MyPage} />
<Route path="/register" component={Register} />
</BrowserRouter>
) : (
<BrowserRouter>
<nav className="navigate">
<Link to="/">Home</Link>
<Link to="/login">Sign in</Link>
<hr />
</nav>
<Route exact path="/" component={Landing} />
<Route path="/login" component={Login} />
<Route path="/mypage" component={MyPage} />
<Route path="/register" component={Register} />
</BrowserRouter>
);
}
export default App;
The Router api/user/authenticate returns json with user information(email, name, token).
It's not like there's an error to the app, but I think maybe it's re-rendered too many times? It's slow and doesn't work like a spa page. I've checked the network tab and there seems to be too many requests (mostly authentication) whenever i go to Home or Mypage.
Stay safe, stay away from virus and please help :(

That's because the submit handler must be passed to the form itself as an onSubmit method instead of the onClick of the button.
<form className="login_form" onSubmit={onSubmitHandler}>
...
</form>

Related

After adding router in App.js in react nothing is displayed in the webpage

After using the BrowserRouter as a wrapper function nothing is displayed on the webpage.
App.js
import React, { Component } from 'react';
import './App.css';
import Homepage from './components/homepage/homepage';
import Login from './components/login/login';
import Register from './components/register/register';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
// <div className="App">
<Router>
<div className="App">
<ul >
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/login">Login</Link>
</li>
<li>
<Link to="/register">Register</Link>
</li>
</ul>
<Routes>
<Route exact path='/' element={< Homepage />}></Route>
<Route exact path='/login' element={< Login />}></Route>
<Route exact path='/register' element={< Register />}></Route>
</Routes>
</div>
</Router>
// </div>
);
}
export default App;
homepage.js
import React from "react"
import "./homepg.css"
const Homepage = () => {
return (
<div className="homepage">
<h1>Hello Homepage</h1>
<div className="button">Logout</div>
</div>
)
}
export default Homepage
login.js
import React, {useState} from "react"
import "./login.css"
import axios from "axios"
const Login = () => {
const [ user, setUser ] = useState({
email : "",
password : ""
})
const handleChange = e => {
const {name , value} = e.target
setUser({
...user,
[name] : value
})
}
const login = () => {
axios.post("http://localhost:9002/login",user)
.then(res => alert(res.data.message))
}
return (
<div className="login">
{console.log("User",user)}
<h1>Login</h1>
<input type="text" name="email" value={user.email} placeholder="Enter email" onChange={handleChange}></input>
<input type="password" name="password" value={user.password} placeholder="Enter password" onChange={handleChange}></input>
<div className="button" onClick={login}>Login</div>
<div>or</div>
<div className="button">Register</div>
</div>
)
}
export default Login
register.js
import React, {useState} from "react"
import "./register.css"
import axios from "axios"
const Register = () => {
const [ user, setUser ] = useState({
name: "",
email : "",
password : "",
reEnterPassword : ""
})
const handleChange = e => {
const {name , value} = e.target
setUser({
...user,
[name] : value
})
}
const register = () =>{
const {name,email,password, reEnterPassword} = user
if(name && email && password && (password === reEnterPassword)){
axios.post("http://localhost:9002/register", user)
// console.log("yo")
.then(res => console.log(res))
}
else{
alert("Invalid input")
}
}
return (
<div className="register">
{console.log("User",user)}
<h1>Register</h1>
<input type="text" name="name" value={user.name} placeholder="Your Name" onChange={handleChange}></input>
<input type="text" name="email" value={user.email} placeholder="Your Email" onChange={handleChange}></input>
<input type="password" name="password" value={user.password} placeholder="Your password" onChange={handleChange}></input>
<input type="password" name="reEnterPassword" value={user.reEnterPassword} placeholder="Re-enter password" onChange={handleChange}></input>
<div className="button" onClick={register}>Register</div>
<div>or</div>
<div className="button">Login</div>
</div>
)
}
export default Register
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />,
);
If only <Homepage />, <Login/>, and <Register /> are present then homepage, login, and register page gets displayed.
But once I use the Router from react-router-dom then the webpage is blank, nothing is displayed.
There are several reasons why your components are not being displayed when using the BrowserRouter component from the react-router-dom library. Some of the most common reasons include:
1. Incorrect Router Import: Make sure you have imported the BrowserRouter component correctly from the react-router-dom library. The correct import statement is: import { BrowserRouter } from 'react-router-dom'.
2. Wrapping the Wrong Components: Make sure that you are wrapping the correct components inside the BrowserRouter. The BrowserRouter should wrap all the components that need access to the routing functionality.
3. Incorrect Route Configuration: Make sure you have correctly set up your routes using the Route component. The Route component should be used to define the mapping between a path and a component.
4. Components not Exporting Correctly: Make sure that your components are being exported correctly. Each component should be exported using export default ComponentName.
If you provide more information about your code and the error message that you're encountering, I'd be happy to help you further.

Using axios post response in the jsx react

I want to take the data response from the Axios post, and display it on the page:
import React, { useRef} from 'react';
import logo from './assets/img/lupa.png';
import { Form } from "#unform/web";
import Input from './components/forms/input';
import * as Yup from "yup";
import './App.css';
import axios from 'axios';
function App() {
const formRef = useRef(null);
async function handleSubmit(data, ){
try{
const schema = Yup.object().shape({
nn: Yup.number().min(8,"O campo eh obrigatorio e precisa ter 8 ou mais caracteres")
})
await schema.validate(data)
console.log(data)
}catch(err){
if(err instanceof Yup.ValidationError){
console.log(err)
}}
axios.post("http://localhost:8080/api", data).then(res => console.log(res.data))
.catch(err => console.log(err));
}
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>$ Search $</h2>
</div>
<Form ref={formRef} onSubmit={handleSubmit}>
<Input name="nn" type="number"/>
<button type='submit'>buscar</button>
</Form>
</div>
);
}
export default App;
But I don't know how to work with that res.data and how to display it on the page by the jsx react, I tried to use useState and set it in the axios.post("http://localhost:8080/api", data).then(res => setState(res.data))
.catch(err => console.log(err)); - but when I console.log someState it brings an object null, i tried to display on the page using
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>$ Search $</h2>
</div>
<Form ref={formRef} onSubmit={handleSubmit}>
<Input name="nn" type="number"/>
<button type='submit'>buscar</button>
</Form>
{
someState.length >=1 ? someState.map((some, idx) =>{
return <p key={idx}>{some.data}</p>
})
: ""
}
</div>
);
}
but nothing were display! ( If you have some suggestion to change of the overall code, you can answer too ), How can I fix this 2 problems ? I want to learn moreThe first object Im printing my input, to check if it are working, and the second object its what I recieved from the axios post response(.then(res => console.log(res.data), I want to display this object "resultado"
Object { nn: "00000000353" }
Object { ip: "200.1******", resultado: 961 }
​
ip: "200.1*****"
​
resultado: 961
​
<prototype>: Object { … }
See this nice post by digitalOcean How to use axios in ReactJs
https://www.digitalocean.com/community/tutorials/react-axios-react
Hope you got a lot of help from this post.

socket.io broadcasting not working with React

I am currently trying to build a connection between a Node.js application in the backend and a React application in the frontend. The connection from the frontend to the backend seems to work without any problems. Unfortunately, the React application, on the other side, cannot accept any data.
The socket.on(...) function throws an error:
dashboard.js:20 Uncaught TypeError: Cannot read properties of null (reading 'on')
I can not explain where the error lies.
app.js (mounting point of the React app):
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import io from 'socket.io-client';
import Dashboard from "./compontents/views/dashboard/dashboard";
function App() {
const [socket, setSocket] = useState(null);
useEffect(() => {
const newSocket = io(`http://${window.location.hostname}:8040`);
setSocket(newSocket);
return () => newSocket.close();
}, [setSocket]);
return (
<Router>
<div className="app">
<div className="app__view">
<Switch>
<Route exact path="/">
<Dashboard socket={socket} />
</Route>
</Switch>
</div>
</div>
</Router>
);
}
export default App;
dashboard.js (child component):
import React, { useEffect, useState } from 'react';
import { Link } from "react-router-dom";
import FeatherIcon from 'feather-icons-react';
import LargeButton from "../../buttons/largeButton/largeButton";
function Dashboard({ socket }) {
function toggleLight(type) {
if(type) {
// this function works fine
socket.emit("toggle light", type);
console.log(type);
}
}
useEffect(() => {
// this line is causing the error
socket.on('toggle button', (type) => {
console.log(type);
});
}, [socket]);
return(
<div className="view">
<div className="all">
<LargeButton icon="sun" text="Alles einschalten" event={toggleLight} />
<LargeButton icon="moon" text="Alles ausschalten" event={toggleLight} />
</div>
</div>
)
}
export default Dashboard;
It seems like your <Dashboard/> component are mounting before the socket instance are ready to go. Socket connection is an a async procedure so you must take this on mind when you use it.
Try change your app.js to this:
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import io from 'socket.io-client';
import Dashboard from './compontents/views/dashboard/dashboard';
function App() {
const [socket, setSocket] = useState(null);
useEffect(() => {
const newSocket = io(`http://${window.location.hostname}:8040`);
setSocket(newSocket);
return () => newSocket.close();
}, [setSocket]);
if (!socket) {
// catch and show some loading screen
// while the socket connection gets ready
return <div>Loading...</div>;
}
return (
<Router>
<div className="app">
<div className="app__view">
<Switch>
<Route exact path="/">
<Dashboard socket={socket} />
</Route>
</Switch>
</div>
</div>
</Router>
);
}

let variable reassigned but stay undefined (socket.io, node.js, react)

I am implementing a real time chat app using react and node but struggling with sending messages to the server. Below is my Chat component where socket connection with the server starts.
The problem is when I hit the send button, sendMsg runs and gives me an error TypeError: Cannot read property 'emit' of undefined. socket is assigned the return values of socketIOClient(ENDPOINT)inside useEffect but seems to stay undefined at the time of hitting the send button. I guess
Can someone please help me fix this?
import React, { useState, useEffect } from 'react';
import socketIOClient from "socket.io-client";
import Button from '#material-ui/core/Button';
import Box from '#material-ui/core/Box';
import Loading from './Loading';
import TextField from '#material-ui/core/TextField';
import { Container, Typography } from '#material-ui/core';
import { Fragment } from 'react';
import queryString from 'query-string';
import { Redirect } from 'react-router-dom';
const ENDPOINT = "http://localhost:4001";
const Chat = ({location}) =>{
const [msgList, setMsgList] = useState([]);
const [name, setName] = useState(null);
let socket;
useEffect(()=>{
const {name} = queryString.parse(location.search);
setName(name)
socket = socketIOClient(ENDPOINT);
socket.emit("setName", name)
if(socket){
socket.on("greeting", (data)=>{
setMsgList(msgList => [...msgList, {type: "server", msg:data}]);
console.log(msgList)
})
}
return()=>{
if(socket){
socket.disconnect();
}
}
},[location.search, name])
const sendMsg = (event)=>{
event.preventDefault();
console.log(socket)
socket.emit("message", {msg: event.target.message.value})
}
return (
<Fragment>
<Container >
<Box maxWidth="600px" height="100vh" marginX="auto" marginY="0" textAlign="center" position="relative">
<Box paddingY="15px">
<Typography variant="h4">Let's Chat!</ Typography>
</Box>
<Box>
{msgList.map((msgObj, index)=>{
return <p key={index + 1}>{msgObj.type}: {msgObj.msg}</p>
})}
</Box>
<Box position="absolute" bottom="0" left="0"width="100%" zIndex="10">
<form autoComplete="off" onSubmit={sendMsg}>
<TextField name="message" label="say something" variant="outlined" fullWidth />
<Button type="submit" color="secondary" fullWidth variant="contained">Send</Button>
</form>
</Box>
</Box>
</Container>
</Fragment>
);
}
export default Chat;

How to effectively protect routes in combination with react-router and passport on the backend

I have React and Node.js with passport.js on the backend which implements my app auth. My react makes a call to my backend and fetches the authorized user via action reducer. Everything works fine but there is a problem with the route guards. This is how I am protecting the routes if the user is not logged in
if(!this.props.auth) return
The problem is when the user is logged in, if page is refreshed, the code above executes faster than mapStateToProps returns the authorized user and the loggedIn user is redirected to the index page. This is bad user experience. Please help me how to resolve this issue and I would appreciate help and advice.
I think what I need to do is to ensure that store is updated first before DOM is rendered but I am not sure how to do it.
Here is dashboard
class Dashboard extends Component {
render() {
if(!this.props.auth) return <Redirect to='/' />
if (!this.props.auth.googleUsername) {
return <div className='container'> Loading ... </div>;
} else {
return (
<div className='container' style={{ margin: '10px 10px' }}>
{this.props.auth.googleUsername}
</div>
);
}
function mapStateToProps({auth}) {
return {auth};
}
export default connect(mapStateToProps)(Dashboard);
Here is App.js
import { connect } from 'react-redux';
import { fetchUser } from './store/actions/index';
import Home from './components/layout/Home';
import Dashboard from './components/layout/Dashboard';
class App extends Component {
componentDidMount() {
this.props.fetchUser();
}
render() {
return (
<div>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path='/' component={Home} />
<Route path='/dashboard' component={Dashboard} />
</Switch>
</div>
</BrowserRouter>
</div>
);
}
}
export default connect(null,{ fetchUser })(App)
Action reducer
import axios from 'axios';
import { FETCH_USER } from './types';
export const fetchUser = () => async dispatch => {
const res = await axios.get('/api/current_user');
dispatch({ type: FETCH_USER, payload: res.data });
};
Auth Reducer
import { FETCH_USER } from '../actions/types';
export default function(state = false, action) {
switch (action.type) {
case FETCH_USER:
return action.payload;
default:
return state;
}
}
For those who has this issue, I managed to solve the probable. The issue was that I need to persist redux store across my app. I used a third party library called 'redux-persist'
Here is the set I used in my index.js
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './store/reducers/rootReducer';
import thunk from 'redux-thunk';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { PersistGate } from 'redux-persist/integration/react';
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = createStore(persistedReducer, applyMiddleware(thunk));
const persistor = persistStore(store);
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>,
document.getElementById('root'));

Resources