How to redirect to another view in React - node.js

Im developing an app where I need to login and after click tha Login button I need to redirect to another view that is in another layout, I've tried using this.props.history.push("/Inicio") to redirect when the Login is succesful. In this case this is the path.
{
path: "/Inicio",
name: "Inicio",
component: Inicio,
layout: "/admin"
},
This is the entire code
import React, { Component } from "react";
import {
Grid,
Col
} from "react-bootstrap";
import { Card } from "components/Card/Card.jsx";
import { FormInputs } from "components/FormInputs/FormInputs.jsx";
import Button from "components/CustomButton/CustomButton.jsx";
class Login extends Component {
constructor(props) {
super(props)
this.state = {
users: [],
user: '',
pass: '',
msg: '',
apiResponse:''
}
this.logChange = this.logChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this)
}
handleSubmit(event) {
event.preventDefault()
var data = {
user: this.state.user,
pass: this.state.pass,
msg: this.state.msg,
apiResponse: this.state.apiResponse
}
console.log(data)
fetch("http://localhost:9000/log/Login", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
}).then(function(response) {
if (response.status >= 400) {
throw new Error("Bad response from server");
}
return response.json();
}).then((data) => {
if(data == "success"){
console.log(data)
this.setState({users: data});
this.props.history.push("/Inicio");
window.location.reload();
}
else{
if(data == 'El usuario o la contraseña no coinciden'){
this.setState({ apiResponse: data })
}
}
}).catch(function(err) {
console.log(err)
});
}
logChange(e) {
this.setState({[e.target.name]: e.target.value});
}
render() {
return (
<div className="content">
<p class="col-md-4"></p>
<Grid >
<Col md={5}>
<Card
title="Login"
content={
<form method='POST' onSubmit= {this.handleSubmit}>
<p class="col-md-2"></p>
<FormInputs
ncols={["col-md-7"]}
properties={[
{
label: "Usuario",
type: "text",
bsClass: "form-control",
placeholder: "Usuario",
maxlength: 20 ,
name: "user",
onChange: this.logChange
}
]}
/>
<p class="col-md-2"></p>
<FormInputs
ncols={["col-md-7"]}
properties={[
{
label: "Contraseña",
type: "password",
bsClass: "form-control",
placeholder: "Contraseña",
maxlength: 20,
name: "pass",
onChange: this.logChange
}
]}
/>
<p >{this.state.apiResponse}</p>
<br/>
<br/>
<Button bsStyle="info" pullRight fill type="submit">
Login
</Button>
<Button bsStyle="info" pullLeft fill type="submit">
Olvide mi Contraseña
</Button>
</form>
}
/>
</Col>
</Grid>
</div>
);
}
}
export default Login;
but in the handleSubmit() everything is working fine except for this.props.history.push("/Inicio") because it doesn't do anything.
The index.js code
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "./assets/css/animate.min.css";
import "./assets/sass/light-bootstrap-dashboard-react.scss?v=1.3.0";
import "./assets/css/demo.css";
import "./assets/css/pe-icon-7-stroke.css";
import AdminLayout from "layouts/Admin.jsx";
import LoginLayout from "layouts/LoginLayout.jsx";
import EfoodLayout from "layouts/EFoodLayout.jsx";
ReactDOM.render(
<BrowserRouter>
<Switch>
<Route path="/admin" render={props => <AdminLayout {...props} />} />
<Route path="/login" render={props => <LoginLayout {...props} />} />
<Route path="/Efood" render={props => <EfoodLayout {...props} />} />
<Redirect from="/" to="/login/Login" />
</Switch>
</BrowserRouter>,
document.getElementById("root")
);
LoginLayout
import React, { Component } from "react";
import { Route, Switch } from "react-router-dom";
import LoginNavbar from "components/Navbars/LoginNavbar";
import Footer from "components/Footer/Footer";
import routes from "routes.js";
class Login extends Component {
getRoutes = routes => {
return routes.map((prop, key) => {
if (prop.layout === "/login") {
return (
<Route
path={prop.layout + prop.path}
render={props => (
<prop.component
{...props}
handleClick={this.handleNotificationClick}
/>
)}
key={key}
/>
);
} else {
return null;
}
});
};
getBrandText = path => {
return "Bienvenido a E Food";
};
render() {
return (
<div className="wrapper">
<LoginNavbar
brandText={this.getBrandText(this.props.location.pathname)}
/>
<Switch>{this.getRoutes(routes)}</Switch>
<Footer />
</div>
);
}
}
export default Login;
I hope you can help me, and thanks to everyone who answer.
PD: If you need more from my code, please let me know.

Ok, since it's obvious the component you want to do the navigation in is a deeply nested component there are a couple options.
Prop Drilling (not recommended)
prop drilling
The cons of prop drilling is that if the right props aren't passed to begin with, or if any component along the way forgets to pass on props, then some child component won't receive what they need and it may be difficult to track down the what or why it's missing. It's more difficult to maintain as now any changes to this component necessitate needing to be concerned about every other component around it. Prop drilling is actually a react anti-pattern.
LoginLayout
Your LoginLayout component appears to consume a routes config object and dynamically render routes. It needs to pass on the route props it received from its parent. Any component between here and the Login where you want to use history.push would need to keep passing all the props down.
class Login extends Component {
getRoutes = routes => {
return routes.map((prop, key) => {
if (prop.layout === "/login") {
return (
<Route
path={prop.layout + prop.path}
render={props => (
<prop.component
{...props}
{...this.props} // <-- Pass on all props passed to this component
handleClick={this.handleNotificationClick}
/>
)}
key={key}
/>
);
} else {
return null;
}
});
};
...
render() {
return (
...
);
}
}
Use withRouter Higher Order Component (recommended)
withRouter
You can get access to the history object’s properties and the closest
<Route>'s match via the withRouter higher-order component. withRouter
will pass updated match, location, and history props to the wrapped
component whenever it renders.
import React, { Component } from "react";
import { withRouter } from 'react-router-dom'; // <-- import HOC
import { Grid, Col } from "react-bootstrap";
import { Card } from "components/Card/Card.jsx";
import { FormInputs } from "components/FormInputs/FormInputs.jsx";
import Button from "components/CustomButton/CustomButton.jsx";
class Login extends Component {
constructor(props) {
...
}
handleSubmit(event) {
event.preventDefault();
var data = {
user: this.state.user,
pass: this.state.pass,
msg: this.state.msg,
apiResponse: this.state.apiResponse
};
console.log(data);
fetch("http://localhost:9000/log/Login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
})
.then(function(response) {
if (response.status >= 400) {
throw new Error("Bad response from server");
}
return response.json();
})
.then(data => {
if (data == "success") {
console.log(data);
this.setState({ users: data });
this.props.history.push("/Inicio"); // <-- history prop should now exist
window.location.reload();
} else {
if (data == "El usuario o la contraseña no coinciden") {
this.setState({ apiResponse: data });
}
}
})
.catch(function(err) {
console.log(err);
});
}
logChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
render() {
return (
<div className="content">
...
</div>
);
}
}
export default withRouter(Login); // <-- decorate Login with HOC

Are you using react-router-dom to handle your routing ? If so, you can use the hooks to navigate.
import { useHistory } from "react-router-dom";
...
const history = useHistory()
...
history.push('/Inicio')
Note that this will only work with React version >= 16.8
The reason why your code is not working is because of the bindings.
When using this.props.history.push("/Inicio"), this refers to the function you're in, not to the Component history.
If you want to keep your code that way, you can simply turn your function into an arrow function:
.then((data) => {
if(data == "success"){
console.log(data)
self.setState({users: data});
**this.props.history.push("/Inicio")** // now this will be correct
**window.location.reload();** // no need, react handles the reload
}
else{
if(data == 'El usuario o la contraseña no coinciden'){
self.setState({ apiResponse: data })
}
}
Although, I'd strongly advise using the latest features of react (check the hooks, it's really useful).

Related

Cannot set headers after they are sent to the client [NEXTJS]

I am currently busy working on a personal project with a dashboard using NextJS, which I believe allows me to learn NextJS and the fundementals of typescript. I am trying to work out how to set a welcome message if a response sent back from the server is set to True, I did manage to code this in, but I got cannot set headers after they are sent to the client.
My entire code file for the dashboard is the following snippet:
import styles from "./index.module.css";
import { type NextPage } from "next";
import Head from "next/head";
import Link from "next/link";
import Container from 'react-bootstrap/Container';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
import NavDropdown from 'react-bootstrap/NavDropdown';
import { signIn, signOut, useSession } from "next-auth/react";
import Image from 'react-bootstrap/Image'
import Router from "next/router";
import { useEffect, useState } from "react";
import { library } from '#fortawesome/fontawesome-svg-core'
import { faHouse, fas, faServer } from '#fortawesome/free-solid-svg-icons'
import "#fortawesome/fontawesome-svg-core/styles.css";
import { Offline, Online } from "react-detect-offline";
import { config } from "#fortawesome/fontawesome-svg-core";
// Tell Font Awesome to skip adding the CSS automatically
// since it's already imported above
config.autoAddCss = false;
{/* The following line can be included in your src/index.js or _App.js file*/}
import 'bootstrap/dist/css/bootstrap.min.css';
library.add(fas, faServer, faHouse)
// import the icons you need
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
const Protected: NextPage = () => {
const { status, data } = useSession()
const { data: sessionData2 } = useSession();
useEffect(() => {
if (status === "unauthenticated") Router.replace("/auth/signin");
}, [status]);
// const isBreakpoint = useMediaQuery(768)
// return (
// <div>
// { isBreakpoint ? (
// <div>
// <HamburgerMenu />
// </div>
// ) : (
// <div>
// <FullMenu />
// </div>
// )
if (status === "authenticated")
return (
<><Navbar bg="dark" expand="lg" variant="dark" className="justify-content-end flex-grow-1 pe-3">
<Container>
<Navbar.Brand href="#home" style={{ fontSize: '25px' }}><strong>Litika.</strong> </Navbar.Brand>
{/* <Navbar.Toggle aria-controls="basic-navbar-nav" /> */}
<Navbar.Collapse id="basic-navbar-nav" className={styles.rightNavbar}>
<Nav className={styles.rightNavbar}>
<Nav.Link href="#home" className={styles.body}><FontAwesomeIcon icon={faHouse} /></Nav.Link>
<Nav.Link href="#link" className={styles.body}>Link</Nav.Link>
<NavDropdown className={styles.body} title={<Image
src={sessionData2 ? `https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_640.png` : 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_640.png'}
roundedCircle
style={{ width: '30px' }} />}
id="basic-nav-dropdown">
<NavDropdown.Item href="#action/3.1" onClick={sessionData2 ? () => void signOut() : () => void signIn()}>
{sessionData2 ? "Sign out" : "Sign in"}
</NavDropdown.Item>
<NavDropdown.Item href="#action/3.2">
Another action
</NavDropdown.Item>
<NavDropdown.Item href="#action/3.3">Something</NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item href="#action/3.4">
Separated link
</NavDropdown.Item>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
<div className={styles.connectivityStatus}>
<br></br>
<div className={styles.connectivityStatusBox}>
<Online>🟢 You are connected to the internet.</Online>
<Offline>🔴 You are not connected to the internet.</Offline>
</div>
<WelcomeContainer />
</div>
<main className={styles.main}>
<h1 className={styles.title}>
Litika. </h1>
<div className={styles.container}>
<div className={styles.cardRow}>
<div className={styles.card}>
<h1 className={styles.cardTitle}><FontAwesomeIcon icon={faServer} /> HELLO!</h1>
<p className={styles.cardText}>How are you? I am under the water.</p>
</div>
</div>
</div>
</main>
<main className={styles.main2}>
<div className={styles.container}>
</div>
</main>
</>
);
return (
<>
<main className={styles.main}>
<div className={styles.container}>
<h1 className={styles.title}>
Loading the Dashboard.
</h1>
</div>
</main>
<main className={styles.main2}>
<div className={styles.container}>
<h1 className={styles.title}>
</h1>
</div>
</main>
</>
);
};
export default Protected;
const WelcomeContainer: React.FC = () => {
const { data: sessionData } = useSession();
var [welcomeMessage, setWelcomeMessage] = useState(null);
const payload = JSON.stringify({
email: sessionData.user?.email,
});
fetch('/api/userService/isuserwelcome', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: payload
})
.then(response => response.json())
.then(data => {
console.log(data);
if (data === true) {
setWelcomeMessage(
<div className={styles.welcomeContainer}>
<h1>Welcome to the Los pollos hermanos family.</h1>
</div>
);
}
})
.catch(error => {
console.error(error);
});
return (
<>
{welcomeMessage}
</>
);
};
The code for the IsUserWelcome API Route is the following:
import type { NextApiRequest, NextApiResponse } from 'next';
import { PrismaClient } from '#prisma/client';
const prisma = new PrismaClient();
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const { email } = req.body;
const cliresponse = await prisma.user.findFirst({
where: {
email,
},
});
console.log(cliresponse.newUser)
if (cliresponse.newUser == true) {
res.status(200).json(cliresponse.newUser)
}
res.status(200).json({ cliresponse });
} catch (err) {
console.log(err)
res.status(500).json({ error: err});
}
}
I have identified the issue to be coming from the WelcomeContainer Functional Component and do understand that NextJS & React are dynamic, which will lead to updates through the DOM. However, I haven't really tried anything yet to fix this issue because nothing from doing a simple Google search could lead me to fix this issue, so any guidance & help will be appreciated!
The parts that are different in this question is that it pertains to react and NextJS in general, not express.
You are sending a response twice, after a response is send you should return from the function.
import type { NextApiRequest, NextApiResponse } from 'next';
import { PrismaClient } from '#prisma/client';
const prisma = new PrismaClient();
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const { email } = req.body;
const cliresponse = await prisma.user.findFirst({
where: {
email,
},
});
console.log(cliresponse.newUser)
if (cliresponse.newUser == true) {
res.status(200).json(cliresponse.newUser)
return; // you need to return here so the code below doesn't get executed
}
res.status(200).json({ cliresponse });
} catch (err) {
console.log(err)
res.status(500).json({ error: err});
}
}

Jest cannot read property of "searchForGames" of undefined

I am trying to create a test, that checks wether a component draws correctly or not. But when i try to run the test i get this error message:
TypeError: Cannot read property 'searchForGames' of undefined
I have tried adding som mock data in searchForGames, but i cant get it to work:(
Here is my code:
search.tsx
import * as React from "react";
import { Component } from "react-simplified";
import { CardGroup, GameCard, Container, NavBar, SubHeader, Alert } from "./widgets";
import { igdbService, Game } from "./services";
export class Search extends Component<{
match: { params: { searchString: string } };
}> {
games: Game[] = [];
render() {
return (
<Container>
{this.games.length !== 0 ? (
<CardGroup>
{this.games.map((game) => {
return (
<div key={game.id} className="col">
<NavBar.Link to={"/games/" + game.id}>
<GameCard
name={game.name}
url={game.cover ? game.cover.url.replace("thumb", "cover_big") : ""}
/>
</NavBar.Link>
</div>
);
})}
</CardGroup>
) : (
<div className="d-flex flex-column justify-content-center">
<h4 className="mx-auto">The game you are looking for could not be found in the IGDB database..</h4>
<img className="w-40 mx-auto" alt="Sadge" src="https://c.tenor.com/kZ0XPsvtqw8AAAAi/cat-farsi-sad.gif"/>
</div>
)}
</Container>
);
}
mounted() {
this.getGames();
}
getGames() {
igdbService
.searchForGames(this.props.match.params.searchString)
.then((response) => (this.games = response))
}
}
search.test.tsx
import {Search} from "../src/Search"
import * as React from "react";
import { shallow } from 'enzyme';
import { NavBar, GameCard } from "../src/widgets";
jest.mock("../src/services", () => {
class Service {
searchForGames(string: searchString) {
return Promise.resolve(
[
{
id:143755,
cover: {
id:1311803,
url: '//images.igdb.com/igdb/image/upload/t_thumb/co2tp7.jpg'
},
name: "Terrorist Killer",
}
]
)
}
getGames() {
return Promise.resolve();
}
}
return new Service();
})
test("Search draws correctly", (done) => {
const wrapper = shallow (<Search match={{params: {searchString: "Terrorist"} }}/>)
setTimeout(() => {
expect(
wrapper.containsAllMatchingElements([
<div>
<NavBar.Link to="/games/143755">
<GameCard
name="Terrorist Killer"
url="//images.igdb.com/igdb/image/upload/t_thumb/co2tp7.jpg"
/>
</NavBar.Link>
</div>
])
).toEqual(true);
done();
})
})

How can I initiate react to reload after a data change on the node js side?

So I added a calendar to react which changes the workout data to a specific date. When I change the date the data does change correctly. If I refresh the react web pages I get the updated data. I'm just a little confused on how to have node js initiate the data update in react. After changing the data I have node emitting to this socket...
import { format } from 'date-fns'
import ScrapeExperienceLevel from './ScrapeExperienceLevel'
export default (websockets, queue, timer, workouts, scrapeExperienceLevel) => {
websockets.on('connection', socket => {
socket.on('joinRoom', roomName => {
socket.join(roomName)
})
socket.on('leaveRoom', roomName => {
socket.leave(roomName)
})
setInterval(function(){
socket.emit('GetCheckedInMemebers', queue.dump());
}, 3000);
socket.on('initScreen', screen => {
const screenWorkouts = workouts.current.filter(workout => workout.station == screen)
socket.emit('screenInfo', screenWorkouts)
socket.emit('queueInfo', queue.screen(screen - 1))
socket.emit('timer', { time: timer.formatTime().formattedTime })
})
// code in question
socket.on('GetWorkoutDate', async function (date) {
await workouts.getNewWorkouts(date.date)
const screenWorkouts = workouts.current.filter(workout => workout.station == 1)
socket.emit('UpdateWorkouts', screenWorkouts)
})
socket.on('initAdmin', () => {
socket.emit('queue', queue.dump())
socket.emit('timer', { time: timer.formatTime().formattedTime })
})
socket.on('initOverview', () => {
socket.emit('workouts', workouts.current)
socket.emit('queue', queue.dump())
socket.emit('timer', { time: timer.formatTime().formattedTime })
})
socket.on('addUser', async (person) => {
if(typeof person.member_id == 'undefined')
person.member_id = ''
else{
person.experiencelevel = await scrapeExperienceLevel.getExperienceLevel(person.member_id)
person.firstname = person.firstname + ' '
}
queue.add(person, timer)
websockets.emit('queue', queue.dump())
})
socket.on('removeUser', ({ group, person, list }) => {
queue.remove(group, person, list)
websockets.emit('queue', queue.dump())
})
socket.on('reorder', waiting => {
queue.reorder(waiting)
websockets.emit('queue', queue.dump())
})
socket.on('toggleTimer', () => {
if (timer.isRunning()) {
timer.pause()
} else {
timer.start()
}
})
})
}
Here's my Screen page's react...
import React, { Component } from 'react'
import { defaultTo } from 'ramda'
import './styles.css'
class Screen extends Component {
state = {
time: '',
rest: false,
workout: [],
queue: [],
}
UpdateTimer = ({ time, rest }) => {
this.setState({ time, rest })
}
UpdateScreenInfo = ({ data }) => {
this.setState({ workout: defaultTo({}, data[0]) })
}
UpdateQueueInfo = ({ data }) => {
this.setState({ queue: defaultTo({}, data) })
}
UpdateQueue = ({ queue, screenNumber }) => {
this.setState({ queue: defaultTo({}, queue[screenNumber - 1]) })
}
componentDidMount() {
const screenNumber = this.props.match.params.id
const { socket } = this.props
socket.on('connect', () => {
socket.emit('joinRoom', 'screens')
socket.emit('initScreen', screenNumber)
})
socket.on('timer', ({ time, rest }) => this.UpdateTimer({ time, rest }))
socket.on('screenInfo', data => this.UpdateScreenInfo({ data }))
socket.on('queueInfo', data => this.UpdateQueueInfo({ data }))
socket.on('queue', ({ queue }) => this.UpdateQueue({ queue, screenNumber }))
//socket.on('UpdateWorkouts', (updatedData) => this.UpdateWorkoutsData(updatedData))
}
componentWillUnmount() {
const { socket } = this.props
socket.off('timer', this.UpdateTimer)
socket.off('screenInfo', this.UpdateScreenInfo)
socket.off('queueInfo', this.UpdateQueueInfo)
socket.off('queue', this.UpdateQueue)
}
renderMovement = (movement, equipment) => {
if (!movement) return <noscript />
return (
<div className="screenMove">
{equipment && `${equipment.title} `}
{movement.title}
</div>
)
}
render() {
const { time, rest, queue } = this.state
const { workout } = this.props
const variation = defaultTo({}, workout.variation)
const person1 = defaultTo({}, queue[0])
const person2 = defaultTo({}, queue[1])
return (
<main className="screenWrapper">
<div className="screenHeader">
<span className="screenFirstUser">
{(() => {
if (person1.experiencelevel === 'novice') {
// light purple
return (
<div style={{color:'#BF5FFF', fontWeight: 'bold', display: 'inline-block'}}>{person1.firstname}</div>
)
} else if (person1.experiencelevel === 'beginner') {
// light blue
return (
<div style={{color:'#87CEFA', fontWeight: 'bold', display: 'inline-block'}}>{person1.firstname}</div>
)
} else if (person1.experiencelevel === 'intermediate') {
return (
<div style={{color:'orange', fontWeight: 'bold', display: 'inline-block'}}>{person1.firstname}</div>
)
} else if (person1.experiencelevel === 'advanced') {
// gym green
return (
<div style={{color:'#93C90E', fontWeight: 'bold', display: 'inline-block'}}>{person1.firstname}</div>
)
}else if (person1.experiencelevel === 'expert') {
return (
<div style={{color:'red', fontWeight: 'bold', display: 'inline-block'}}>{person1.firstname}</div>
)
}
})()}
</span>
<span className={`screenTimer alt ${rest ? 'rest' : ''}`}>{time ? time : '0:00'}</span>
<span className="screenSecondUser">
{(() => {
if (person2.experiencelevel === 'novice') {
// light purple
return (
<div style={{color:'#BF5FFF', fontWeight: 'bold', display: 'inline-block'}}>{person2.firstname}</div>
)
} else if (person2.experiencelevel === 'beginner') {
// light blue
return (
<div style={{color:'#87CEFA', fontWeight: 'bold', display: 'inline-block'}}>{person2.firstname}</div>
)
} else if (person2.experiencelevel === 'intermediate') {
return (
<div style={{color:'orange', fontWeight: 'bold', display: 'inline-block'}}>{person2.firstname}</div>
)
} else if (person2.experiencelevel === 'advanced') {
// gym green
return (
<div style={{color:'#93C90E', fontWeight: 'bold', display: 'inline-block'}}>{person2.firstname}</div>
)
}else if (person2.experiencelevel === 'expert') {
return (
<div style={{color:'red', fontWeight: 'bold', display: 'inline-block'}}>{person2.firstname}</div>
)
}
})()}
</span>
</div>
<p className="screenVariation">{variation.title}</p>
<div className="screenMoves">
{this.renderMovement(workout.movementOne, workout.equipmentOne)}
{this.renderMovement(workout.movementTwo, workout.equipmentTwo)}
{this.renderMovement(workout.movementThree, workout.equipmentThree)}
</div>
</main>
)
}
}
export default Screen
Also found the parent component Daniele enlightened me about. Now I'm sharing 1 socket connection throughout all the components.
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { path } from 'ramda'
import { connect, Provider } from 'react-redux'
import ReactDOM from 'react-dom'
import { BrowserRouter, Route, Redirect, Switch } from 'react-router-dom'
import openSocket from 'socket.io-client'
import { defaultTo } from 'ramda'
import './index.css'
import 'semantic-ui-css/semantic.min.css'
import store from './redux/store'
import Admin from './screens/Admin'
import Screens from './screens/Screens'
import Auth from './screens/Auth'
import BackScreen from './screens/Screens/Back'
import FrontScreen from './screens/Screens/Front'
const socket = openSocket(`http://${window.location.hostname}:${process.env.REACT_APP_WEBSOCKET_PORT}`)
function mapState(state) {
return {
loggedIn: path(['conf', 'loggedIn'], state),
}
}
class App extends Component {
state = {
workout: []
}
static propTypes = {
loggedIn: PropTypes.bool,
}
componentDidMount() {
socket.on('UpdateWorkouts', (workout) => { console.log(workout[0]); this.setState({ workout: defaultTo({}, workout[0]) }) })
}
render() {
const { loggedIn } = this.props
const workout = this.state
return (
<BrowserRouter>
<Switch>
{!loggedIn && <Route path="/" component={(props) => <Auth socket={socket} {...props} /> }/> }
<Route exact path="/admin" component={(props) => <Admin socket={socket} {...props} /> } />
<Route path="/s/back" component={(props) => <BackScreen socket={socket} {...props} /> } />
<Route path="/s/front" component={(props) => <FrontScreen socket={socket} {...props} /> } />
<Route path="/s/:id" component={(props) => <Screens {...props} socket={socket} workout={workout} /> } />
<Redirect to={'/s/1'} />
</Switch>
</BrowserRouter>
)
}
}
const ConnectedApp = connect(mapState)(App)
const rootEl = document.getElementById('root')
ReactDOM.render(
<Provider store={store}>
<ConnectedApp />
</Provider>,
rootEl,
)
Here's my Admin page.
import React, { Component, Fragment } from 'react'
import { Button, Grid, Header, Dimmer, Loader } from 'semantic-ui-react'
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { defaultTo } from 'ramda'
import './styles.css'
import WaitingList from './WaitingList'
import Stations from './Stations'
import AddUserModal from './AddUserModal'
class Admin extends Component {
state = {
loading: false,
time: '',
rest: false,
data: {
waiting: [],
queue: [],
},
startDate: new Date(),
showAddUserModal: false,
}
handleChange = date => {
const { socket } = this.props
this.setState({
startDate: date
})
socket.emit('GetWorkoutDate', { date })
}
UpdateTimer = ({ time, rest }) => {
this.setState({ time, rest })
}
UpdateQueue = ({ data }) => {
this.setState({ queue: defaultTo({}, data) })
}
GetCheckedInMemebers = ({ data }) => {
this.setState({ data })
}
componentDidMount() {
const { socket } = this.props
socket.on('connect', () => {
socket.emit('initAdmin')
})
socket.on('timer', ({ time, rest }) => this.UpdateTimer({ time, rest }))
socket.on('queue', data => this.UpdateQueue({ data }))
socket.on('GetCheckedInMemebers', data => this.GetCheckedInMemebers({ data }))
}
componentWillUnmount() {
const { socket } = this.props
socket.off('timer', this.UpdateTimer)
socket.off('queue', this.UpdateQueue)
socket.off('GetCheckedInMemebers', this.GetCheckedInMemebers)
}
addPersonToWaitingList = person => {
const { socket } = this.props
socket.emit('addUser', person)
}
removePersonFromList = (groupIndex, personIndex, list) => {
const { socket } = this.props
socket.emit('removeUser', { group: groupIndex, person: personIndex, list: list })
}
reorderWaitingList = waiting => {
const { data } = this.state
const { socket } = this.props
this.setState({ data: { ...data, waiting } })
socket.emit('reorder', waiting)
}
toggleTimer = () => {
const { socket } = this.props
socket.emit('toggleTimer')
}
render() {
const { loading, rest, time, data, showAddUserModal } = this.state
return (
<Fragment>
change workout date <DatePicker
dateFormat="M/d/yy"
selected={this.state.startDate}
onChange={this.handleChange}
/>
<Grid container columns={2} divided id="adminWrapper">
<Grid.Row className="fGrow">
<Grid.Column className="listWrapper">
<Header size="huge" textAlign="center">
Queue
<Button positive floated="right" onClick={() => this.setState({ showAddUserModal: true })}>
Add
</Button>
</Header>
<WaitingList
className="adminList"
waiting={data.waiting}
reorder={this.reorderWaitingList}
removeFromList={this.removePersonFromList}
/>
</Grid.Column>
<Grid.Column className="listWrapper">
<Header size="huge" textAlign="center">
Stations
</Header>
<Stations className="adminList" queue={data.queue}
removeFromList={this.removePersonFromList}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Button fluid color={rest ? 'red' : 'blue'} onClick={this.toggleTimer}>
<span>{time ? time : '0:00'}</span>
</Button>
</Grid.Row>
</Grid>
<AddUserModal
show={showAddUserModal}
handleClose={() => this.setState({ showAddUserModal: false })}
addUser={this.addPersonToWaitingList}
setLoading={isLoading => this.setState({ loading: isLoading })}
loading={loading}
/>
<Dimmer active={loading} page>
<Loader />
</Dimmer>
</Fragment>
)
}
}
export default Admin
New to node and react. Appreciate the guidance!
Edit: The calendar (date picker) is on another react page. GetWorkoutDate is being called. However, react never gets the data from the emit socket.emit('UpdateWorkouts', screenWorkouts). I verified the everything works correctly except getting the new data to update the react state.
Final Edit:
So for some reason I was not able to send the workout through props for the Screen page. Although, it is the correct way for react there just must be something going on with my environment. Here's what I did get working for me. When Screen page loads it loads the workout. Then I just added settimeout to repeat getting the workout every 5 secs.
socket.on('initScreen', async screen => {
setInterval(async function(){
const screenWorkouts = await workouts.current.filter(workout => workout.station == screen)
socket.emit('screenInfo', screenWorkouts)
}, 5000)
socket.emit('queueInfo', queue.screen(screen - 1))
socket.emit('timer', { time: timer.formatTime().formattedTime })
})
What's weird is after sending the workout to screens between 3 and 8 times correctly eventually it sends an empty array. So to prevent the workout from updating when it's empty I check it for length before I try and update the state.
UpdateScreenInfo = ({ data }) => {
if(data.length !== 0)
this.setState({ workout: defaultTo({}, data[0]) })
I'm awarding Daniele the points since they are about to expire and Daniele helped so much!
The problem is that each time you call openSocket you open a new connection to the server. Looking at your server code it seems that your 'GetWorkoutDate' message handler replies with 'UpdateWorkouts' message on the same socket connection; the problem is that 'UpdateWorkouts' is received by the react component sending the 'GetWorkoutDate' message and not by other react components due they open a new connection each.
The proper way to handle with WebSockets in a react app where more components needs to access the WebSocket is to open it only in the root component and passing it to child components as a property. Something like:
MainComponent extends React.Component {
componentDidMount() {
// Open the socket only in the main component
this.socket = openSocket(...);
}
componentWillUnmount() {
this.socket.close();
}
render() {
return (
<div>
{/* pass the socket as property to child components;
this can be repeated with all nested sub-component */}
<ChildComponent socket={this.socket}>
</div>
)
}
}
ChildComponent extends React.Component {
componentDidMount() {
const { socket } = this.props;
// create the required handlers and store them
this.handler = data => { /* do what you need with data */ };
// add the handlers to the socket
socket.on("message", this.handler);
}
componentWillUnmount() {
const { socket } = this.props;
// remove the handlers from the socket
socket.off("message", this.handler);
}
}
By this way you open a single connection and you share it between all components, now you can send messages to the server from a component and handle the response message on any other component.
Edit after last question update:
Two things:
the parent component is the root App component ok, but I can't still understand which is the component which emits the 'GetWorkoutDate' message (I was thinking it was the Screen 's parent component, but it seems I'm wrong); probably to find final solution we need to clarify this;
you are passing the socket (as a property) to the Route component, not to the Screen component: you need to change your routes as follows
<Route path="/s/:id" component={() => <Screens socket={socket} />} />
I checked better last version of your Screen component: you can't do this in componentDidMount method:
socket.on('timer', ({ time, rest }) => this.setState({ time, rest }))
you need to store the reference of the handler function in order to remove it later in componentWillUnmount method.
You can't do this in componentWillUnmount method:
socket.close();
now you have only one connection shared between all components of your app, if you close it once, you close it forever.
The strategy is: in componentDidMount method:
for each message (or more on general for each event), to create a handler function and store its reference
attache the referenced handler function to the desired message (or more in general, to the desired event)
then, in componentWillUnmount method:
to detach the handler function (throug the reference we still have) from the message (or more in general from the event),
by this way each time the component is mounted it starts listening on the desired messages/events, each time the component is unmounted it stops doing it and no actions will be performed on it while not mounted be the message/event handlers.
Edit:
Having circular import dependency is a bad idea: it's better to remove any var App = require('./index') (or similar) from child component files
But moreover, if the purpose of handleChange is only to emit something on the ws you don't need a so complicated design pattern: you can access the socket from Admin Component.
Probably what you need is
class App extends Component {
componentDidMount() {
// App component will never unmount: the reference for unmounting is not required
socket.on('UpdateWorkouts', ([workout]) => this.setState({ workout }));
}
render() {
const { workout } = this.state;
return (
...
<Route path="/s/:id" component={props => <Screens {...props, socket, workout} />} />
...
);
}
}
class Screen extends Component {
render() {
const { time, rest, queue } = this.state;
const { workout } = this.props;
...
}
}
You are calling componentDidMount function when socket emits UpdateWorkouts which is causing the loop. You should not call componentDidMount function of a react component, react handles the lifecycles methods itself. You can have another method to handle updates as following.
handleUpdateWorkouts = (updatedData) => {
//Update your workout, queue etc. whatever you update
this.setState({updatedData})
}
And assign this function as callback to on "UpdateWorkouts" events.
this.socket.on('UpdateWorkouts', (updatedData) => this.handleUpdateWorkouts(updatedData));
Make sure you are emitting GetWorkoutDate properly upon the calendar onChange.
this.socket.on('GetWorkoutDate', date); //something like this
If getNewWorkouts function is an async, then use await
socket.on('GetWorkoutDate', async function (date) {
await workouts.getNewWorkouts(date.date) //<----- here
const screenWorkouts = workouts.current.filter(workout => workout.station == 1)
console.log(screenWorkouts[0])
socket.emit('UpdateWorkouts', screenWorkouts)
})
I do have one solution at architect level which is, Return synchronous actions from the nodeJs sockets. So the moment client receive it, just dispatch the action received via socket and then automatically update it.
For eg: if socket returns
{
type: 'actionName',
data
}
then dispatching it directly would result in auto update of subscribed components.

How to update the user feed after updating the post?

I have a UserFeed component and EditForm component. As soon as I edit the form, I need to be redirected to the UserFeed and the updated data should be shown on UserFeed(title and description of the post).
So, the flow is like-UserFeed, which list the posts, when click on edit,redirected to EditForm, updates the field, redirected to UserFeed again, but now UserFeed should list the posts with the updated data, not the old one.
In this I'm just redirectin to / just to see if it works. But I need to be redirected to the feed with the updated data.
EditForm
import React, { Component } from "react";
import { connect } from "react-redux";
import { getPost } from "../actions/userActions"
class EditForm extends Component {
constructor() {
super();
this.state = {
title: '',
description: ''
};
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]: value
})
};
componentDidMount() {
const id = this.props.match.params.id
this.props.dispatch(getPost(id))
}
componentDidUpdate(prevProps) {
if (prevProps.post !== this.props.post) {
this.setState({
title: this.props.post.post.title,
description: this.props.post.post.description
})
}
}
handleSubmit = () => {
const id = this.props.match.params.id
const data = this.state
this.props.dispatch(updatePost(id, data, () => {
this.props.history.push("/")
}))
}
render() {
const { title, description } = this.state
return (
<div>
<input
onChange={this.handleChange}
name="title"
value={title}
className="input"
placeholder="Title"
/>
<textarea
onChange={this.handleChange}
name="description"
value={description}
className="textarea"
></textarea>
<button>Submit</button>
</div>
);
}
}
const mapStateToProps = store => {
return store;
};
export default connect(mapStateToProps)(EditForm)
UserFeed
import React, { Component } from "react"
import { getUserPosts, getCurrentUser } from "../actions/userActions"
import { connect } from "react-redux"
import Cards from "./Cards"
class UserFeed extends Component {
componentDidMount() {
const authToken = localStorage.getItem("authToken")
if (authToken) {
this.props.dispatch(getCurrentUser(authToken))
if (this.props && this.props.userId) {
this.props.dispatch(getUserPosts(this.props.userId))
} else {
return null
}
}
}
render() {
const { isFetchingUserPosts, userPosts } = this.props
return isFetchingUserPosts ? (
<p>Fetching....</p>
) : (
<div>
{userPosts &&
userPosts.map(post => {
return <Cards key={post._id} post={post} />
})}
</div>
)
}
}
const mapStateToPros = state => {
return {
isFetchingUserPosts: state.userPosts.isFetchingUserPosts,
userPosts: state.userPosts.userPosts.userPosts,
userId: state.auth.user._id
}
}
export default connect(mapStateToPros)(UserFeed)
Cards
import React, { Component } from "react"
import { connect } from "react-redux"
import { compose } from "redux"
import { withRouter } from "react-router-dom"
class Cards extends Component {
handleEdit = _id => {
this.props.history.push(`/post/edit/${_id}`)
}
render() {
const { _id, title, description } = this.props.post
return (
<div className="card">
<div className="card-content">
<div className="media">
<div className="media-left">
<figure className="image is-48x48">
<img
src="https://bulma.io/images/placeholders/96x96.png"
alt="Placeholder image"
/>
</figure>
</div>
<div className="media-content" style={{ border: "1px grey" }}>
<p className="title is-5">{title}</p>
<p className="content">{description}</p>
<button
onClick={() => {
this.handleEdit(_id)
}}
className="button is-success"
>
Edit
</button>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
nothing: "nothing"
}
}
export default compose(withRouter, connect(mapStateToProps))(Cards)
updatePost action
export const updatePost = (id, data, redirect) => {
return async dispatch => {
dispatch( { type: "UPDATING_POST_START" })
try {
const res = await axios.put(`http://localhost:3000/api/v1/posts/${id}/edit`, data)
dispatch({
type: "UPDATING_POST_SUCCESS",
data: res.data
})
redirect()
} catch(error) {
dispatch({
type: "UPDATING_POST_FAILURE",
data: { error: "Something went wrong"}
})
}
}
}
I'm not sure if my action is correct or not.
Here's the updatePost controller.
updatePost: async (req, res, next) => {
try {
const data = {
title: req.body.title,
description: req.body.description
}
const post = await Post.findByIdAndUpdate(req.params.id, data, { new: true })
if (!post) {
return res.status(404).json({ message: "No post found "})
}
return res.status(200).json({ post })
} catch(error) {
return next(error)
}
}
One mistake is that to set the current fields you need to use $set in mongodb , also you want to build the object , for example if req.body does not have title it will generate an error
updatePost: async (req, res, next) => {
try {
const data = {};
if(title) data.title=req.body.title;
if(description) data.description=req.body.description
const post = await Post.findByIdAndUpdate(req.params.id, {$set:data}, { new: true })
if (!post) {
return res.status(404).json({ message: "No post found "})
}
return res.status(200).json({ post })
} catch(error) {
return next(error)
}
}

Data Response Not able to map in the react router

I have created a e Commerce App in react. As you can see from the screenshot below, that when I click on the Apparels->Girls->Shoes , the data is not displayed in the screen.
So first, in the index.js file, I have set the BrowserRouter and created a component Main which holds all my other components.
index.js
import React from "react";
import ReactDOM from "react-dom";
import Main from "./Main";
import "./index.css";
import 'bootstrap/dist/css/bootstrap.css';
import {Route, NavLink, BrowserRouter} from 'react-router-dom';
ReactDOM.render((
<BrowserRouter>
<Main/>
</BrowserRouter>
)
,
document.getElementById("root")
);
After this I have created Main.js, where I have created components for Navigation and PLPMenu( which should display after clicking on the Girls->Shoes). Also in the Main.js, I have set the switch and Route paths
Main.js
import React, { Component } from "react";
import 'bootstrap/dist/css/bootstrap.min.css';
import { Route, Switch } from 'react-router-dom';
import Navigation from "./components/topNavigation";
import Footer from "./components/Footer";
import Banner from "./components/Banner";
import PLPMenu from "./components/PLPMenu";
import PDP from "./components/PDP";
import Home from "./components/Home";
class Main extends Component {
render() {
return (
<div>
<Navigation />
<Switch>
<Route exact path="/" component={Home} />
<Route path="Apparel/Girls/:id" component={PLPMenu}/>
<Route path="/PDP" component={PDP} />
<Route path="/Banner" component={Banner} />
<Route path="/Footer" component={Footer} />
</Switch>
</div>
)
}
}
export default Main;
In the topNavigation.js, I'm displaying the first level of categories like Apparel, Electronics, Grocery etc. Also, I have created, a component SubMenu for displaying the second level of categories like Girls, Boys, Women etc.
topNavigation.js
import React, { Component } from 'react';
import axios from 'axios';
import SubMenu from './subMenu';
class Navigation extends Component {
state = {
mainCategory: []
}
componentDidMount() {
axios.get('http://localhost:3030/topCategory')
.then(res => {
console.log(res.data.express);
this.setState({
mainCategory: res.data.express.catalogGroupView
})
})
}
render() {
const { mainCategory } = this.state;
return mainCategory.map(navList => {
return (
<ul className="header">
<li key={navList.uniqueID}>
<a className="dropbtn ">{navList.name} </a>
<ul className="dropdown-content">
<SubMenu below={navList.catalogGroupView} />
</ul>
</li>
</ul>
)
})
}
}
export default Navigation;
subMenu.js
In the submenu.js, I have created one more component SubListMenu for displaying the inner categories like Shoes, Pants, Skirts, Tops etc.
import React, { Component } from 'react';
import SubListMenu from './subListMenu';
class SubMenu extends Component {
render() {
const { below } = this.props;
return below.map(sub => {
return (
<React.Fragment>
<li key={sub.uniqueID}>
<a>{sub.name}</a>
{
<ul className="sub-menu">
{sub.catalogGroupView !== undefined && <SubListMenu id={sub.uniqueID} subBelow={sub.catalogGroupView} />}
</ul>
}
</li>
</React.Fragment>
)
})
}
}
export default SubMenu;
subListMenu.js
import React, { Component } from 'react';
import {Link} from 'react-router-dom';
class SubListMenu extends Component {
render() {
const { subBelow, id } = this.props;
console.log(subBelow)
return(
<React.Fragment>
{subBelow && subBelow.map(subl => {
return (
<li key={subl.uniqueID}><Link to = {`Apparel/Girls/${subl.name}/${ subl.uniqueID }`}>{subl.name}</Link></li>
)
})
}
</React.Fragment>
)
}
}
export default SubListMenu;
As you can see from my subListMenu.js code, that I have set the Link to PLPMenu.js. But in my case it's not happening. Also, the part Apparel/Girls in the Link, I have hard coded which i'm not able to make it dynamic.
PLPMenu.js
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
class PLPMenu extends Component {
state = {
shoeCategory: []
}
componentDidMount() {
let pathname= this.props.match.params.id
console.log(pathname)
axios.get(`http://localhost:3030/${pathname}`)
.then(res => (res.json()))
.then(data => {
this.setState({
shoeCategory: data.express.catalogEntryView
})
});
}
render() {
const { shoeCategory } = this.state;
const picUrl = 'https://149.129.128.3:8443'
return (
<div>
<div className="container">
<div className="row">
{
shoeCategory && shoeCategory.map(shoeList => (
<div className="col-md-4">
<h2 key={shoeList.uniqueID}></h2>
<img src={picUrl + shoeList.thumbnail} />
<Link to="/PDP"><p className="pdp">{shoeList.name}</p></Link>
<p>Price : {shoeList.price[0].value} {shoeList.price[0].currency}</p>
</div>
))
}
</div>
</div>
</div>
)
}
}
export default PLPMenu;
For fetching the data, I have used a node server.
server.js
const express = require('express');
const cors = require('cors');
const Client = require('node-rest-client').Client;//import it here
const app = express();
app.use(cors());
app.get('/topCategory', (req, res) => {
var client = new Client();
// direct way
client.get("http://149.129.128.3:3737/search/resources/store/1/categoryview/#top?depthAndLimit=-1,-1,-1,-1", (data, response) => {
res.send({ express: data });
});
});
app.get('/GirlShoeCategory', (req, res) => {
var client = new Client();
// direct way
client.get("http://149.129.128.3:3737/search/resources/store/1/productview/byCategory/10015", (data, response) => {
res.send({ express: data });
});
});
const port = 3030;
app.listen(port, () => console.log(`Server running on port${port}`));
I don't know where my code is getting wrong. Maybe I feel that from the node server, there is a mismatch with the reactjs routes, for which only in the url, it's displaying the link but not the contents. Can someone please give me an insight on this. My console browser window:
for this issue
In the PLPMenu.js page, I'm trying to fetch the data. But all I'm getting is this undefined.
componentDidMount() {
let pathname= this.props.match.params.id
console.log(this.props.match.params.id)
axios.get(`http://localhost:3030/${pathname}`)
.then(res => {return res.json();})
.then(data => {
this.setState({
shoeCategory: data.express.catalogEntryView
})
});
}
try this it will solve undefined issue.
I believe you have to take id from subBelow instead this.props.id.
so change the code like this.
<li key={subl.uniqueID}><Link to = {`Apparel/Girls/${ subl.uniqueID }`}>{subl.name}</Link></li>
The reason you get undefined in URL bar because you are not passing the unique id from SubMenu down to SubListMenu component.
What you need to do is
SubMenu.js
import React, { Component } from 'react';
import SubListMenu from './subListMenu';
class SubMenu extends Component {
render() {
const { below } = this.props;
return below.map(sub => {
return (
<React.Fragment>
<li key={sub.uniqueID}>
<a>{sub.name}</a>
{
<ul className="sub-menu">
{sub.catalogGroupView !== undefined && <SubListMenu id={sub.uniqueID} subBelow={sub.catalogGroupView} />}
</ul>
}
</li>
</React.Fragment>
)
})
}
}
export default SubMenu;
SubListMenus.js
import React, { Component } from 'react';
import {Link} from 'react-router-dom';
class SubListMenu extends Component {
render() {
const { subBelow, id } = this.props;
console.log(subBelow)
return(
<React.Fragment>
{subBelow && subBelow.map(subl => {
return (
<li key={subl.uniqueID}><Link to = {`Apparel/Girls/${ id }`}>{subl.name}</Link></li>
)
})
}
</React.Fragment>
)
}
}
export default SubListMenu;
Regarding below issue You need to do res.json() and in next .then get the data
In the PLPMenu.js page, I'm trying to fetch the data. But all I'm getting is this undefined.
Do this
componentDidMount() {
let pathname= this.props.match.params.id
console.log(this.props.match.params.id)
axios.get(`http://localhost:3030/${pathname}`)
.then(res => (res.json()))
.then(data => {
this.setState({
shoeCategory: data.express.catalogEntryView
})
});
}
Edit:
Add below condition in PLPMenu component
{shoeCategory && shoeCategory.map(shoeList => (

Resources