Not really sure how to move forward on this one. I have a (fairly) simple setup with react-router, almost everything works as expected - I get requests and responses between the app and my api - except I have a function I expect to run from my app's componentDidMount lifecycle function, but it's somehow not getting hit...
Here's a pretty minimal version of my code:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Switch } from 'react-router';
import { connect } from 'react-redux';
import { authenticate, unauthenticate } from './actions/session';
import RouteAuthenticated from './RouteAuthenticated'; // Route if authenticated
import RedirectAuthenticated from './RedirectAuthenticated'; // Redirect to login if not authenticated
import Home from './Home';
import Login from './Login';
import Signup from './Signup';
class App extends Component {
componentDidMount() {
const token = localStorage.getItem('token'); // eslint-disable-line no-undef
console.info(token); // <==== Shows a token, great!
console.info(this.props.authenticate.toString()); // Output below
if (token) {
console.info(`We have a token`); // <=== I get to here
this.props.authenticate(); // <=== Never gets run...????
} else {
this.props.unauthenticate();
}
}
render() {
const { isAuthenticated, willAuthenticate } = this.props;
const authProps = { isAuthenticated, willAuthenticate };
return (
<div className="App">
<Header />
<Switch>
<RouteAuthenticated exact path="/" component={Home} {...authProps} />
<RedirectAuthenticated exact path="/login" component={Login} {...authProps} />
<RedirectAuthenticated exact path="/signup" component={Signup} {...authProps} />
</Switch>
</div>
);
}
}
App.propTypes = {
authenticate: PropTypes.func.isRequired,
unauthenticate: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool.isRequired,
willAuthenticate: PropTypes.bool.isRequired
};
const mapStateToProps = (state) => ({
isAuthenticated: state.isAuthenticated,
willAuthenticate: state.willAuthenticate
});
const mapDispatchToProps = () => ({
authenticate,
unauthenticate
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
And since this seemed super weird, I decided to go ahead and just make sure that there actually was a function to run. I put in the console.info(this.props.authenticate.toString()); line above, and here's what I get, which looks fine except that it never actually runs:
function authenticate() {
return function (dispatch) {
console.info("auth function");
dispatch({
type: 'AUTHENTICATION_REQUEST'
});
try {
console.info("AUTHing");
setCurrentUser(dispatch, _api2.default.get('is-auth'));
} catch (e) {
console.info("AUTH ERROR");
localStorage.removeItem('token'); // eslint-disable-line no-undef
window.location = '/login'; // eslint-disable-line no-undef
}
};
}
And here is the original function:
export const authenticate = () => (dispatch) => {
console.info("auth function"); // <=== never called?
dispatch({
type: 'AUTHENTICATION_REQUEST'
});
try {
console.info("AUTHing");
setCurrentUser(dispatch, api.get('is-auth'));
} catch (e) {
console.info("AUTH ERROR");
localStorage.removeItem('token');
window.location = '/login';
}
};
And the store:
import { createStore, applyMiddleware } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import history from './history';
import reducers from './reducers';
const middleWare = [thunk, routerMiddleware(history)];
const createStoreWithMiddleware = applyMiddleware(...middleWare)(createStore);
const store = createStoreWithMiddleware(reducers);
export default store;
Any ideas what I can try from here?
Related
I am trying to use context with my Gatsby project. I have successfully implemented this in my previous project and I have copied the code over to my new project and it's not working as intended.
This is my context.js file:
import React, { useContext, useState } from "react";
const defaultState = {
isLoggedIn: false,
};
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [isLoggedIn, setIsLoggedIn] = useState(false);
function toggle() {
console.log("BOO!");
}
const value = {
isLoggedIn,
setIsLoggedIn,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
This is my app.js file:
import React from "react";
import { Router } from "#reach/router";
import IndexPage from "./index";
import ProjectPage from "./project";
import { AuthProvider } from "../contexts/context";
const App = () => (
<AuthProvider>
<Router basepath="/app">
<IndexPage path="/" component={IndexPage} />
<ProjectPage path="/project" component={ProjectPage} />
</Router>
</AuthProvider>
);
export default App;
This is my index.js file:
import React, { useContext } from "react";
import { Link } from "gatsby";
import { useAuth } from "../contexts/context";
import { AuthContext } from "../contexts/context";
const IndexPage = () => {
console.log(useAuth())
return (
<div className="w-40 h-40 bg-red-400">
{/*<Link to="/project">to projects</Link>*/}
<div>Click me to toggle: uh</div>
</div>
);
};
export default IndexPage;
useAuth() should return the desired components and functions but instead is always returning undefined. I have looked over my previous code as well as snippets on stack overflow and I can't seem to find the correct fix.
The following includes code that successfully built and executed:
Original context.js
import '#stripe/stripe-js'
/* Functionality */
import React, { useContext, useEffect, useState } from "react";
import { navigate } from "#reach/router";
import firebase from 'gatsby-plugin-firebase';
import { useLocalStorage } from 'react-use';
const AuthContext = React.createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [isLoggedIn, setIsLoggedIn] = useState(false)
const [isLoading, setIsLoading] = useLocalStorage("loading", false);
// Sign In
const signInWithRedirect = (source) => {
let provider;
switch(source) {
case 'Google':
provider = new firebase.auth.GoogleAuthProvider()
break;
case 'Github':
provider = new firebase.auth.GithubAuthProvider()
break;
default:
break;
}
setIsLoading(true)
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION)
.then(() => {
// Existing and future Auth states are now persisted in the current
// session only. Closing the window would clear any existing state even
// If a user forgets to sign out.
// ...
// New sign-in will be persisted with session persistence.
return firebase.auth().signInWithRedirect(provider)
})
.catch((error) => {
// Handle Errors here.
let errorCode = error.code;
let errorMessage = error.message;
});
}
// Sign Out
const signOut = () => {
firebase.auth().signOut().then(() => {
// Sign-out successful.
setIsLoggedIn(false)
navigate('/app/login')
}).catch((error) => {
// An error happened.
});
}
useEffect(() => {
firebase.auth().onAuthStateChanged((user) => {
try {
// If user is authenticated
if (!!user) {
// Fetch firestore document reference
var docRef = firebase.firestore().collection("study_guide_customers").doc(user.uid)
docRef.get().then((doc) => {
console.log('checking doc')
// If the document doesn't exist, create it and add to the firestore database
if (!doc.exists) {
console.log('inside customer')
const customer = {
customerCreationTimestamp: firebase.firestore.Timestamp.now(),
username: user.displayName,
email: user.email
}
firebase.firestore().collection("study_guide_customers").doc(user.uid).set(customer)
.then(() => {
// After docuement for user is created, set login status
setIsLoggedIn(!!user)
setIsLoading(false)
})
.catch((error) => {
console.error("Error writing document: ", error);
});
// If document for user exists, set login status
} else {
setIsLoggedIn(!!user)
setIsLoading(false)
}
})
}
} catch {
console.log('Error checking firestore existence and logging in...')
}
})
}, [isLoggedIn, isLoading, setIsLoading, setIsLoggedIn])
const value = {
signOut,
isLoggedIn,
isLoading,
setIsLoading,
setIsLoggedIn,
signInWithRedirect,
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
Original app.js
/* Stripe Security */
import '#stripe/stripe-js'
/* Functionality */
import React from "react"
import { Router } from "#reach/router"
import PrivateRoute from "../components/PrivateRoute"
import Profile from "../components/Profile"
import Login from "../components/Login"
import Projects from "../components/Projects"
import IndexPage from "./index"
import NotFoundPage from './404'
import { AuthProvider } from "../contexts/context"
const App = () => (
<AuthProvider>
<Router basepath="/app">
<PrivateRoute path="/profile" component={Profile} />
<Login path="/login" component={Login}/>
<IndexPage path="/" component={IndexPage}/>
<Projects path="/projects" component={Projects} />
</Router>
</AuthProvider>
)
export default App
Original index.js
/* Stripe Security */
import '#stripe/stripe-js'
/* Functionality */
import * as React from "react"
import IndexContact from "../components/Index/Contact"
import IndexSelectedProjects from "../components/Index/SelectedProjects"
import IndexFeaturedProjects from "../components/Index/FeaturedProjects"
import IndexFooter from "../components/Index/Footer"
import IndexStudyGuide from "../components/Index/StudyGuide"
import IndexNavbar from "../components/Index/Navbar"
import IndexHeader from "../components/Index/Header"
import IndexAbout from '../components/Index/About'
import IndexExperience from '../components/Index/Experience'
import { useMount } from 'react-use';
const IndexPage = () => {
useMount(() => localStorage.setItem('loading', false));
return (
<>
<IndexNavbar />
<IndexHeader />
<IndexAbout />
<IndexExperience />
<IndexFeaturedProjects />
<IndexSelectedProjects />
<IndexStudyGuide />
<IndexContact />
<IndexFooter />
</>
)
}
export default IndexPage
Then in any component I could simply use the following code to access the context
import { useAuth } from "../contexts/context"
const { isLoggedIn, signInWithRedirect, isLoading } = useAuth()
Child components are mounted before parent. Fix your context.js file to add a default value for isLoggedIn state:
const defaultState = {
isLoggedIn: false,
setIsLoggedIn: () => {}
};
const AuthContext = React.createContext(defaultState);
Your defaultState should also include default methods for any parts of the context you wish to work with.
When a user is authenticated, I can refresh the page and access all the routes through the website or by entering/refreshing the URL. However, when the user is unauthenticated, although the routing through the website works well, refreshing the URL even on plain route pages (non-private) redirects me to index (main page).
I have used this as the template of my react app and this for adding authentication to my react app. I have tried these two guides seperately and both work well but somehow in my react app their combination leads to the issue I described above.
Below is the code for my app routes:
import React, { Component, lazy, Suspense} from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import Spinner from '../app/shared/Spinner';
const MainIndex = lazy(() => import( "./mainpage/Index"));
const TermsIndex = lazy(() => import( "./mainpage/Terms"));
const Dashboardmain = lazy(() => import( "./dashboard/Dashboardmain"));
const Login = lazy(() => import( "./user-pages/Login"));
const Register = lazy(() => import( "./user-pages/Register"));
class AppRoutes extends Component {
render () {
return (
<Suspense fallback={<Spinner/>}>
<Switch>
<Route exact path="/login" component={ Login } />
<Route exact path="/index" component={ MainIndex } />
<Route exact path="/terms" component={ TermsIndex } />
<PrivateRoute exact path="/internal/dashboard" component={ Dashboardmain } />
<Route exact path="/register" component={ Register } />
</Switch>
</Suspense>
);
}
}
export default AppRoutes;
My app pages look like this - the FullPageLayout is to check if header, footer or sidebar should be added for the page or not:
import React, { Component } from 'react';
import AppRoutes from './AppRoutes';
import Navbar from './shared/Navbar';
import Sidebar from './shared/Sidebar';
import Footer from './shared/Footer';
import { withRouter } from 'react-router-dom';
class AppPages extends Component {
state = {}
componentDidMount() {
this.onRouteChanged();
}
render () {
let navbarComponent = !this.state.isFullPageLayout ? <Navbar/> : '';
let sidebarComponent = !this.state.isFullPageLayout ? <Sidebar/> : '';
let footerComponent = !this.state.isFullPageLayout ? <Footer/> : '';
return (
<div className="container-scroller">
{ sidebarComponent }
<div className="container-fluid page-body-wrapper">
{ navbarComponent }
<div className="main-panel">
<div className="content-wrapper">
<AppRoutes/>
</div>
{ footerComponent }
</div>
</div>
</div>
);
}
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
this.onRouteChanged();
}
}
onRouteChanged() {
const containment = [];
const fullPageLayoutRoutes = ['/login', '/register',
'/index', '/terms'];
const body = document.querySelector('body');
body.classList.remove('rtl')
for (var i = 0; i < fullPageLayoutRoutes.length; i++) {
containment[containment.length] = this.props.location.pathname.toLowerCase().includes(fullPageLayoutRoutes[i])
}
if (containment.includes(true)) {
this.setState({isFullPageLayout: true})
document.querySelector('.page-body-wrapper').classList.add('full-page-wrapper');
} else {
this.setState({isFullPageLayout: false})
document.querySelector('.page-body-wrapper').classList.remove('full-page-wrapper');
}
}
}
export default withRouter(AppPages);
And my app is as follows:
import React, { Component } from 'react';
import { BrowserRouter } from 'react-router-dom';
import './App.scss';
import { Provider } from "react-redux";
import jwt_decode from "jwt-decode";
import setAuthToken from "../utils/setAuthToken";
import { setCurrentUser, logoutUser } from "../actions/authActions";
import store from "../store";
import AppPages from './AppPages';
// Check for token to keep user logged in
if (localStorage.jwtToken) {
// Set auth token header auth
const token = localStorage.jwtToken;
setAuthToken(token);
// Decode token and get user info and exp
const decoded = jwt_decode(token);
// Set user and isAuthenticated
store.dispatch(setCurrentUser(decoded));
// Check for expired token
const currentTime = Date.now() / 1000; // to get in milliseconds
if (decoded.exp < currentTime) {
// Logout user
store.dispatch(logoutUser());
// Redirect to login
window.location.href = "/login";
}
}
class App extends Component {
render () {
return (
<Provider store={store}>
<BrowserRouter>
<AppPages/>
</BrowserRouter>
</Provider>
);
}
}
export default App;
I tried debugging the issue by changing/simplifying things in the router or app part but I don't think the problem is here. I think the problem is somewhere within the reducers or actions.
My store is defined as follows:
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
(window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__()) ||
compose
)
);
export default store;
My authReducer is defined as:
import { SET_CURRENT_USER, USER_LOADING } from "../actions/types";
const isEmpty = require("is-empty");
const initialState = {
isAuthenticated: false,
user: {},
loading: false
};
export default function(state = initialState, action) {
switch (action.type) {
case SET_CURRENT_USER:
return {
...state,
isAuthenticated: !isEmpty(action.payload),
user: action.payload
};
case USER_LOADING:
return {
...state,
loading: true
};
default:
return state;
}
}
My authAction is defined as:
import axios from "axios";
import setAuthToken from "../utils/setAuthToken";
import jwt_decode from "jwt-decode";
import { GET_ERRORS, SET_CURRENT_USER, USER_LOADING } from "./types";
// Register User
export const registerUser = (userData, history) => dispatch => {
axios
.post("/api/users/register", userData)
.then(res => history.push("/login")) // re-direct to login on successful register
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};
// Login - get user token
export const loginUser = userData => dispatch => {
axios
.post("/api/users/login", userData)
.then(res => {
// Save to localStorage
// Set token to localStorage
const { token } = res.data;
localStorage.setItem("jwtToken", token);
// Set token to Auth header
setAuthToken(token);
// Decode token to get user data
const decoded = jwt_decode(token);
// Set current user
dispatch(setCurrentUser(decoded));
})
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};
// Set logged in user
export const setCurrentUser = decoded => {
return {
type: SET_CURRENT_USER,
payload: decoded
};
};
// User loading
export const setUserLoading = () => {
return {
type: USER_LOADING
};
};
// Log user out
export const logoutUser = () => dispatch => {
// Remove token from local storage
localStorage.removeItem("jwtToken");
// Remove auth header for future requests
setAuthToken(false);
// Set current user to empty object {} which will set isAuthenticated to false
dispatch(setCurrentUser({}));
};
It's something to do with the order of execution. The redux state gets erased when you refresh so you need the store.dispatch(setCurrentUser(decoded)) to get called before the PrivateRoute tries to render. It's not obvious to me precisely where it's going wrong because the if (localStorage.jwtToken) { block is not async, though the dispatch might be?
I would recommend setting the initialState in authReducer to isAuthenticated: null and update to either true or false once you've examined the token. Right now your PrivateRoute only knows two states: authenticated and unauthenticated. We need it to understand a third which is "I don't know yet". In your PrivateRoute you would render nothing or a loading spinner while isAutheticated is null. Don't render the Redirect until you have a definite false.
So after some debugging, I realised that my problem is coming from AppPages and the line with { navbarComponent }. This navbar is used with the PrivateRoute and requires the user to be defined. When the user is unauthenticated, although this will not technically render for plain routes, it is still processed and inside this navbar is a condition that if the user is undefined, will return to the index page.
I'm using Firebase for user authentication and I want to use the onAuthStateChanged() to make a user persist even after refreshing the browser. I'm also using redux-sagas to handle async operations.
Index.jsx file:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './index.css';
import './App.scss';
import store from './store';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
serviceWorker.unregister();
App.jsx:
import React, { Component } from 'react';
import { Route, BrowserRouter as Router, Switch } from 'react-router-dom';
import { connect } from 'react-redux';
import Navbar from './components/navbar';
import routes from './routes';
import { actionTypes } from './components/signin/actionTypes';
const { VERIFY_REQUEST } = actionTypes;
const mapDispatchToProps = {
VERIFY_REQUEST,
};
class App extends Component {
render() {
return (
<Router>
<Navbar />
<Switch>
{routes.map(route => (
<Route
key={route.path}
path={route.path}
exact={route.exact}
component={route.component}
/>
))}
</Switch>
</Router>
);
}
}
export default connect(null, mapDispatchToProps)(App);
My sagas generator function binded to the action type:
function onAuthState() {
return new Promise((resolve, reject) => {
loginToFirebase.auth().onAuthStateChanged(user => {
if (user) {
console.log(user);
resolve(user);
} else {
reject(new Error('Ops!'));
}
});
});
}
function* verifyUserAuth() {
try {
const LOGIN_API_URL = process.env.REACT_APP_USER_AUTH_API;
const { user } = yield onAuthState();
console.log(user);
const userInfo = { userAuth: user, userType: 'user' };
const config = { headers: { 'Content-Type': 'application/json' } };
const body = JSON.stringify(userInfo);
const response = yield axios.post(LOGIN_API_URL, body, config);
if (response.status === 200) {
const { data: { info } } = response.data;
yield put({ payload: info, type: VERIFY_SUCCESS });
} else yield put(loginError(response.status));
} catch (error) {
yield put(loginError(error));
}
}
export default function* watchUserLoginAction() {
yield takeEvery(VERIFY_REQUEST, verifyUserAuth);
}
Everytime I check my redux tools, I don't see the action being fired on component mount.
You can use the componentDidMount lifecycle method whenever you want to do something after the component is mounted. Modify your mapDispatchToProps
const mapDispatchToProps = dispatch => {
return {
verifyRequest: () => { dispatch( {type : VERIFY_REQUEST} ) }
};
};
and then call verifyRequest from componentDidMount
componentDidMount = () =>{
this.props.verifyRequest()
}
Also, it is better to create action creators instead of directly dispatching the action, like so
export const verifyRequestAction = () => {
return {
type: VERIFY_REQUEST
}
}
and then
const mapDispatchToProps = dispatch => {
return {
verifyRequest: () => { dispatch(verifyRequestAction()}
};
};
I have a state that is getting the right object from my action but It won't seem to actually append to the state. Does anyone have any experience working with reducers in React-Redux that can lend a hand? I'm not sure why I can't return the new state.
Here is the code in progress I have at the moment.
import * as types from '../constants'
const defaultState = {}
const userReducer = (state = defaultState, action) =>{
switch(action.type){
case types.USER_LOGGED_IN:
console.log("in the logged in reducer")
console.log(action.cookie)
return {
...state,
cookie: action.cookie
}
case types.USER_LOGGED_OUT:
return state
default:
return state
}
}
export default userReducer
The console.log will actually print out the correct cookie value for me. Any ideas or help would be greatly appreciated.
per Request here is the container,
import React, { Component, PropTypes } from 'react'
import { routerActions } from 'react-router-redux'
import { connect } from 'react-redux'
import GoogleLogin from '../components/GoogleLogin'
import actions from '../actions/'
import cookie from 'react-cookie'
const login = actions.userActions.login
function select(state, ownProps) {
const isAuthenticated = state.user.cookie || false
console.log(isAuthenticated)
const redirect = ownProps.location.query.redirect || '/'
return {
isAuthenticated,
redirect
}
}
class LoginContainer extends Component {
componentWillMount() {
const { isAuthenticated, replace, redirect } = this.props
if (isAuthenticated) {
replace(redirect)
}
}
componentWillReceiveProps(nextProps) {
const { isAuthenticated, replace, redirect } = nextProps
const { isAuthenticated: wasAuthenticated } = this.props
if (!wasAuthenticated && isAuthenticated) {
replace(redirect)
}
}
onClick(e){
e.preventDefault()
//console.log("in the onClick")
var status = cookie.load("MarketingStatsApp",false)
if(!(status == undefined)){
const login = actions.userActions.login
this.props.login({
cookie: status
})
}
window.location.href='auth/google'
};
render() {
return (
<div>
<h1>Login using Google</h1>
<GoogleLogin onClick={this.onClick.bind(this)}/>
</div>
)
}
}
export default connect(select, { login, replace: routerActions.replace })(LoginContainer)
Here you can see the false get printed out the first time through and then the cookie get printed out. You can even see the action but I can't click into the arrows for that action and the the other ones don't display the updated state as how I expect it to be
Thanks for the help everyone
The problem ended up being on my double verification on my router page. I needed to re-bring in the action variable for it to launch properly. Here is the code for that
import { Router, Route, browserHistory,IndexRedirect } from 'react-router'
import React from 'react';
import Layout from './components/Layout'
import Home from './containers/Home'
import Login from './containers/LoginContainer'
import Dashboard from './containers/Dashboard'
import { UserIsAuthenticated} from './util/wrapper'
import cookie from 'react-cookie'
import axios from 'axios'
import actions from './actions'
import store from './reducers';
const Authenticated = UserIsAuthenticated((props) => props.children)
function doubleCheckGUID(guid){
return axios.post("/auth/guid",{guid: guid}).then(response => {
const login = actions.userActions.login
store.dispatch(login({
cookie: cookie.load("MarketingStatsApp",false)
}))
return true;
}).catch(error => {
return false;
})
}
if(cookie.load("MarketingStatsApp",false)){
doubleCheckGUID(cookie.load("MarketingStatsApp",false))
}
const AppRouter = () =>
<Router history={browserHistory} >
<Route path="/" component={Layout}>
<IndexRedirect to="/home" />
<Route path="home" component={Home} />
<Route path="login" component={Login}/>
<Route component={Authenticated} >
<Route path="dash" component={Dashboard} />
</Route>
</Route>
</Router>
export default AppRouter
I am trying to implement a simple form logic for educational purposes. I am stuck trying to redirect to url on form submission. Here are relevant sections of my code;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, hashHistory } from 'react-router'
import { syncHistoryWithStore, routerReducer, routerMiddleware } from 'react-router-redux'
import { reducer as formReducer } from 'redux-form'
import {Home, Foo, Bar} from './components'
import {YirtibatLoginForm as LoginForm} from './containers/LoginForm'
import * as reducers from './reducers'
const reducer = combineReducers({
...reducers,
routing: routerReducer,
form: formReducer
})
const middleware = routerMiddleware(hashHistory)
const store = createStore(reducer, applyMiddleware(middleware))
const history = syncHistoryWithStore(hashHistory, store)
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="foo" component={Foo} />
<Route path="bar" component={Bar} />
<Route path="login" component={LoginForm} />
</Route>
</Router>
</Provider>,
document.getElementById('root')
);
containers/LoginForm.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { push } from 'react-router'
import LoginForm from '../components/LoginForm'
export class BaseYirtibatLoginForm extends Component {
constructor() {
super();
this.handlesubmit = this.handlesubmit.bind(this);
}
handlesubmit(ev) {
this.props.submitting();
fetch('/login', {
method:'POST',
body:JSON.stringify(ev)
}).then(resp => {
if(!resp.ok) {
throw new Error(resp.statusText)
}
return resp.json()
}).then( resjson => {
this.props.submitsuccess(resjson)
}).catch(err => {
this.props.submiterror(err);
})
}
render() {
return (
<LoginForm onSubmit={this.handlesubmit} />
);
}
}
const mapStateToProps = (state) => {return {}}
const mapDispatchToProps = (dispatch) => {
return {
submitting: () => dispatch({type:'submitting'}),
submitsuccess: (data) => push("/success"),
submiterror: (err) => push("/error")
}
}
export const YirtibatLoginForm = connect(mapStateToProps, mapDispatchToProps)(BaseYirtibatLoginForm);
I think this code supposed to redirect hash url after the form has been submitted. However I am getting following error in browser console;
Uncaught (in promise) TypeError: (0 , _reactRouter.push) is not a function
at Object.submiterror (LoginForm.js:45)
at LoginForm.js:29
submiterror # LoginForm.js:45
(anonymous) # LoginForm.js:29
What is the prefered method to redirect to a route component after for submission events?
There is no push function exported by react-router. You could work with the history object directly, as mentioned in the comments, but the best way is to use the withRouter higher-order component. The code below touches the key points with inline comments.
// import
import { withRouter } from 'react-router'
...
export class BaseYirtibatLoginForm extends Component {
...
handlesubmit(ev) {
this.props.submitting();
fetch('/login', ...
).then( resjson => {
// take `router` from `this.props` and push new location
this.props.router.push("/success")
}).catch(err => {
// take `router` from `this.props` and push new location
this.props.router.push("/error")
})
}
}
const mapStateToProps = (state) => {return {}}
const mapDispatchToProps = (dispatch) => {
return {
submitting: () => dispatch({type:'submitting'}),
// redirect is not done through redux actions
}
}
// apply withRouter HoC
export const YirtibatLoginForm = withRouter(connect(mapStateToProps, mapDispatchToProps)(BaseYirtibatLoginForm));