React Component not updating after adding new value in store - reactjs

So I am using react, redux and firebase for this small crud app, whenever a new employee is created I redirect to the home component which should display all the employees created including the new one. But the new employee isn't showing up after redirection from create employee. What seems to be the issue, essentially I want is for the Home component to update with the new data.
Home.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import EmployeesList from '../employees/EmployeesList'
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'
class Home extends Component {
render() {
const { employees } = this.props
return (
<div>
<EmployeesList employees={employees} />
</div>
)
}
}
const mapStateToProps = (state) => {
// console.log(state)
return ({
employees: state.firestore.ordered.employees
})
}
export default compose(
connect(mapStateToProps),
firestoreConnect([
{ collection: 'employees', orderBy: ['createdAt', 'desc'] }
])
)(Home)
CreateEmployee.js
import React, { Component } from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { withRouter } from "react-router";
import { createEmployee } from '../../store/actions/employeeActions'
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import Typography from '#material-ui/core/Typography';
const styles = theme => ({
bt_create: {
margin: theme.spacing.unit,
padding: '10'
},
input: {
display: 'none',
},
});
class CreateEmployee extends Component {
state = {
name: '',
email: '',
department: '',
salary: ''
}
handleChange = e => {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = e => {
e.preventDefault()
// console.log(this.state)
// TODO store state data in db
this.props.createEmployee(this.state)
this.props.history.push({
pathname: '/'
})
}
render() {
return (
<div>
<br />
<Typography variant="h6" color="inherit">
Create new employee
</Typography>
<form onSubmit={this.handleSubmit}>
<TextField
id="name"
label="Name"
defaultValue=""
margin="normal"
onChange={this.handleChange}
/>
<br />
<TextField
id="email"
label="Email"
defaultValue=""
margin="normal"
onChange={this.handleChange}
/>
<br />
<TextField
id="department"
label="Department"
defaultValue=""
margin="normal"
onChange={this.handleChange}
/>
<br />
<TextField
id="salary"
label="Salary"
defaultValue=""
margin="normal"
onChange={this.handleChange}
/>
<br />
<br />
<Button type="submit" variant="contained" color="primary" className="bt_create">Create</Button>
</form>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
createEmployee: (employee) => dispatch(createEmployee(employee))
}
}
export default compose(
withStyles(styles),
withRouter,
connect(null, mapDispatchToProps)
)(CreateEmployee)
Create employee action
export const createEmployee = employee => {
return (dispatch, getState, { getFirebase, getFirestore }) => {
const firestore = getFirestore()
// TODO add employee here
firestore.collection('employees').add({
...employee,
createdAt: new Date(),
updatedAt: new Date()
}).then(() => {
dispatch({
type: 'CREATE_EMPLOYEE_SUCCESS',
employee: employee
})
}).catch((err) => {
dispatch({ type: 'CREATE_EMPLOYEE_ERROR', err })
})
}

Related

Textarea input fields in Chakra UI wont submit to react hook form

I am using nextjs (v13), react (v18) chakraUI and react hook form.
If I use Inputs (only), I can submit this form. If I change the description field to be a Textarea (from ChakraUI), the form displays on the page, but will not submit. I get no errors in the console - I can't see what's causing the issue.
Is it possible to submit data from a Textarea via react-hook-form?
import * as React from "react"
import { gql } from "#apollo/client"
import { Button, Stack, Textarea, Text } from "#chakra-ui/react"
import { useRouter } from "next/router"
import { useCreateIssueGroupMutation } from "lib/graphql"
import { useForm } from "lib/hooks/useForm"
import Yup from "lib/yup"
import { ButtonGroup } from "./ButtonGroup"
import { Form } from "./Form"
import { FormError } from "./FormError"
import { Input } from "./Input"
import { Modal } from "antd"
const _ = gql`
mutation CreateIssueGroup($data: IssueGroupInput!) {
createIssueGroup(data: $data) {
id
}
}
`
interface Props {
onClose: () => void
}
const IssueGroupSchema = Yup.object().shape({
title: Yup.string().required(),
description: Yup.string().required(),
})
export function AdminCreateIssueGroupForm(props: Props) {
const router = useRouter()
const [createIssueGroup] = useCreateIssueGroupMutation()
const defaultValues = {
title: "",
description: "",
}
const form = useForm({ defaultValues, schema: IssueGroupSchema })
const handleSubmit = (data: Yup.InferType<typeof IssueGroupSchema>) => {
return form.handler(() => createIssueGroup({ variables: { data: { ...data } } }), {
onSuccess: (res, toast) => {
toast({ description: "Issue group created" })
form.reset()
props.onClose()
},
})
}
return (
<Form {...form} onSubmit={handleSubmit}>
<Stack>
<Input name="title" label="Title" />
// this input works and allows me to submit the form
{/* <Input name="description" label="Description" /> */}
// the next 2 lines do not work. The page renders but the form does not submit
<Text mb='8px' fontWeight="medium" fontSize="sm" > Description</Text>
<Textarea name="description" rows={4} />
<FormError />
<ButtonGroup>
<Button onClick={props.onClose}>Cancel</Button>
<Button
type="submit"
isLoading={form.formState.isSubmitting}
isDisabled={form.formState.isSubmitting}
color="brand.white"
fontWeight="normal"
backgroundColor="brand.orange"
_hover={{
backgroundColor: "brand.green",
color: "brand.white",
}}
>
Create
</Button>
</ButtonGroup>
</Stack>
</Form>
)
}
My Form component has:
import * as React from "react"
import type { FieldValues, UseFormReturn } from "react-hook-form"
import { FormProvider, useFormContext } from "react-hook-form"
import { Box } from "#chakra-ui/react"
import * as Sentry from "#sentry/nextjs"
import { useToast } from "lib/hooks/useToast"
interface FormContainerProps {
onSubmit?: (values: any) => Promise<any> | any
onBlur?: (values: any) => Promise<any> | any
}
const FormContainer: React.FC<FormContainerProps> = (props) => {
const toast = useToast()
const { handleSubmit } = useFormContext()
const onSubmit = async (values: any) => {
try {
if (props.onBlur) {
return await props.onBlur(values)
}
if (props.onSubmit) {
return await props.onSubmit(values)
}
} catch (e) {
console.log(e)
Sentry.captureException(e)
toast({
title: "Application error",
description: "Something went wrong. We have been notified!",
status: "error",
})
return
}
}
return (
<Box
as="form"
w="100%"
{...(props.onSubmit && { onSubmit: handleSubmit(onSubmit) })}
{...(props.onBlur && { onBlur: handleSubmit(onSubmit) })}
>
{props.children}
</Box>
)
}
interface Props<T extends FieldValues> extends UseFormReturn<T>, FormContainerProps {
children: React.ReactNode
isDisabled?: boolean
}
export function Form<T extends FieldValues>({ onSubmit, onBlur, isDisabled, ...props }: Props<T>) {
return (
<FormProvider {...props}>
<fieldset disabled={isDisabled}>
<FormContainer {...{ onSubmit, onBlur }}>{props.children}</FormContainer>
</fieldset>
</FormProvider>
)
}
Input has:
import * as React from "react"
import { useFormContext } from "react-hook-form"
import type { InputProps } from "#chakra-ui/react";
import { FormControl, Input as CInput } from "#chakra-ui/react"
import { InputError } from "./InputError"
import { InputLabel } from "./InputLabel"
interface Props extends InputProps {
name: string
label?: string
subLabel?: string
}
export const Input = ({ label, subLabel, ...props }: Props) => {
const {
register,
formState: { errors },
} = useFormContext()
const fieldError = errors?.[props.name]
return (
<FormControl isInvalid={!!fieldError} isRequired={props.isRequired}>
<InputLabel label={label} subLabel={subLabel} name={props.name} />
<CInput {...register(props.name)} mb={0} {...props} />
<InputError error={fieldError} />
</FormControl>
)
}
Each form component connected to React Hook Form needs to receive a register or be wrapped by a Controller component. Your input component receives this by useFormContext as you mentioned:
<CInput {...register(props.name)} mb={0} {...props} />
However, TextArea component doesn't receive anything from Hook Form, in that case, you need to use the same register('').
An example of this implementation (live on CodeSandbox):
function App() {
const { register, handleSubmit } = useForm({
defaultValues: {
title: "",
description: ""
}
});
return (
<>
<form onSubmit={handleSubmit((data) => console.log(data))}>
<Heading>Welcome to Chakra + TS</Heading>
<p>Title</p>
<Input {...register("title")} />
<p>Description</p>
<Textarea {...register("description")} />
<Button type="submit">Submit</Button>
</form>
</>
);
}
Useful links:
Register
Controller
Live Example

Getting the error of cloning for react typescript and react router dom at props.history.push

Without passing state to the props.history.push, its work very well but for data with state its give error.DOMException: Failed to execute 'pushState' on 'History': function transformRequest(data, headers) {normalizeHeaderName(headers, 'Accept');normalizeHeaderName(...... } could not be cloned.at globalHistory.pushState. I am getting the following error: history.js:357 Uncaught (in promise)
My Code is;
import axios from 'axios';
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import Button from '#mui/material/Button';
import TextField from '#mui/material/TextField';
import InputLabel from '#mui/material/InputLabel';
import MenuItem from '#mui/material/MenuItem';
import Box from '#mui/material/Box';
import FormGroup from '#mui/material/FormGroup';
import Select from '#mui/material/Select';
//import { History } from 'history';
/* interface ChildComponentProps {
history : History
} */
/* interface HomeProps {
history: RouteComponentProps["history"];
location: RouteComponentProps['location'];
match: RouteComponentProps['match'];
} */
interface WeatherDataCredentials {
StationName?: string,
StartDay?: string,
EndDay?: string
}
class SearchData extends React.Component<RouteComponentProps, WeatherDataCredentials> {
constructor(props: RouteComponentProps) {
super(props)
this.state = {
StationName: '',
StartDay: '',
EndDay: ''
}
}
onButtonClick = async (event: React.FormEvent) => {
event.preventDefault();
await axios.post('http://localhost:4000/api/weatherData',
{
StationName: this.state.StationName,
StartDay: this.state.StartDay,
EndDay: this.state.EndDay
})
.then(data => {
console.log('Props', this.props)
this.props.history.push({
pathname: '/data',
state:
{
data:data
}
});
}
)
}
render() {
return (
<Box
component="form"
sx={{
'& .MuiTextField-root': { m: 1, width: '25ch' },
}}
noValidate
autoComplete="off"
>
<div>
<h3>Weather Data Search</h3>
<FormGroup >
<InputLabel id="demo-simple-select-label">Station Name</InputLabel>
<Select
value={this.state.StationName}
label="Station Name"
onChange={(event) => this.setState({
StationName: event.target.value
})}
>
<MenuItem value="Buche">Buche</MenuItem>
{/* <MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem> */}
</Select>
{/* <TextField
required
label="StationName"
type="text"
value={this.state.StationName}
onChange={(event) => this.setState({
StationName: event.target.value
})}
/> */}
<TextField
required
label="StartDay"
type="text"
value={this.state.StartDay}
onChange={(event) => this.setState({
StartDay: event.target.value
})}
/>
<TextField
required
label="StartDay"
type="text"
value={this.state.EndDay}
onChange={(event) => this.setState({
EndDay: event.target.value
})}
/>
<Button onClick={this.onButtonClick} variant='contained' color='primary'>Search
Data</Button>
</FormGroup>
</div>
</Box>
)
}
}
export default withRouter(SearchData)
When using router state, that state needs to be clonable, but the axios response object (what you call data, though that's a misleading variable name) is not clonable, i'd guess because it contains functions.
I recommend you pick the values you need and create the state with those, not the entire response object. For example if you just need the response data:
onButtonClick = async (event: React.FormEvent) => {
event.preventDefault();
const response = await axios.post("http://localhost:4000/api/weatherData", {
StationName: this.state.StationName,
StartDay: this.state.StartDay,
EndDay: this.state.EndDay,
});
this.props.history.push({
pathname: "/data",
state: {
data: response.data,
},
});
};

how to do testing in react redux application using Jest enzyme?

Login.test.js:-
import { shallow, mount } from "enzyme";
import renderer from "react-test-renderer";
import Login from "../Login";
import { Provider } from "react-redux";
//import LoginReducer from "../../../redux/reducers/loginReducer";
import configureStore from "redux-mock-store";
const mockStore = configureStore([]);
describe("Login Component", () => {
let store;
let jsx;
beforeEach(() => {
store = mockStore({ login: { email: "", password: "" } });
jsx = (
<Provider store={store}>
<Login />
</Provider>
);
});
it("should render an email input tag", () => {
const wrapper = shallow(jsx);
expect(wrapper.find("Field[type='email']").exists()).toBe(true);
});
it("should render a password input tag", () => {
const wrapper = shallow(jsx);
expect(wrapper.find('Field[type="password"]').exists()).toBe(true);
});
it("should render a submit button", () => {
const wrapper = shallow(jsx);
expect(wrapper.find('button[type="submit"]').exists()).toBe(true);
});
});
Appstore:-
/** combine reducers*/
let rootReducer = combineReducers({
register: RegisterReducer,
login: LoginReducer
Login.js:-
import { React, useState, useEffect, useContext } from "react";
import { useHistory } from "react-router";
import "./common_style.css";
import { connect } from "react-redux";
import * as actionCreator from "../../redux/actions/userActionCreater";
import "../common/common_style.css";
import {
Grid,
Paper,
Avatar,
TextField,
Button,
Typography,
Link,
} from "#material-ui/core";
import LockOutlinedIcon from "#material-ui/icons/LockOutlined";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Checkbox from "#material-ui/core/Checkbox";
import InputAdornment from "#material-ui/core/InputAdornment";
import AccountCircle from "#material-ui/icons/AccountCircle";
import LockIcon from "#material-ui/icons/Lock";
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
//import { propTypes } from "react-bootstrap/esm/Image";
import InitializeReduxState from "./InitializeReduxState";
const paperStyle = {
padding: 20,
height: "70vh",
width: 280,
margin: "60px auto",
marginTop: "110px",
};
const avatarStyle = { backgroundColor: "#1bbd7e" };
const btnstyle = { margin: "8px 0" };
const Login = (props) => {
const initialValues = {
email: "",
password: "",
};
const validationSchema = Yup.object().shape({
email: Yup.string().email("Please enter valid email").required("Required"),
password: Yup.string("Enter your password")
.required("Required")
.min(4, "Password should be of minimum 4 characters length"),
});
const onSubmit = (values) => {
const payload = { email: values.email, password: values.password };
props.login(payload);
};
let history = useHistory();
useEffect(() => {
if (props.isLoggedIn === true) {
props.flashNotification({
message: "Login Succeessful...",
type: "success",
});
if (props.role === "admin") {
history.push("/admin");
} else if (props.role === "patient") {
patientStatus();
} else {
history.push("/physician");
}
}
}, []);
const patientStatus = () => {
if (props.currentUser.isActive) {
history.push("/demographics");
} else {
history.push("/patientinactive");
}
};
return (
<>
<Grid>
<Paper elevation={10} style={paperStyle}>
<Grid align="center">
<Avatar style={avatarStyle}>
<LockOutlinedIcon />
</Avatar>
<br />
<h4>Sign In</h4>
</Grid>
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={validationSchema}
>
{(props) => (
<Form>
<Field
as={TextField}
label="Email"
margin="normal"
type="text"
name="email"
// onChange={handleUserChange}
placeholder="Enter email"
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
),
}}
variant="standard"
helperText={<ErrorMessage name="email" />}
/>
<Field
as={TextField}
label="password"
placeholder="Enter password"
type="password"
name="password"
// onChange={handleUserChange}
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<LockIcon />
</InputAdornment>
),
}}
helperText={<ErrorMessage name="password" />}
/>
<Button
type="submit"
color="primary"
variant="contained"
style={btnstyle}
fullWidth
>
Sign in
</Button>
</Form>
)}
</Formik>
<Typography> Do you have an account ?</Typography>
Sign Up
<p className="text text-danger fw-bold text-center">
{props.globalmessage}!!!
</p>
</Paper>
</Grid>
<InitializeReduxState />
</>
);
};
const mapStatetoProps = (state) => {
return {
isLoggedIn: state.login.isLoggedIn,
role: state.login.role,
globalmessage: state.login.globalmessage,
authToken: state.login.authToken,
currentUser: state.login.loggedUserInfo,
};
};
const mapDispatchToProps = (dispatch) => {
return {
login: (user) => dispatch(actionCreator.Login(user)),
};
};
let hof = connect(mapStatetoProps, mapDispatchToProps);
export default hof(Login);
Here I have tested my Login component with some simple test cases and I have also mock my store because my application is using Redux but it is giving me error like
expect(received).toBe(expected) // Object.is equality
Expected: true
Received: false
I have a doubt that in beforeeach I have used the email and password. Is it correct? Is there is something that I have done wrong?

Dispatch redux not executed

I'm trying to submit a form when a user signUp. When the submit button clicked an action creator should executed to start an asynchronous action but actually the submit is not triggered and the action creator is not launched.
actions.ts:
import { ActionTypes } from "./types";
import { SignUpUser, User } from "../apis/authentication";
import { AxiosError } from "axios";
import { Dispatch } from "redux";
export interface ReturnedUser {
username: string;
}
export interface SignUpSuccessAction {
type: ActionTypes.SucceedSignUp;
payload: ReturnedUser;
}
export interface SignUpFailAction {
type: ActionTypes.FailSignUp;
payload: string;
}
export interface SignUpStartAction {
type: ActionTypes.StartSignUp;
}
const signUpStarted = (): SignUpStartAction => ({
type: ActionTypes.StartSignUp
});
const signUpSucceeded = (user: ReturnedUser): SignUpSuccessAction => ({
type: ActionTypes.SucceedSignUp,
payload: user
});
const signUpFailed = (error: string): SignUpFailAction => ({
type: ActionTypes.FailSignUp,
payload: error
});
export const signUpFetch = (user: User) => {
return async (dispatch: Dispatch) => {
dispatch(signUpStarted());
SignUpUser(user).then(
(response: any) => {
const { username } = response;
return dispatch(signUpSucceeded(username));
},
(error: AxiosError) => {
let errorMessage = "Internal Server Error";
if (error.response) {
errorMessage = error.response.data;
}
return dispatch(signUpFailed(errorMessage));
}
);
};
};
reducers/reducer.ts:
import { Action, ActionTypes } from "../actions";
export const SignUpReducer = (state = {}, action: Action) => {
switch (action.type) {
case ActionTypes.SucceedSignUp:
return { ...state, user: action.payload };
case ActionTypes.FailSignUp:
return { ...state, error: action.payload };
default:
return state;
}
};
reducers/index.ts:
import { SignUpReducer } from "./signUp";
import { combineReducers } from "redux";
export const reducer = combineReducers({
signUp: SignUpReducer
});
index.tsx:
import React from "react";
import ReactDOM from "react-dom";
import SignUp from "./containers/Signup/SignUp";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import { reducer } from "./reducers/index";
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));
const App = () => <SignUp />;
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
SignUp.tsx:
import React, { useState } from "react";
import Button from "#material-ui/core/Button";
import { connect } from "react-redux";
import { Form, Field } from "react-final-form";
import { makeStyles, Theme, createStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import CardWrapper from "../../components/CardWrapper";
import PasswordField from "../../components/Password";
import TextField from "../../components/TextField";
import { validate, submit } from "./validation";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
container: {
padding: 16,
margin: "auto",
maxWidth: "100%",
flexGrow: 1
},
paper: {
padding: 16
},
item: {
marginTop: 16
}
})
);
const SignUp = () => {
const classes = useStyles();
const [showPassword, setPassword] = useState(false);
const handleClickShowPassword = () => {
setPassword(!showPassword);
};
const handleMouseDownPassword = (
event: React.MouseEvent<HTMLButtonElement>
) => {
event.preventDefault();
};
return (
<div className={classes.container}>
<Form
onSubmit={submit}
validate={validate}
render={({ handleSubmit, form, submitting, pristine }) => (
<form onSubmit={handleSubmit}>
<CardWrapper title='SignUp Form'>
<Grid container justify='center' spacing={3}>
<Grid item md={12}>
<Field fullWidth required name='username'>
{props => (
<TextField
label='Username'
type='text'
value={props.input.value}
onChange={props.input.onChange}
onBlur={props.input.onBlur}
meta={props.meta}
fullWidth={true}
/>
)}
</Field>
</Grid>
<Grid item md={12}>
<Field fullWidth required name='email'>
{props => (
<TextField
label='Email'
type='email'
value={props.input.value}
onChange={props.input.onChange}
onBlur={props.input.onBlur}
meta={props.meta}
fullWidth={true}
/>
)}
</Field>
</Grid>
<Grid item md={12}>
<Field fullWidth required name='password'>
{props => (
<PasswordField
value={props.input.value}
handleChange={props.input.onChange}
showPassword={showPassword}
handleClickShowPassword={handleClickShowPassword}
handleMouseDownPassword={handleMouseDownPassword}
fullWidth={true}
onBlur={props.input.onBlur}
meta={props.meta}
/>
)}
</Field>
</Grid>
<Grid item className={classes.item}>
<Button
type='button'
variant='contained'
onClick={form.reset}
disabled={submitting || pristine}
>
Reset
</Button>
</Grid>
<Grid item className={classes.item}>
<Button
variant='contained'
color='primary'
type='submit'
disabled={submitting || pristine}
>
Submit
</Button>
</Grid>
</Grid>
</CardWrapper>
</form>
)}
/>
</div>
);
};
export default connect()(SignUp);
validation.ts:
interface SignUpValues {
email: string;
password: string;
username: string;
}
const submit = (values: SignUpValues) => {
const user = {
username: values.username,
email: values.email,
password: values.password
};
return signUpFetch(user);
};
export { submit };
I find a similar question posted about the same issue described by Redux Dispatch Not Working in Action Creator but the answer does not fix my problem. Does I make something wrong when linking the different component with redux?
It wont dispatch because in component You didnt dispatch Your function
return signUpFetch(user)
Instead Connect the component with Redux and dispatch the function
in Index.tsx
import { connect } from 'react-redux';
const mapDispatchToProps = {
submit
};
export default connect(null, mapDispatchToProps)(SignUp);
And access it with
this.props.submit
Add dispatch in Submit function
const submit = (values: SignUpValues) =>(dispatch, getState)=> {
const user = {
username: values.username,
email: values.email,
password: values.password
};
return dispatch(signUpFetch(user));
};
Whenever you need to update redux state, dispatch the function from where it is also called and also in the actions.
You need to connection the component to the store when you do the dispatch:
import { connect } from 'react-redux';
const submit = (values: SignUpValues) => {
const user = {
username: values.username,
email: values.email,
password: values.password
};
return this.props.signUpFetch(user);
};
export const connectedSubmit = connect(null, {signUpFetch})(submit);
import { validate, connectedSubmit as submit } from "./validation";
And also you can just return at SignUpUser
export const signUpFetch = (user: User) => {
return async (dispatch: Dispatch) => {
dispatch(signUpStarted());
return SignUpUser(user).then(
(response: any) => {
const { username } = response;
dispatch(signUpSucceeded(username));
},
(error: AxiosError) => {
let errorMessage = "Internal Server Error";
if (error.response) {
errorMessage = error.response.data;
}
dispatch(signUpFailed(errorMessage));
}
);
};
}

connected-react-router push is called nothing happens

There is a Login component
// #flow
import type {
TState as TAuth,
} from '../redux';
import * as React from 'react';
import Button from '#material-ui/core/Button';
import FormControl from '#material-ui/core/FormControl';
import Input from '#material-ui/core/Input';
import InputLabel from '#material-ui/core/InputLabel';
import Paper from '#material-ui/core/Paper';
import { withNamespaces } from 'react-i18next';
import { Link } from 'react-router-dom';
import {
connect,
} from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import useStyles from './styles';
import { login } from '../../redux';
import { push } from 'connected-react-router';
const logo = './assets/images/logo.png';
const {
useEffect,
} = React;
type TInputProps = {
input: Object,
meta: {
submitting: boolean,
}
}
const UserNameInput = (props: TInputProps) => (
<Input
id="userName"
name="userName"
autoComplete="userName"
autoFocus
{...props}
{...props.input}
disabled={props.meta.submitting}
/>
);
const PasswordInput = (props: TInputProps) => (
<Input
name="password"
type="password"
id="password"
autoComplete="current-password"
{...props}
{...props.input}
disabled={props.meta.submitting}
/>
);
type TProps = {
t: Function,
login: Function,
handleSubmit: Function,
error: string,
submitting: boolean,
auth: TAuth,
}
// TODO: fix flow error inside
const Login = ({
t,
login,
handleSubmit,
error,
submitting,
auth,
}: TProps) => {
const classes = useStyles();
useEffect(() => {
if (auth) {
console.log('push', push);
push('/dashboard');
}
}, [auth]);
return (
<main className={classes.main}>
<Paper className={classes.paper}>
<img src={logo} alt="logo" className={classes.logo} />
<form
className={classes.form}
onSubmit={handleSubmit((values) => {
// return here is very important
// login returns a promise
// so redux-form knows if it is in submission or finished
// also important to return because
// when throwing submissionErrors
// redux-form can handle it correctly
return login(values);
})}
>
<FormControl margin="normal" required fullWidth>
<Field
name="userName"
type="text"
component={UserNameInput}
label={
<InputLabel htmlFor="userName">{t('Username')}</InputLabel>
}
/>
</FormControl>
<FormControl margin="normal" required fullWidth>
<Field
name="password"
type="password"
component={PasswordInput}
label={
<InputLabel htmlFor="password">{t('Password')}</InputLabel>
}
/>
</FormControl>
<div className={classes.error}>{error}</div>
<Button
disabled={submitting}
type="submit"
fullWidth
variant="outlined"
color="primary"
className={classes.submit}
>
{t('Sign in')}
</Button>
<Link className={classes.forgot} to="/forgot">
{t('Forgot Password?')}
</Link>
</form>
</Paper>
</main>
);
};
const mapStateToProps = ({ auth }) => ({ auth });
const mapDispatchToProps = {
login,
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(
reduxForm({ form: 'login' })(withNamespaces()(Login))
);
In the useEffect hook the push from connected-react-router is used. The hook fires ok but nothing happens after it.
The same way, push is used in login action.
// #flow
import type {
TReducer,
THandlers,
TAction,
TThunkAction,
} from 'shared/utils/reduxHelpers';
import type {
TUser,
} from 'shared/models/User';
import createReducer from 'shared/utils/reduxHelpers';
import urls from 'constants/urls';
import axios, { type $AxiosXHR } from 'axios';
import { SubmissionError } from 'redux-form';
import { push } from 'connected-react-router';
export type TState = ?{
token: string,
result: $ReadOnly<TUser>,
};
export const ON_LOGIN = 'ON_LOGIN';
export const login: ({ userName: string, password: string }) => TThunkAction =
({ userName, password }) => async (dispatch, getState) => {
const res: $AxiosXHR<{username: string, password: string}, TState> =
await axios.post(`${urls.url}/signin`, { username: userName, password })
.catch((err) => {
throw new SubmissionError({ _error: err.response.data.message });
});
const data: TState = res.data;
dispatch({
type: ON_LOGIN,
payload: data,
});
push('/dashboard');
};
const handlers: THandlers<TState, TAction<TState>> = {
[ON_LOGIN]: (state, action) => action.payload,
};
const initialState = null;
const reducer: TReducer<TState> = createReducer(initialState, handlers);
export default reducer;
Here everything goes successful and dispatch happens and there is no push happening again.
Whats the problem?
Shouldn't there be dispatch(push('/dashboard')); ?
You just have to make sure that you do not create your middleware and pass in the history api before calling createRootReducer function.
If you try to create your middleware with routerMiddleware(history) too early , history will be passed in as undefined. Follow the README.md as it explains the exact execution order.
// configureStore.js
...
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from './reducers'
...
export const history = createBrowserHistory()
export default function configureStore(preloadedState) {
const store = createStore(
createRootReducer(history), // <-- Initiates the History API
preloadedState,
compose(
applyMiddleware(
routerMiddleware(history), // <--- Now history can be passed to middleware
// ... other middlewares ...
),
),
)
return store
}

Resources