I was looking at all previous answer but none managed to solve my issue. I'm new to react and currently working on a pet project, I'm using react with typescript, redux and material UI.
For some reason trying to redirect from inside a component changes the url but doesn't render
the matching component (i.e. it defaults to my 404 page although the actual path is under the same router).
My code is as follows -
On this page I'm trying to redirect the user to projects page in case he is already logged in - the history.push on the componentDidMount here "fails".
Login.tsx page :
import React from "react";
import { bindActionCreators, Dispatch } from "redux";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import Typography from "@material-ui/core/Typography";
import {
StyledComponentProps,
Theme,
withStyles,
} from "@material-ui/core/styles";
import Avatar from "@material-ui/core/Avatar";
import Button from "@material-ui/core/Button";
import CssBaseline from "@material-ui/core/CssBaseline";
import TextField from "@material-ui/core/TextField";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Checkbox from "@material-ui/core/Checkbox";
import Link from "@material-ui/core/Link";
import Grid from "@material-ui/core/Grid";
import LockOutlinedIcon from "@material-ui/icons/LockOutlined";
import Container from "@material-ui/core/Container";
import CenteredForm from "../../components/CenteredForm";
import {
isEmailValid,
isPasswordValid,
} from "../../core/validators/authValidators";
import { loginUser } from "./actions";
import { history } from "../history";
interface OwnProps {
email: string;
password: string;
}
interface StyledProps {
classes: StyledComponentProps & any;
}
interface DispatchProps {
loginUser: typeof loginUser;
}
type LoginProps = OwnProps & StyledProps & DispatchProps;
interface LoginState {
email: string;
password: string;
isEnabled: boolean;
}
const initialState: LoginState = {
email: "",
password: "",
isEnabled: false,
};
const styles = (theme: Theme) => ({
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(1),
marginBottom: theme.spacing(15),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
});
class Login extends React.Component<LoginProps, LoginState> {
constructor(props: LoginProps, state: LoginState) {
super(props);
this.state = initialState;
this.handleChange = this.handleChange.bind(this);
this.login = this.login.bind(this);
}
componentDidMount() {
if (localStorage.getItem("accessToken")) {
history.push("/projects");
}
}
// https://stackoverflow.com/questions/40676343/typescript-input-onchange-event-target-value
// https://stackoverflow.com/a/52030057/1091286
handleChange = (field: string) => (
event: React.ChangeEvent<HTMLInputElement>
) => {
event.preventDefault();
const newState = { ...this.state, [field]: event.target.value };
newState.isEnabled =
isEmailValid(newState.email) && isPasswordValid(newState.password);
this.setState({
...newState,
});
};
login = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
if (this.state.isEnabled) {
this.props.loginUser({
email: this.state.email,
password: this.state.password,
});
}
};
render() {
const { classes } = this.props;
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<CenteredForm>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form className={classes.form} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
onChange={this.handleChange("email")}
label="Email Address"
name="email"
autoComplete="email"
autoFocus
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
onChange={this.handleChange("password")}
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
onClick={this.login}
disabled={!this.state.isEnabled}
className={classes.submit}
>
Sign In
</Button>
<Grid container>
<Grid item xs>
<Link href="/reset-password" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="/register" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</CenteredForm>
</Container>
);
}
}
function mapStateToProps(state: LoginState) {
return {
email: state.email,
password: state.password,
isEnabled: state.isEnabled,
};
}
function mapDispatchToProps(dispatch: Dispatch) {
return bindActionCreators({ loginUser }, dispatch);
}
const connectedLoginPage = withRouter(
connect<LoginState, any, any, any>(
mapStateToProps,
mapDispatchToProps
// @ts-ignore
)(withStyles(styles)(Login))
);
export { connectedLoginPage as Login };
App.tsx page :
import React from "react";
import { Component } from "react";
import "./App.css";
import { Router } from "react-router";
import { Switch, Route } from "react-router-dom";
import {
isMobile,
} from "react-device-detect";
import { history } from "../history";
import Header from "../../components/Header";
import Footer from "../../components/Footer";
import Home from "../Home";
import Login from "../Login";
import Register from "../Register";
import ForgotPassword from "../ForgotPassword";
import Projects from "../Projects";
import NotFound from "../NotFound";
class App extends Component {
render() {
if (isMobile) {
return <div> This content is unavailable on mobile</div>;
} else {
return (
<div className="page-container">
<Header />
<div className="content-wrap">
<Router history={history}>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
<Route
exact
path="/reset-password"
component={ForgotPassword}
/>
<Route exact path="/projects" component={Projects} />
<Route component={NotFound} />
</Switch>
</div>
</Router>
</div>
<Footer />
</div>
);
}
}
}
export default App;
index.tsx page:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { ThemeProvider } from "@material-ui/core";
import "./index.css";
import theme from "./theme";
import store from "./store";
import App from "./features/App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<Provider store={store}>
<App />
</Provider>
</ThemeProvider>
</React.StrictMode>,
document.getElementById("root")
);
serviceWorker.unregister();
history.ts class :
import { createBrowserHistory } from "history";
export const history = createBrowserHistory();
Can someone tell me what am I missing out?