I created a store with redux to experiment with the app state management in react. So far I'm just trying to make a fake authentication behavior when clicking on the "sign in" button on the login page, which is working because my isLogged state change to true. But then when I try to access a path that I protected by checking if isLogged is true, I get false... why is the state of isLogged not saved when routing with react-router_dom?
index.js
const store = createStore(
allReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App/>
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
App.js
class App extends Component {
render() {
return (
<Box style={{width: "99.6vw", height: "95.6vh"}}>
<Router>
<SideNavBar/>
<Switch>
<Route exact path={"/"} render={() => <Redirect to={"/login"}/>}/>
<Route path={"/login"} component={LoginPage}/>
<ProtectedRoute path={"/somepage"} component={somePage}/>
</Switch>
</Router>
</Box>
);
}
}
LoginPage.js
class LoginPage extends Component {
render() {
const {dispatch} = this.props;
return (
<LoginPageContainer>
<img src={logo} alt={""} height={"350rem"}/>
<FilledInput placeholder={"Login or email"}/>
<FilledInput placeholder={"Password"}/>
<Button onClick={() => dispatch({ type: "SIGN_IN" })}>
Sign in
</Button>
</LoginPageContainer>
);
}
}
export default connect(null, null)(LoginPage);
ProtectedRoute.js
import {connectProtectedRoute as connect} from "../redux/connectProtectedRoute";
class ProtectedRoute extends Component {
render() {
const {isLogged, component} = this.props;
return (
<Route render={
() => {
if (isLogged)
return (component);
else
return (<Redirect to={"/login"}/>);
}
}/>
);
}
}
ProtectedRoute.propTypes = {
component: PropTypes.elementType.isRequired
};
export default connect(ProtectedRoute);
connectProtectedRoute.js
import {connect} from "react-redux";
function mapStateToProps(state) {
return ({
isLogged: state.isLogged
});
}
export const connectProtectedRoute = connect(mapStateToProps, null);
reducers.js
const allReducers = combineReducers({
isLogged: isLoggedReducer
});
export default allReducers;
isLoggedReducer.js
const isLoggedReducer = (state = false, action) => {
switch (action.type) {
case "SIGN_IN": return true;
case "SIGN_OUT": return false;
default: return state;
}
}
export default isLoggedReducer;
So I was just unaware of the losing state fact upon refresh. Comment from original post said it all, here they are for anyone ending here:
Modifying the URL manually (outside of react router) will cause a full page refresh and all state will be lost (unless you persist it in local storage or by some other method). This is your problem, nothing in the code looks wrong. – Brian Thompson
Modifying the url causes the page refresh and follows the rerunning the app, so all data in store are removed. Try to use history for page navigation. Here is how to use it. reacttraining.com/react-router/web/api/Hooks/usehistory – TopWebGhost
Related
I am trying to redirect user in case user is not authenticated and vice versa
so, I have the directory structure as follow
myproject
src
App.js
UserContext.js
routes
index.js
route.js
pages
Dashboard
index.js
authentication
login.js
In my app.js i do a call and get my authentication token
and set auth to true and pass it in user context but it has the default values and i cannot redirect currently redirecting with only window.location.href
my code for usercontext.js
import { createContext } from "react";
export const UserContext = createContext(null)
APP.js
const App = props => {
const [user,setUser] = React.useState(null)
var [auth,setAuth] = React.useState(false)
const isAuthenticated = ()=>
{
var isAdmin = true;
axios.get(`/verifyToken`).then((response)=>{
console.log(response.data.auth)
setUser({...response.data.user})
setAuth(response.data.auth)
console.log(response.data.user)
})
}
useEffect(() => {
isAuthenticated()
console.log(auth)
},[]);
function getLayout() {
let layoutCls = VerticalLayout
switch (props.layout.layoutType) {
case "horizontal":
layoutCls = HorizontalLayout
break
default:
layoutCls = VerticalLayout
break
}
return layoutCls
}
const Layout = getLayout()
return (
<React.Fragment>
<Router>
<Switch>
<UserContext.Provider value={{user,setUser,auth,setAuth,isAuthenticated}}>
{publicRoutes.map((route, idx) => (
<Authmiddleware
path={route.path}
layout={NonAuthLayout}
component={route.component}
key={idx}
isAuthProtected={auth}
exact
/>
))}
{authProtectedRoutes.map((route, idx) => (
<Authmiddleware
path={route.path}
layout={Layout}
component={route.component}
key={idx}
isAuthProtected={auth}
exact
/>
))}
</UserContext.Provider>
</Switch>
</Router>
</React.Fragment>
)
}
My index.js file has component and routes names array which i am looping above
and this is my route.js
const Authmiddleware = ({
component: Component,
layout: Layout,
isAuthProtected,
...rest
}) => (
<Route
{...rest}
render={props => {
return (
<Layout>
<Component {...props} />
</Layout>
)
}}
/>
)
Authmiddleware.propTypes = {
isAuthProtected: PropTypes.bool,
component: PropTypes.any,
location: PropTypes.object,
layout: PropTypes.any,
}
export default Authmiddleware;
So, now If in my dashboard.js I try to access user on wan tto redirect if auth is false it only has default values of user and auth
I am fetching as follows in dashboard.js
import {UserContext} from '../../UserContext'
const {user,setUser,auth,setAuth,isAuthenticated} = React.useContext(UserContext)
React.useEffect(()=>{
if(auth == false){
window.location.href='/login'
//IT TAKES ME LOGIN EVERYTIME AT IT IS ONLY GETTING DEFAULT VALUE THAT IS FALSE
},[])
WHAT I HAVE TRIED
If i place the isAuthenticated() function call in every component it works
but that would be like so many lines of code same in every component
What is the way to go with?
Anyone facing the same issue I resolved it by
bringing out
<UserContext.Provider></UserContext.Provider>
outside the switch
<UserContext.Provider value={{user,setUser,auth,setAuth,isAuthenticated}}>
<Switch>
</Switch>
</UserContext.Provider value={{user,setUser,auth,setAuth,isAuthenticated}}>
I FOUND THE REASON HERE: https://coderedirect.com/questions/324089/how-to-use-context-api-with-react-router-v4
The reason posted in answer here was that Switch expects routes directly.
I'm pretty new to React, and I'm having an issue with State in my App.jsx. My react-router code is in App.js, which after logging in the default page (HomePage) will load. I want to read the state after login to render my Header page if there is a user loaded in state. The issue I have is that the state in App.jsx is still null.
After login, my state shows that user is populated, but App.jsx is always null. I've tried pulling "user" from both props, and state, and I've tried adding user to mapState.
Here is what I have in my App.jsx:
import React from "react";
import { Router, Route } from "react-router-dom";
import { connect } from "react-redux";
import { history } from "../_helpers";
import { alertActions } from "../_actions";
import { PrivateRoute } from "../_components";
import { HomePage } from "../HomePage";
import { LoginPage } from "../LoginPage";
import { RegisterPage } from "../RegisterPage";
import Header from "../Header/Header";
class App extends React.Component {
constructor(props) {
super(props);
history.listen((location, action) => {
// clear alert on location change
this.props.clearAlerts();
});
}
render() {
const { alert } = this.props;
var user = this.state;
let header = null;
if (user) {
header = <Header />;
}
return (
<div>
{header}
<div className="jumbotron">
<div className="container">
<div className="col-sm-8 col-sm-offset-2">
{alert.message && (
<div className={`alert ${alert.type}`}>{alert.message}</div>
)}
<Router history={history}>
<div>
<PrivateRoute exact path="/" component={HomePage} />
<Route path="/login" component={LoginPage} />
<Route path="/register" component={RegisterPage} />
</div>
</Router>
</div>
</div>
</div>
</div>
);
}
}
function mapState(state) {
const { alert } = state;
return { alert };
}
const actionCreators = {
clearAlerts: alertActions.clear
};
const connectedApp = connect(
mapState,
actionCreators
)(App);
export { connectedApp as App };
Any help, or suggestions of a better way to do this, would be greatly appreciated.
You have to initialize the state fist.
You have 2 options to do that:
Within your constructor: call this.state = {...}
Outside: somewhere in your component call state = {...}
Both of these calls will initialize your state to the default state and after that, calls to this.state will be defined.
Hope this helps.
The state isn't initialized. Be careful with that. var user = this.state can be removed and then you can initialize the state. Also, add user to the state object and instead of if (user) use if (this.state.user). Then you can call state. I think this might be what you need.
For some reason after I connect my Routes component with the connect() code - my components are causing re-rendering after clicking them. Can someone help explain what is wrong with this code? After commenting our the connect functions click my buttons cause re-rendering. I removed my import statements to reduce amount of code.
// List of routes that uses the page layout
// listed here to Switch between layouts
// depending on the current pathname
const listofPages = [
/* See full project for reference */
];
class Routes extends React.Component {
constructor(props){
super(props);
this.state = {
hideNavigation: false
};
};
toggleHeader = () => {
const { hideNavigation } = this.state
this.setState({ hideNavigation: !hideNavigation })
};
render(){
const currentKey = this.props.location.pathname.split('/')[1] || '/';
const timeout = { enter: 500, exit: 500 };
// Animations supported
// 'rag-fadeIn'
// 'rag-fadeInRight'
// 'rag-fadeInLeft'
const animationName = 'rag-fadeIn'
return (
// Layout component wrapper
// Use <BaseHorizontal> to change layout
<Base hideNavigation={this.state.hideNavigation}>
<TransitionGroup>
<CSSTransition key={currentKey} timeout={timeout} classNames={animationName} exit={false}>
<div>
<Suspense fallback={<PageLoader/>}>
<Switch location={this.props.location}>
<Route
loggedIn={this.props.isLoggedIn}
path="/login"
render={() => (<Login toggleHeader={this.toggleHeader} />)}
/>
<PrivateRoute
loggedIn={this.props.isLoggedIn}
path="/my-credentials"
component={MyCredentials}
/>
<PrivateRoute
loggedIn={this.props.isLoggedIn}
path="/submenu"
component={SubMenu}
/>
<PrivateRoute
loggedIn={this.props.isLoggedIn}
path="/new-application"
toggleHeader={this.toggleHeader}
component={NewApplication}
/>
{ this.props.isLoggedIn ?
<Redirect to="/submenu"/> :
<Redirect to="/login"/>
}
</Switch>
</Suspense>
</div>
</CSSTransition>
</TransitionGroup>
</Base>
)
}
}
const mapStateToProps = (state) => {
console.log(state)
return {
"isLoggedIn": state.auth.isLoggedIn
}
}
const mapDispatchToProps = dispatch => ({ })
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(Routes));
Change the order of the HOCs. So change
export default connect(mapStateToProps, mapDispatchToProps(withRouter(Routes));
to
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Routes));
//routes
const AppRoute = () => {
return (
<BrowserRouter>
<div className="container">
<Switch>
<Route path='/' component={BooksList} exact/>
<Route path='/create' component={BookCreate}/>
<Route path='/books/:id' component={BookShow}/>
</Switch>
</div>
</BrowserRouter>
);
};
export default AppRoute;
//store
const store = createStore(reducers, applyMiddleware(Promise));
ReactDOM.render(
<Provider store={store}>
<AppRoute/>
</Provider>,
document.getElementById("root")
);
I use react and redux.
I created a BookShow component to show data of one book. Data loads correctly but when I refresh the page I get this error:
Type Error: Cannot read property 'title' of undefined and hole state is undefined.
Why did this happen and how can I prevent it from happening?
this is my code
import React from 'react';
import {connect} from 'react-redux'
const BookShow = props => {
if(!props){
return <div>loading...</div>
}
return (
<div>
<h2 className="text-center">{props.book.title}</h2>
<p className="">{props.book.body}</p>
{console.log(props)}
</div>
);
};
const mapStateToProps = (state, props) => {
return {
book: state.books.find((book) => {
return book.id === props.match.params.id
})
}
};
export default connect(mapStateToProps)(BookShow);
I have not tested it though! Try it and let me know.
import React from 'react';
import {connect} from 'react-redux'
class BookShow extends React.Component{
constructor(props, context) {
super(props, context);
this.state = {
book: {}
}
}
componentWillMount(){
const { match: { params }, books } = this.props;
this.state.book = books.find((book) => {
return book.id === params.id
});
}
render(){
const { book } = this.props;
if(!props){
return <div>loading...</div>
}
return (
<div>
<h2 className="text-center">{book.title}</h2>
<p className="">{book.body}</p>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
books: state.books
}
};
export default connect(mapStateToProps)(BookShow);
BookShow is a stateless component, try to make it a class,
import React, { Component } from 'react';
export default class BookShow extends Component {
render() {
return (
<div>
your code...
</div>
);
}
}
import {withRouter} from 'react-router-dom';
export default withRouter(connect(mapStateToProps)(BookShow));
when you start from homePage and then navigate to some book you can use props.match.params.id but when refreshing page you can't. Try to use withRouter to see if it will fix your problem.
What's the best way of making sure that my react app has all the data needed when I am using react router? Basically I want to fetch some basic data that are used across whole application, the problem is that when I did it "the easy way" some of my data are fetched twice.
When I enter index route (Dashboard) it first mount this component and fire this.props.fetchAllProjects(), than it mount Loader component and fire this.props.fetchUsersInfo() so it shows just Spinner component and after user info data are fetched id again mount Dashboard and fire this.props.fetchAllProjects() is there any good way of doing this?
Here's my current code:
AppRouter.jsx
<Router history={browserHistory}>
<Route component={Loader}>
<Route path="/" component={MainLayout}>
<IndexRoute components={{ rightSidebar: RightSidebar, main: Dashboard }} />
<Route path="accounts" components={{ rightSidebar: RightSidebar, main: Accounts }} />
</Route>
</Route>
<Route path="*" component={PageNotFound} />
Loader.jsx
import React from 'react';
import { connect } from 'react-redux';
import { fetchUsersInfo } from 'actions/index.actions';
import Spinner from 'components/spinner/Spinner.component';
class Loader extends React.Component {
componentDidMount() {
this.props.fetchUsersInfo();
}
render() {
return (
<div>
{this.props.appState.isFetchingUsersInfo ?
<div>
<Spinner />
</div>
:
<div>
{this.props.children}
</div>
}
</div>
);
}
}
Loader.propTypes = {
children: React.PropTypes.node.isRequired,
appState: React.PropTypes.shape({
isFetchingUsersInfo: React.PropTypes.bool.isRequired,
}),
fetchUsersInfo: React.PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
appState: {
isFetchingUsersInfo: state.appState.isFetchingUsersInfo,
},
});
export default connect(mapStateToProps, { fetchUsersInfo })(Loader);
Dashboard.jsx
import React from 'react';
import { connect } from 'react-redux';
import { fetchAllProjects } from 'actions/index.actions';
import styles from './Dashboard.container.scss';
class Dashboard extends React.Component {
componentDidMount() {
this.props.fetchAllProjects();
}
render() {
return (
<div>
Dashboard
</div>
);
}
}
Dashboard.propTypes = {
appState: React.PropTypes.shape({
isFetchingProjects: React.PropTypes.bool.isRequired,
}),
fetchAllProjects: React.PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
appState: {
isFetchingProjects: state.appState.isFetchingProjects,
},
});
export default connect(mapStateToProps, { fetchAllProjects })(Dashboard);