React is unable to react property path of undefined - javascript

I am building an Higher Order component to create a Idle Timeout featuer in my react app. I have the autologout component, I am in the process of including that in my index.js file. I am however, running into an issue where its unable to read the path. What am i doing wrong?
Here is my HOC component:
import IdleTimer from "react-idle-timer";
import React, { Component } from 'react'
import PropTypes from 'prop-types';
import { Switch, Route } from 'react-router-dom'
import DefaultLayout from "../containers/DefaultLayout";
export default function HOC (WrappedComponent) {
return class extends Component{
constructor(props) {
super(props);
this.state = {
timeout: 1000*5,
showModal: false,
userLoggedIn: false,
isTimedOut: false
};
this.idleTimer = null;
this.onAction = this._onAction.bind(this);
this.onActive = this._onActive.bind(this);
this.onIdle = this._onIdle.bind(this);
}
_onAction(e){
console.log('User did something', e);
this.setState({isTimedOut: false})
}
_onActive(e){
console.log('user is active', e);
this.setState({isTimedOut: false})
}
_onIdle(e){
console.log('user is idle', e);
const isTimedOut = this.state.isTimedOut;
if (isTimedOut){
this.props.history.push('/')
}else {
this.setState({showModal: true});
this.idleTimer.reset();
this.setState({isTimedOut: true})
}
}
render() {
const { match } = this.props;
return (
<WrappedComponent>
<IdleTimer
ref={ref => {this.idleTimer = ref}}
element={document}
onActive={this.onActive}
onIdle={this.onIdle}
onAction={this.onAction}
debounce={250}
timeout={this.state.timeout} />
<div className="">
<Switch>
<Route
exact path={`${match.path}/sales-analysis/dashboard`}
render={(props) => <DefaultLayout {...props} /> }/>
/>
</Switch>
</div>
</WrappedComponent>
)
}
}
}
HOC.propTypes = {
match: PropTypes.any.isRequired,
history: PropTypes.func.isRequired
};
Here is the index.js file containing the routes I am tyring to monitor
class AppEntry extends React.Component {
componentDidMount() {
window.addEventListener("appNotifyUpdate", this.appNotifyUpdate);
window.addEventListener("appUpdate", this.appUpdate);
window.addEventListener("offline", function(e) {
store.dispatch(setOffline(true));
});
window.addEventListener("online", function(e) {
store.dispatch(setOffline(false));
});
}
componentWillUnmount() {
window.removeEventListener("appNotifyUpdate", this.appNotifyUpdate);
window.removeEventListener("appUpdate", this.appUpdate);
window.removeEventListener("offline", function(e) {
store.dispatch(setOffline(true));
});
window.removeEventListener("online", function(e) {
store.dispatch(setOffline(false));
});
}
appNotifyUpdate = e => {
store.dispatch(setAppUpdateBar(true));
};
appUpdate = e => {
store.dispatch(setAppUpdateBar(false));
};
render() {
return (
<Provider store={this.props.store}>
<PersistGate loading={<div />} persistor={this.props.persistor}>
<BrowserRouter>
<div id="browserRouter">
<Route exact path="/" component={LoginContainer} key="login" />
<Route
path="/change-password"
component={LoginContainer}
key="change-password"
/>
<Route path="/logout" component={Logout} key="logout" />
<DefaultLayout
path="/sales-analysis/dashboard"
component={HOC(DashboardContainer)}
/>
<DefaultLayout
path="/sales-analysis/active-clients"
component={ActiveClientsContainer}
/>
<DefaultLayout
path="/sales-analysis/activity-reports"
component={ActivityReportsContainer}
/>
<DefaultLayout
path="/sales-analysis/segments"
component={SegmentsContainer}
/>
<DefaultLayout path="/prospects" component={ProspectsContainer} />
<DefaultLayout
path="/preferences/general"
component={PreferenceGeneral}
/>
<DefaultLayout
path="/preferences/account-manager"
component={PreferenceAccountManager}
/>
<DefaultLayout
path="/preferences/flex-fields"
component={PreferenceFlexFields}
/>
<DefaultLayout
path="/preferences/oem-manager"
component={PreferenceOEMManager}
/>
<DefaultLayout
path="/preferences/users"
component={PreferenceUsers}
/>
<DefaultLayout
path="/preferences/group-users"
component={PreferenceGroupUsers}
/>
</div>
</BrowserRouter>
</PersistGate>
</Provider>
);
}
}
AppEntry = HOC(AppEntry);
The exact error I am getting is this
TypeError: Cannot read property 'path' of undefined
render
./src/components/AutoLogout.js:65:52
62 | <div className="">
63 | <Switch>
64 | <Route
> 65 | exact path={`${match.path}/sales-analysis/dashboard`}
| render={(props) => <DefaultLayout {...props} /> }/>
67 | />
68 | </Switch>

This is due the fact you are not passing down props to your HoC.
Try, (Eg):
//Imports
import HOC from 'path/to/my/hoc';
//...code
const MyHoc = HOC(DashboardContainer);
//Main class
class AppEntry extends React.Component {
//...code
// In the render method take advantage of render prop
render() {
return (
//...code
<Route exact path="/my-path" render={(props) => <MyHoc {...props} />} />

Related

How can I send data from one component to another without calling that component in React?

How can I send the room.id data I obtained in the Homepage.js component to the Player.js component? Before doing this, I used PrivateRouter component in App.js. Therefore, my job is getting very difficult since I cannot call the component directly while routing the route with Link.
homepage.js
<Heading as="h1" mb={6}>
Rooms
</Heading>
<Divider orientation="horizontal" />
{rooms.map((room)=> (
<ListItem>
<ListItemText primary={room.roomName} secondary={room.roomInfo}/>
{/* <Link to={`/room/${room.id}`}></Link> */}
<Link to={`/room/${room.id}`}>
<Button>
Join Room
</Button>
</Link>
</ListItem>))}
</GridItem>
app.js
function App() {
return (
<Router>
<Layout>
<Switch>
<PrivateRoute exact path="/">
<Homepage />
</PrivateRoute>
<PrivateRoute exact path="/create-room">
<CreateRoom />
</PrivateRoute>
<PrivateRoute exact path="/contribute">
<Contribute />
</PrivateRoute>
<PrivateRoute exact path="/room/:id">
<Player />
</PrivateRoute>
<Route path="/login">
<LoginForm />
</Route>
<Route path="/confirm">
<ConfirmForm />
</Route>
<Route>
<NotFound />
</Route>
</Switch>
</Layout>
</Router>
);
}
and final, player.js
class Player extends Component {
constructor(){
super();
const params = this.getHashParams();
this.state = {
logeedIn : params.access_token ? true : false,
currentStatus: false,
rooms: {
roomAdminMail: "",
roomName: "",
roomInfo: ""
},
nowPlaying: {
artist_name : 'Not Checked',
song_name: 'Not Checked',
image: ''
}
}
this.getNowPlaying = this.getNowPlaying.bind(this);
this.getRoomInfo = this.getRoomInfo.bind(this);
if(params.access_token){
spotifyWebApi.setAccessToken(params.access_token);
}
}
getRoomInfo = () => {
const db = firebase.firestore();
db.collection('rooms').doc("H5H2XjdwCyrsAboQeRxT").get()
.then((doc) => {
if (doc.exists) {
this.setState( {
rooms: {
roomAdminMail: doc.data().roomAdminMail,
roomName: doc.data().roomName,
roomInfo: doc.data().roomInfo
}
})
} else {
console.log("No such document!");
}
}
)
}
All I want is to send the room.id I use when routing links in homepage.js to the getRoomInfo function in Player.js, that is to make it available as db.collection('rooms').doc(roomId).get()
In player.js you can use this.props.match.params.id.
match.params are key/value pairs parsed from the URL corresponding to the dynamic segments of the path
More info here: https://reactrouter.com/web/api/match
Use Events. A CustomEvent is one native solution you could start with, or you can use an event bus.

Protected route not working correctly with React and Firebase

I'm building a small app with firebase and react and currently working on implementing the authentication. I've set the onAuthStateChanged in my app component as a side effect and whenever user is logged in it should be redirected to a desired component from ProtectedRoute.
This works correctly but unfortunately when refreshing the page the ProtectedRoute is not rendering correct component and is just firing redirection.
I get what is happening: on refresh user is empty and only after then it change so I would expect to see a screen flicker and a proper redirection.
Could you please look at below code and maybe tell me how to fix this behavior?
App component:
const App = () => {
const [authUser, setAuthUser] = useState<firebase.User | null>(null);
const Firebase = useContext(FirebaseContext);
useEffect(() => {
const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
authUser ? setAuthUser(authUser) : setAuthUser(null);
});
return () => authListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<AuthUserContext.Provider value={authUser}>
<Router>
<div>
<Navigation />
<hr />
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
<Route
exact
path={ROUTES.PASSWORD_FORGET}
component={PasswordForget}
/>
<ProtectedRoute exact path={ROUTES.HOME} component={Home} />
<ProtectedRoute exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ADMIN} component={Admin} />
</div>
</Router>
</AuthUserContext.Provider>
);
};
Protected Route:
interface Props extends RouteProps {
component?: any;
children?: any;
}
const ProtectedRoute: React.FC<Props> = ({
component: Component,
children,
...rest
}) => {
const authUser = useContext(AuthUserContext);
return (
<Route
{...rest}
render={(routeProps) =>
!!authUser ? (
Component ? (
<Component {...routeProps} />
) : (
children
)
) : (
<Redirect
to={{
pathname: ROUTES.SIGN_IN,
state: { from: routeProps.location },
}}
/>
)
}
/>
);
};
Found the fix. Had to add the flag checking for user authentication status (default value of that flag is set to true). Flag needs to be passed to ProtectedRoute as prop and if is True then render some loading component:
App component:
const App = () => {
const [authUser, setAuthUser] = useState(false);
const [authPending, setAuthPending] = useState(true);
const Firebase = useContext(FirebaseContext);
useEffect(() => {
const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
setAuthUser(!!authUser);
setAuthPending(false);
});
return () => authListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<AuthUserContext.Provider value={authUser}>
<Router>
<div>
<Navigation />
<hr />
<Switch>
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
<Route
exact
path={ROUTES.PASSWORD_FORGET}
component={PasswordForget}
/>
<ProtectedRoute
pendingAuth={authPending}
exact
path={ROUTES.HOME}
component={Home}
/>
<ProtectedRoute
pendingAuth={authPending}
exact
path={ROUTES.ACCOUNT}
component={Account}
/>
<Route exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ADMIN} component={Admin} />
</Switch>
</div>
</Router>
</AuthUserContext.Provider>
);
};
ProtectedRoute:
interface Props extends RouteProps {
component?: any;
children?: any;
pendingAuth: boolean;
}
const ProtectedRoute: React.FC<Props> = ({
component: Component,
children,
pendingAuth,
...rest
}) => {
const authUser = useContext(AuthUserContext);
if (pendingAuth) {
return <div>Authenticating</div>;
}
return (
<Route
{...rest}
render={(routeProps) =>
!!authUser ? (
Component ? (
<Component {...routeProps} />
) : (
children
)
) : (
<Redirect
to={{
pathname: ROUTES.SIGN_IN,
state: { from: routeProps.location },
}}
/>
)
}
/>
);
};

Changing Nav Bar Contents on Authentication React

I am trying to change the navigation bar contents from Sign In/Register to other things such as Profile once the user logs in. My server sends a 401 when the user is not logged in and I have a HOC (RequireAuth.js) which checks the same for protected routes and redirects them to login if they have not logged in. However, I could not come up with a way to change the navbar contents with this logic and was wondering if there is a good way to do this (I do not want to use Redux for this purpose if possible).
RequireAuth.js
const RequireAuth = ( Component ) => {
return class Apps extends React.Component {
state = {
isAuthenticated: false,
isLoading: true
}
checkAuthentication = async() => {
const url = '/getinfo'
const json = await fetch(url, {method: 'GET'})
if (json.status !== 401) {
setTimeout(
function() {
this.setState({isAuthenticated: true, isLoading: false});}.bind(this), 1500);
} else {
setTimeout(
function() {
this.setState({isLoading: false});}.bind(this), 1500);
}
}
componentDidMount() {
this.checkAuthentication()
}
render() {
const style = {position: "fixed", top: "50%", left: "50%", transform: "translate(-50%, -50%)" };
console.log(this.state.isLoading)
const { isAuthenticated, isLoading } = this.state;
if(!isAuthenticated) {
return this.state.isLoading? <div style={style}><PacmanLoader color={'#36D7CC'}/></div> : <Redirect to="/" />
}
return <Component {...this.props} />
}
}
}
export { RequireAuth }
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
const NotFoundComponent = () => <div>404 NOT FOUND</div>
return (
<div>
<Router>
<NavigationBar />
<Switch>
<Route exact path = '/'
component = {LandingPage}
/>
<Route exact path = '/register'
component = {Register}
/>
<Route exact path = '/Profile'
component = {RequireAuth(Profile)}
/>
<Route exact path = '/About'
component = {RequireAuth(About)}
/>
<Route exact path = '/Name'
component = {RequireAuth(Name)}
/>
<Route path="*" component = {NotFoundComponent}/>
</Switch>
</Router>
</div>
);
}
}
export default withRouter(App);
Navigation.js
class NavigationBar extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Navbar bg="dark" variant="dark" expand="lg">
<Navbar.Brand >Hello</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
<Nav.Link as={Link} to='/'>Login</Nav.Link>
<Nav.Link as={Link} to='/register'>Register</Nav.Link>
</Nav>
</Navbar.Collapse>
</Navbar>
)
}
}
export default withRouter(NavigationBar);

check if data has been loaded before passing as a prop react/redux

I have an async/await api call that is made and the data is passed into my productsReducer, the problem I'm having is that the data is not loaded into the redux state when it is passed a prop into ProductsList and therefore throws an error when it tries to filter/map the data.
adding a check to see if it is loaded is possible in ProductsList products && products but I want to check on the Main component first if it is loaded and if so only pass this then as a prop.
I have attempted to do so with products: state.data.products.loadng === false && state.data.products.items.data but this always returns false
How can i check if my data is loaded before passing it as a prop?
Main.js
import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import { TransitionGroup, CSSTransition } from "react-transition-group";
import { connect } from "react-redux";
import styled from 'styled-components'
import ScrollToTop from '../atoms/ScrollToTop'
import Header from '../Header'
import Footer from '../Footer'
import Home from '../Home'
import * as actionCreators from './actions'
import media from '../atoms/Media'
import BreadCrumbs from '../Breadcrumbs'
import Category from '../Category'
import ProductsList from '../ProductsList'
import Product from '../Product'
import Alert from '../atoms/Alert'
import { fetchProducts } from "./actions";
import { ADD_TO_CART_E, ADD_TO_CART_P } from '../atoms/translations'
class Main extends React.Component {
componentDidMount() {
this.props.fetchCategories()
this.props.fetchSubCategories()
this.props.fetchProducts()
}
handleHideAlert() {
setTimeout(() => {
this.props.hideAlert()
}, 1000)
}
render() {
const {alert, categories, filteredColors, filteredSizes, language, products, showAlert, subCategories} = this.props
const e = language === 'english'
const p = language === 'polish'
return(
<Router>
<Wrap>
{alert && <div><Alert />{this.handleHideAlert()}</div>}
<Header e={e} p={p} categories={categories} subCategories={subCategories} language={language} />
{/* <BreadCrumbs /> */}
<Route style={{ flex: 1 }} render={({ location }) =>
<TransitionGroup>
<CSSTransition
key={location.key}
timeout={500}
classNames="page"
mountOnEnter={true}
unmountOnExit={true}
>
<Switch location={location}>
<MainWrap>
<Route exact path="/" render={props => <Home e={e} p={p} categories={categories} subCategories={subCategories} products={products} language={language} {...props} />} />
<Route exact path="/:catId" render={props => <Category e={e} p={p} categories={categories} subCategories={subCategories} language={language} {...props} />} />
<Route exact path="/:catId/:subCatId" render={props => <ProductsList e={e} p={p} filteredColors={filteredColors} filteredSizes={filteredSizes} categories={categories} subCategories={subCategories} products={products} language={language} {...props} />} />
<Route exact path="/:catId/:subCatId/:productId" render={props => <Product categories={categories} subCategories={subCategories} products={products} showAlert={showAlert} language={language} {...props} />} />
</MainWrap>
</Switch>
</CSSTransition>
</TransitionGroup>
} />
{console.log('products',products)}
{ e ? ADD_TO_CART_E : ADD_TO_CART_P}
<Footer />
</Wrap>
</Router>
)
}
}
const mapStateToProps = state => ({
alert: state.ui.alert,
language: state.language,
categories: state.data.categories.categories,
subCategories: state.data.subCategories.subCategories,
products: state.data.products.loadng === false && state.data.products.items.data
productsLoading: state.data.products.loadng,
filteredColors: state.filters.colors,
filteredSizes: state.filters.sizes
});
export default connect(mapStateToProps, actionCreators)(Main);
productsReducer.js
import {
FETCH_PRODUCTS_REQUEST,
FETCH_PRODUCTS_SUCCESS,
FETCH_PRODUCTS_FAILURE
} from '../../Constants'
const initialState = {
items: [],
loading: false,
error: null
};
export default function productReducer(state = initialState, action) {
switch(action.type) {
case FETCH_PRODUCTS_REQUEST:
return {
...state,
loading: true,
error: null
};
case FETCH_PRODUCTS_SUCCESS:
return {
...state,
loading: false,
items: action.payload
};
case FETCH_PRODUCTS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
items: []
};
default:
return state;
}
}
productsList.js
class ProductsList extends React.Component {
render() {
const { e, p, filteredColors, filteredSizes, match, products } = this.props
const productFilter = products && products.filter(products =>
(
(filteredColors.length >= 1 && filteredSizes.length < 1 && products.cat === match.params.subCatId) && filteredColors.includes(products.color) ||
(filteredSizes.length >= 1 && filteredColors.length < 1 && products.cat === match.params.subCatId) && filteredSizes.includes(products.size) ||
(filteredSizes.length >= 1 && filteredColors.length >= 1 && products.cat === match.params.subCatId) && filteredColors.includes(products.color) && filteredSizes.includes(products.size)) ||
(filteredSizes.length < 1 && filteredColors.length < 1 && products.cat === match.params.subCatId)
)
return(
<Section>
<Container>
<Grid>
{console.log(productFilter)}
{productFilter && productFilter.map(filteredProduct =>
<Cell key={filteredProduct.id}>
<ProductListCard e={e} p={p} match={match} {...filteredProduct} />
</Cell>
)}
</Grid>
</Container>
<Filters>
<Filter />
</Filters>
</Section>
)
}
}
const mapStateToProps = state => ({
filteredProducts: state.filteredProducts
});
export default connect(mapStateToProps, actionCreators)(ProductsList);
You could set the initial state as loading is true. In the constructor of the component or in the reducer. In the render function of Main.js check if the loading state is true and make sure to render something like a loading component.
When the data is loading set the loading state to false. The page will rerender and render the list with the data. Is this enough or would you like an example?

React router error (Failed prop type: Invalid prop `children` supplied to `Switch`, expected a ReactNode.)

Try to modify component, the main idea is, I want create login page, try modify App.js but get error
warning.js?6327:36 Warning: Failed prop type: Invalid prop children
supplied to Switch, expected a ReactNode.
My code is:
class App extends Component {
constructor(props) {
super(props)
}
routeWithSubRoutes(route) {
return (
<Route
key={_.uniqueId()}
exact={route.exact || false}
path={route.path}
render={props => (
// Pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes || null} />
)}
/>
);
}
render () {
return (
<div className={styles.App}>
<Helmet {...config.app} />
<NavBar />
<Switch>
{routes.map(route => this.routeWithSubRoutes.bind(this,route))}
</Switch>
</div>
)
}
}
export default App;
Code that I try to modify
export default () => {
// Use it when sub routes are added to any route it'll work
const login = () => {
}
const routeWithSubRoutes = route => (
<Route
key={_.uniqueId()}
exact={route.exact || false}
path={route.path}
render={props => (
// Pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes || null} />
)}
/>
);
var isLogin = false;
if(!isLogin) {
return (
<Login />
)
}
if(isLogin) {
return (
<div className={styles.App}>
<Helmet {...config.app} />
<NavBar />
<Switch>
{routes.map(route => routeWithSubRoutes(route))}
</Switch>
</div>
);
}
};
this code is working, but my not, how to fix this?
Function.bind doesn't call the function, it only binds its context. Instead, you should bind it in the constructur:
class App extends Component {
constructor(props) {
super(props)
this.routeWithSubRoutes = this.routeWithSubRoutes.bind(this)
}
/* ... */
render () {
return (
<div className={styles.App}>
<Helmet {...config.app} />
<NavBar />
<Switch>
{routes.map(route => this.routeWithSubRoutes(route))}
</Switch>
</div>
)
}
}

Categories

Resources