requireAuth with react-router v4 and redux - javascript

I have HOC for check is logged user.
import React from 'react';
import { Redirect } from 'react-router-dom';
export default function requireAuthComponent(store, Component, redirectTo = '/') {
return class RequireAuthComponent extends React.Component {
render() {
const state = store.getState();
const auth = state.auth;
if (auth.logged) {
return <Component {...this.props} />;
}
return <Redirect to={redirectTo} />;
}
};
}
And route
import PrivatContainer from './container/PrivatContainer';
import requireAuth from '../../components/requireAuth';
export default store => ({
path: '/privat',
exact: true,
component: requireAuth(store, PrivatContainer),
});
PrivatComponent
import React from 'react';
export default ({ auth: { logged }, toggleLogin }) => (
<div>
<h1>Privat Route</h1>
<h3>User is {logged.toString()}</h3>
<button onClick={() => toggleLogin()}>Logout</button>
</div>
);
When first enter on route all work fine, but when I change store with logged: false, I still stay on this component(route) because route call once and don't update on store update, how it fix or subscribe RequireAuthComponent on store update ?

Maybe it is not properly, so if you have better idea please tell me :)
export default function requireAuthComponent(store, Component, redirectTo = '/') {
return class RequireAuthComponent extends React.Component {
state = {
listener: null,
}
componentWillMount() {
const listener = store.subscribe(() => {
this.forceUpdate();
});
this.setState({ listener });
}
componentWillUnmount() {
const { listener } = this.state;
listener();
this.setState({ listener: null });
}
render() {
const state = store.getState();
const auth = state.auth;
if (auth.logged) {
return <Component {...this.props} />;
}
return <Redirect to={redirectTo} />;
}
};
}

Related

componentDidUpdate() is not firing when component is wrapped in HOC

I have HOC component which wraps all the page Components. The page component has pagination, when user clicks next, the route params changes and the difference between route param and state is compared in componentDidUpdate and then api is called. The code works on without wrapping HOC.
Routes
import React from 'react';
import { Redirect, Route, Switch, withRouter } from 'react-router-dom';
import hocWrapper from './hocWrapper'
import Dashboard from './components/screens/dashboard/Dashboard';
import Movies from './components/screens/movies/Movies';
const Routes = (props) => (
<Switch style={{ position: 'absolute' }}>
<Route exact path="/all/page:pageNumber" component={hocWrapper(Dashboard)} />
<Route exact path="/movies/page:pageNumber" component={Movies} />
</Switch>
);
export default withRouter(Routes);
HOC wrapper Component
import React, { useEffect } from 'react';
import { useDispatch } from "react-redux";
import { searchTextAction } from './containers/actions/userActions'
export default function (ComposedClass) {
const ClosedRouteForUser = (props) => {
const dispatch = useDispatch();
useEffect(() => {
console.log(window.location.pathname)
if (window.location.pathname !== `/search/page1` &&
window.location.pathname.includes('details') === false) {
dispatch(searchTextAction(''))
}
}, []);
return <ComposedClass {...props} />;
};
return ClosedRouteForUser;
}
Page Component
import React, { Component } from 'react'
import apiCall from '../../../services/apiCall';
import { trendingURL } from '../../../services/apiURL'
import MediaList from '../../common/MediaList'
import { withRouter } from 'react-router-dom';
class Dashboard extends Component {
state = {
dataList: [],
refresh: false,
pageNumber: this.props.match?.params && this.props.match.params.pageNumber,
}
async componentDidMount() {
try {
if (this.props.match?.params.routedFrom) {
localStorage.setItem("routedFrom", this.props.match.params.routedFrom)
}
console.log('cd mount')
window.scrollTo(0, 0)
this.setState({ refresh: true })
let data = { page: 1, media_type: "all" }
let apiData = await apiCall(trendingURL, data)
this.setState({ dataList: apiData.results, refresh: false })
} catch (error) {
console.log(error)
}
}
async componentDidUpdate(prevProps, prevState) {
if (this.props.match.params.pageNumber !== this.state.pageNumber) {
console.log('cd updates')
let data = { page: this.props.match.params.pageNumber, media_type: "all" }
let apiData = await apiCall(trendingURL, data)
this.setState({
dataList: apiData.results,
pageNumber: this.props.match.params.pageNumber,
refresh: false
})
}
}
pageNavigate = (value) => {
window.scrollTo(0, 0)
this.setState({ pageNumber: value })
this.props.history.replace({ pathname: `/all/page${value}` })
}
previous = () => {
this.pageNavigate(parseInt(this.props.match.params.pageNumber) - 1)
}
next = () => {
this.pageNavigate(parseInt(this.props.match.params.pageNumber ?
this.props.match.params.pageNumber :
1) + 1)
}
render() {
const { dataList, refresh } = this.state
return (
<MediaList
listData={dataList}
refresh={refresh}
previous={this.previous}
next={this.next}
/>
)
}
}
export default withRouter(Dashboard)

React Js state error (Cannot update during an existing state)

I wanted to set state for logged in user if user not logged in then navigate is true by this.setstate() method. but problem its showing error at console and not working the process here is error bellow
0.chunk.js:219252 Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
in Header (at App.js:9)
in App (at src/index.js:17)
in ErrorBoundary (at src/index.js:16)
in Router (created by BrowserRouter)
in BrowserRouter (at src/index.js:15)
Here bellow my app.js code
import React from 'react';
import './App.scss';
import Header from './components/Header/Header';
import Sidebar from './components/Sidebar/Sidebar';
import Routes from './routes/Routes';
function App() {
return (
<>
<Header />
<Sidebar />
<Routes />
</>
);
}
export default App;
Signin.js code
export default class SignIn extends Component {
constructor(props) {
super(props)
this.state = {
redirect: false,
};
}
}
render() {
if (this.state.redirect) {
return <Redirect to="/" />;
}
}
onSignInHandler = () => {
.......
this.setState({
....
redirect: true,
});
}
At Header.js where logout button have. I wanted to give this link onclick() handler. here below are code
export default class Header extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false
}
}
onLogoutHandler = () => {
....
this.setState({
navigate: true,
});
};
render() {
const { navigate } = this.state;
if (navigate) {
return <Redirect to="/signin" push={true} />;
}
return(<header></header>)
}
}
At header.js I have logout link which will trigger the onLogoutHandler and state will then will redirect to signin page. please help me for this issue
Please check if this works
onLogoutHandler = async () => {
....
await this.setState({
navigate: true,
});
};
You should have return statement at the end of your render() method from your class component
render() {
const { navigate } = this.state;
if (navigate) {
return <Redirect to="/signin" push={true} />;
}
// you should also have return statement in this line here
// return something like
return null;
}

this.props.history.push is not working while using HOC for logger

I have written below code,
1.I want to use Connect for storing usernamein local storage
2.I am using HOC component for logging purpose (callInfoLogger and callErrorLogger)
3.If I use connect and HOC together then this.props.history.push is not working (Its not redirecting to MyDashboard page)
Could you please let me know what do I need to do to fix the code?
App.js
import { BrowserRouter as Router, Route, Switch, } from "react-router-dom";
class App extends Component {
render() {
return (
<Router>
<Switch>
<Route path="/login" component={Login} />
<Route path="/dashboard" component={MyDashboard} />
</Switch>
</Router>
)
}
}
export default App;
Login.js
import React, { Component } from 'react';
import { withRouter } from "react-router-dom";
import { connect } from 'react-redux';
import HighLevelComponent from './HighLevelComponent';
class Login extends Component {
state = {
username: '',
password: '',
loginsuccess: true
}
callOnSubmit = (e) => {
e.preventDefault();
this.props.callErrorLogger("Inside call on Submit");
if (this.state.loginsuccess === true) {
this.props.callInfoLogger("Calling Info logger ");
this.props.onLoginSuccess(this.state.username);
this.props.history.push('/dashboard');
}
};
render() {
return (
<body>
<form className="login-form" onSubmit={this.callOnSubmit}>
<input
type="text" onChange={e => {
this.setState({
...this.state,
username: e.target.value
})
}}
/>
<input type="password"
onChange={e => {
this.setState({
...this.state,
password: e.target.value
})
}}
/>
<input type="submit" className="btnSbumit" value="LOG IN" />
</form>
</body>
)
}
}
const mapDispatchToProps = dispatch => {
return {
onLoginSuccess: (username) => dispatch({ type: "LOGIN_SUCCESS", username: username })
}
}
export default withRouter(HighLevelComponent(connect(null, mapDispatchToProps)(Login)));
MyDashboard.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class MyDashboard extends Component {
render() {
return (
<body>
<h1>Welcome to React.. {this.props.username}</h1>
</body>
)
}
}
const mapStateToProps = state => {
return {
username: state.username
}
}
export default connect(mapStateToProps, null)(MyDashboard);
HighLevelComponent.js
import React from 'react';
const HighLevelComponent = (WrapperComponent) => {
class NewComponent extends React.Component {
callInfoLogger = (infomsg) => {
console.info(infomsg);
}
callErrorLogger = (errmsg) => {
console.error(errmsg);
}
render() {
return <WrapperComponent callInfoLogger={this.callInfoLogger} callErrorLogger={this.callErrorLogger} />
}
}
return NewComponent;
}
export default HighLevelComponent;
In the HOC names HighLevelComponent pass the props to the wrapper component as follows:
const HighLevelComponent = (WrapperComponent) => {
class NewComponent extends React.Component {
callInfoLogger = (infomsg) => {
console.info(infomsg);
}
callErrorLogger = (errmsg) => {
console.error(errmsg);
}
render() {
return <WrapperComponent callInfoLogger={this.callInfoLogger} callErrorLogger={this.callErrorLogger} {...props} />
}
}
return NewComponent;
}
Please note the {...props} on the wrapper component. In this way all the props will be further passed.

How and where call function in component conditionally based on redux state

i have a component and in my component i have some child component.
in my parent component i have some function and i want to trigged it from child component. So i make it with redux.
It's my parent component:
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
import { bindActionCreators } from "redux";
import { splashStop } from "store/actions/Home/splashStop";
import { connect } from "react-redux";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
};
this.goPage = this.goPage.bind(this);
}
componentDidMount() {
}
goPage = () => {
this.props.history.push("/agencies");
};
render() {
if (this.props.homeSplash.splashStart == true) {
myTime.play();
}
return (
<div>
<ChildComponent />
</div>
);
}
}
const mapStateToProps = state => ({
homeSplash: state.homeSplash
});
function mapDispatchToProps(dispatch) {
return {
splashStop: bindActionCreators(splashStop, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(Home));
it's my child component:
here is in my child component at onClick function i dispatch redux action:
triggerSplash = () => {
this.props.splashStart();
};
my action:
export const START_SPLASH =
"START_SPLASH";
export const splashStart = () => {
return dispatch => {
dispatch({
type: START_SPLASH,
payload: true
});
};
};
and my reducer:
import { START_SPLASH } from "store/actions/Home/splashStart";
let initialState = {
splashStart: false
};
export default (state = initialState, action) => {
switch (action.type) {
case START_SPLASH:
return { ...state, splashStart: action.payload };
default:
return state;
}
};
my reducer, action is working correctly.
here is i wonder why myTime.play(); working always when component mount it's just don't care this control:
if (this.props.homeSplash.splashStart == true) {
myTime.play();
}
i place it to wrong place or what ?
In your redux structure, it seems everything OK. But you should provide your childComponent also to make it more clear.
If you have connected redux action correctly in your child component then try this:
<button ... onClick={() => this.triggerSplash()}>Click</button>
Put arrow function inside onClick. Because, in the component initialization, all component functions are called automatically in the render time.

Component rendering too early

I am trying to create a PrivateRoute(HOC) to test if a user has been authenticated(check is 'auth' exist in redux store) before sending them to the actual route. The issue is the privateroute finishes before my auth shows up in redux store.
The console.log runs twice, the first time, auth doesnt appear in the store, but it does the second time, but by that time, its already routed the user to the login screen.... How can I give enough time for the fetch to finish? I know how to do this condition when I simply want to display something conditionally(like login/logout buttons) but this same approach does not work when trying to conditionally route someone.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route } from 'react-router-dom'
class PrivateRoute extends Component {
render() {
const { component: Component, ...rest } = this.props
console.log(this.props)
return (
<Route {...rest} render={(props) => (props.auth ? <Component {...props} /> : props.history.push('/login'))} />
)
}
}
function mapStateToProps({ auth }) {
return { auth }
}
export default connect(mapStateToProps)(PrivateRoute)
I didn't use redux here, but I think you would get the main point. Hope this will help and feel free to ask any questions!
import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import Dashboard from "path/to/pages/Dashboard";
class App extends Component {
state = {
isLoggedIn: null,
};
componentDidMount () {
// to survive F5
// when page is refreshed all your in-memory stuff
// is gone
this.setState({ isLoggedIn: !!localStorage.getItem("sessionID") });
}
render () {
return (
<BrowserRouter>
<Switch>
<PrivateRoute
path="/dashboard"
component={Dashboard}
isLoggedIn={this.state.isLoggedIn}
/>
<Route path="/login" component={Login} />
{/* if no url was matched -> goto login page */}
<Redirect to="/login" />
</Switch>
</BrowserRouter>
);
}
}
class PrivateRoute extends Component {
render () {
const { component: Component, isLoggedIn, ...rest } = this.props;
return (
<Route
{...rest}
render={props =>
isLoggedIn ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
}
}
class Login extends Component {
state = {
login: "",
password: "",
sessionID: null,
};
componentDidMount () {
localStorage.removeItem("sessionID");
}
handleFormSubmit = () => {
fetch({
url: "/my-app/auth",
method: "post",
body: JSON.strigify(this.state),
})
.then(response => response.json())
.then(data => {
localStorage.setItem("sessionID", data.ID);
this.setState({ sessionID: data.ID });
})
.catch(e => {
// error handling stuff
});
};
render () {
const { sessionID } = this.state;
if (sessionID) {
return <Redirect to="/" />;
}
return <div>{/* login form with it's logic */}</div>;
}
}
When your action creator return the token, you need to store it in localStorage. and then you can createstore like below,
const store = createStore(
reducers,
{ auth: { authenticated : localStorage.getItem('token') }},
applyMiddleware(reduxThunk)
)
if user already logged in then token will be there. and initial state will set the token in store so you no need to call any action creator.
Now you need to secure your components by checking if user is logged in or not. Here's the HOC for do that,
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default ChildComponent => {
class ComposedComponent extends Component {
componentDidMount() {
this.shouldNavigateAway();
}
componentDidUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.auth) {
this.props.history.push('/');
}
}
render() {
return <ChildComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return { auth: state.auth.authenticated };
}
return connect(mapStateToProps)(ComposedComponent);
};

Categories