socket.io broadcasting not working with React - node.js

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

Related

Conditional rendering is not working in react

After getting data from the backend, I can't get to display is on the homepage
Everthing is okay like server, database but conditional rendering is not working
Homepage UI with error
`
import React, {useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getAllPizzas } from "../actions/pizzaActions";
import Error from "../components/Error";
import Loading from "../components/Loading";
import Pizza from "../components/Pizza";
const Homescreen = () => {
const dispatch = useDispatch();
const pizzasstate = useSelector((state) => state.getAllPizzasReducer);
const { pizzas, error, loading } = pizzasstate;
useEffect(() => {
dispatch(getAllPizzas());
}, [dispatch]);
return (
<div>
<div className="row">
{loading ? (
<Loading/>
) : error ? (
<Error error='Something went wrong'/>
) : (
pizzas.map((pizza) => {
return (
<div className="col-md-3 m-3" key={pizza._id}>
<div>
<Pizza pizza={pizza} />
</div>
</div>
);
})
)}
</div>
</div>
);
};
export default Homescreen;
Thank you in advance

How to forward page from react js frontend to node js backend using proxy

I created a simple react app that has two pages ("/", "/show-stocks") using Routes to switch between the two. I created a node/express js backend that has an end point called "/show-stocks/btc", I setted the proxy in the package.json to "proxy": "http://localhost:4000/", Now I want to be able to forward to the backend the page "/show-stocks/btc" (the preset end point).
I tried this solution but it didn't worked. It does take the user to /show-stocks/btc but in the localhost:3000 and sends an error that says that this path does not exsist.
I saw on the internet that if the page does not exists its automatically checks if the proxy has it.
(React listening on port 3000, Node listening on port 4000)
Does someone know how can I implement this?
App.js
import React from "react";
import Navbar from "./components/Navbar";
import "./App.css";
import Home from "./components/pages/Home";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import ShowStocks from "./components/pages/ShowStocks";
function App() {
return (
<>
<Router>
<Navbar />
<Routes>
<Route path="/" exact element={<Home />} />
<Route path="/show-stocks" element={<ShowStocks />} />
</Routes>
</Router>
</>
);
}
export default App;
ShowStocks.js:
(incase user goes to "localhost:3000/show-stocks")
import React from "react";
import "../../App.css";
import GetStocks from "../../GetStocks";
export default function ShowStocks() {
return (
<>
<GetStocks />
</>
);
}
GetStocks:
import React, { Component } from "react";
import axios from "react-axios";
import { Link } from "react-router-dom";
export default class GetStocks extends Component {
constructor() {
super();
this.state = {
BTCprice: "Noy Yet Gotten",
};
}
getBTC = () => {
// here I am trying to get response from the proxy /show-stocks/btc end point.
axios.get("/btc").then((response) => {
console.log(response.data.price);
this.setState({
BTCprice: response.data.price,
});
});
};
render() {
return (
<div>
<h1>BTC price: {this.state.BTCprice}</h1>
<Link to="/show-stocks/btc" className="btn-mobile">
<button className={`btn--test`} onClick={this.getBTC}>
Get price
</button>
</Link>
</div>
);
}
}
I fixed it
the problem where at GetStocks.js
this is the new code
import React, { Component } from "react";
import axios from "axios";
export default class GetStocks extends Component {
constructor() {
super();
this.state = {
BTCprice: "Noy Yet Gotten",
};
}
getBTC = () => {
console.log("jey");
axios.get("/show-stocks/btc").then((response) => {
console.log(response.data.price);
this.setState({
BTCprice: response.data.price,
});
});
};
render() {
return (
<div>
<h1>BTC price: {this.state.BTCprice}</h1>
<button onClick={this.getBTC}>Get price</button>
</div>
);
}
}

Browser Reload: Does not get back to selected page, goes to default homepage - ReactJs

I am new in using ReactJS and learning it bit by bit. I have 3 pages: homepage, contacts and moviesDetails. When I travel through contacts or moviesDetails and hit browser's reload, it gets me back to homepage which I do not want. I want it to stay on the same page which I am in.
If I am in contacts page, and hit browser's reload, I want it to stay on contacts page. I do not want it to go to homepage.
I don't know how to store the opened page's path in localStorage. I need help here as I cannot figure out where I am going wrong.
Following is App.js code.
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Homepage from "./homepage/Homepage";
import Contacts from "./contacts/Contacts";
import PrivateRoute from "./private-route/PrivateRoute";
import MoviesDetails from "./MoviesDetails/MoviesDetails";
class App extends Component {
render() {
return (
<Router>
<div className="App">
<Switch>
<PrivateRoute exact path="/homepage" component={Homepage} />
<PrivateRoute exact path="/contacts" component={Contacts} />
<PrivateRoute exact path="/moviesDetails" component={MoviesDetails} />
</Switch>
</div>
</Router>
);
}
}
export default App;
Following is contacts.js code: (All respective components are being imported)
import React, { Component } from "react";
import { connect } from "react-redux";
import Container from '#material-ui/core/Container';
import { changeHeaderName} from '../../actions/homepageActions';
class contacts extends Component {
constructor() {
super();
this.state = {
data:"",
value: 0,
date: "",
errorList: {}
};
}
componentDidMount() {
this.props.header("Contacts");
}
render() {
const { classes } = this.props;
return (
<Container>
<TabPanel value={this.state.value}>
<Grid container
justify="flex-start"
alignItems="center"
>
<Grid xs={6}>
<Typography>
(Names here)
</Typography>
</Grid>
</Grid>
<Grid xs={3}>
<Typography>
Contacts
</Typography>
</Grid>
<Grid xs={5}>
<Typography>
(All the contacts are listed here)
</Typography>
</Grid>
</TabPanel>
</Container>
);
}
}
const mapStateToProps = state => {
return {mainUser: state.auth.mainUser}
};
export default connect(mapStateToProps, {header})(configurations);
Following is store.js code:
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = {};
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk),
(b&&a()) ||compose)
);
export default store;
And store exported above is been used in index.js file.
Given the above code, I do not want my loaded page go back to the homepage. I want to stay on the same page. Browser reload gets me back to "/homepage" instead of "/contacts". Browser reload gets me back to "/homepage" instead of "/moviesDetails".
I am not using any hooks here. So I don't want my code to be in hooks. Just a simple react.js code.
EDIT NO: 1
Following is my PrivateRoute.js code
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
const PrivateRoute = ({component: Component, authentic, ...rest}) => (
<Route
{...rest}
render={ props =>
authentic.isAuthenticated === true ? (
<div>
<Component {...props} />
</div>
) : (
<Redirect to="/" />
)
}
/>
);
PrivateRoute.propTypes = {
authentic: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
authentic: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
EDIT NO: 2
Following is redux store provider : (this is in index.js file)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './components/App';
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
EDIT NO: 3
Following is MoviesDetails component
import React, { Component } from "react";
import Container from '#material-ui/core/Container';
import { connect } from "react-redux";
import Table from '#material-ui/core/Table';
import TableCell from '#material-ui/core/TableCell';
import TableHead from '#material-ui/core/TableHead';
import TableRow from '#material-ui/core/TableRow';
import TableBody from '#material-ui/core/TableBody';
import TableContainer from '#material-ui/core/TableContainer';
import { moviesList } from "./actions/moviesActions";
class MoviesDetails extends Component {
constructor() {
super();
this.state = {
skip: 0,
limit: 10,
pageNumber: 0,
value: '',
nameMovie:"",
genre:"",
ratings:"",
numberOfSongs:"",
releaseDate:"",
};
}
componentDidMount() {this.fetchRecords();}
fetchRecords=async()=>{
let payload ={
nameMovie:this.state.nameMovie,
genre:this.state.genre,
ratings:this.state.ratings,
numberOfSongs :this.state.numberOfSongs ,
releaseDate : this.state.releaseDate,
skip : this.state.limit * this.state.pageNumber,
limit: this.state.limit,
}
await this.props.moviesList(payload);
}
render() {
const { classes } = this.props;
return (
<div>
<div />
<Container >
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Movie Name</TableCell>
<TableCell>Genre</TableCell>
<TableCell>Song Count</TableCell>
<TableCell>Ratings</TableCell>
<TableCell>Release Date</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.props.movies.moviesList.map((movie, index) => {
return (
<TableRow >
<TableCell>
{nameMovie}
</TableCell>
<TableCell>{genre}</TableCell>
<TableCell>{numberOfSongs}</TableCell>
<TableCell>{ratings}</TableCell>
<TableCell>{releaseDate}</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</TableContainer>
</Container>
</div>
);
}
}
const mapStateToProps = state => {
return {
movie: state.movie,
adim: state.auth.admin,
}
};
export default connect(mapStateToProps, {moviesList })(MoviesDetails);
I handle my moviesDetails tab like this:
handleMovies = (e) => { e.preventDefault();
this.props.history.push("/moviesDetails"); }
You should persist your redux state to local storage when it updates, and initialize your store from local storage when app loads.
Minimal Redux Store Persistence Example:
Create a "middle" component to handle persisting state updates to localStorage.
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux];
const StorePersister = ({ children }) => {
const state = useSelector(state => state);
useEffect(() => {
localStorage.setItem('myState', JSON.stringify(state));
}, [state]);
return children;
};
index - wrap the App component with the store persister.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './components/App';
import StorePersister from './components/StorePersister';
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<StorePersister>
<App />
</StorePersister>
</Provider>,
document.getElementById('root')
);
Initialize state from local storage. If there is no "myState" key or the parsing returns null then the empty object ({}) will be used as a fallback.
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = JSON.parse(localStorage.getItem('myState')) || {};
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk),
(b&&a()) || compose
)
);
export default store;
There is also a redux-persist package out there that offers a bit of customization for what gets persisted to and initialized from the persistence.

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