Redux-Form unable to redirect after onSubmit Success - redirect

I would like to redirect to another page after a successful submit in redux-form.
I have tried the follow but the redirect either fails or doesn't do anything
REACT-ROUTER-DOM:
This results is an error 'TypeError: Cannot read property 'push' of undefined'
import { withRouter } from "react-router-dom";
FacilityComplianceEditForm = withRouter(connect(mapStateToProps (FacilityComplianceEditForm));
export default reduxForm({
form: "FacilityComplianceEditForm",
enableReinitialize: true,
keepDirtyOnReinitialize: true,
onSubmitSuccess: (result, dispatch, props) => {
props.history.push('/facilities') }
})(FacilityComplianceEditForm);
REACT-ROUTER-REDUX:
This submits successfully, the data is saved to the DB, but the page does not redirect.
import { push } from "react-router-redux";
export default reduxForm({
form: "FacilityComplianceEditForm",
enableReinitialize: true,
keepDirtyOnReinitialize: true,
onSubmitSuccess: (result, dispatch, props) => { dispatch(push('/facilities')) }
})(FacilityComplianceEditForm);
I also tried onSubmitSuccess: (result, dispatch, props) => dispatch(push('/facilities')) without the {} around dispatch statement but it didn't work
APP.JS to show the path does exist
class App extends Component {
render() {
return (
<div>
<Header />
<div className="container-fluid">
<Switch>
<Route exact path="/facilities" render={() => <FacilitySearch {...this.props} />} />
<Route exact path="/facilities/:id" render={(props) => <FacilityInfo id={props.match.params.id} {...this.props} />} />
<Route exact path="/facilities/compliance/:id" render={(props) => <FacilityComplianceEditForm id={props.match.params.id}{...this.props} />
<Redirect from="/" exact to="/facilities" />
<Redirect to="/not-found" />
</Switch>
</div>
<Footer />
</div>
);
}
}
export default App;
REDUCER:
export const complianceByIdReducer = (state = INTIAL_STATE.compId, action) => {
switch (action.type) {
console.log(state, action)
case "CREATE_NEW_COMPLIANCE":
return {
...state,
compId: action.compCreate
}
default:
return state
}
}
ACTION:
export const createCompliance = (id, compObj) => {
return dispatch => {
axios.post("/api/facilities/compliance/" + id, compObj)
.then(res => { return res.data })
.then(compCreate => {
dispatch(createComplianceSuccess(compCreate));
alert("New compliance created successfully") //this does get triggered
})
}
}
const createComplianceSuccess = compCreate => {
return {
type: "CREATE_NEW_COMPLIANCE",
compCreate: compCreate
}
}
REDIRECT OBJECT RETURNED FROM SUBMIT SUCCESS
STORE
import * as redux from "redux";
import thunk from "redux-thunk";
import {
facilityListReducer,
facilityReducer,
facilityLocationReducer,
facilityHistoricalNameReducer,
facilityComplianceReducer,
complianceByIdReducer
} from "../reducers/FacilityReducers";
import { projectFormsReducer } from "../reducers/FormsReducers";
import { errorReducer } from "../reducers/ErrorReducer";
import { reducer as formReducer } from "redux-form";
export const init = () => {
const reducer = redux.combineReducers({
facilityList: facilityListReducer,
facility: facilityReducer,
facilityLocation: facilityLocationReducer,
historicalNames: facilityHistoricalNameReducer,
facilityCompliance: facilityComplianceReducer,
compId: complianceByIdReducer,
countyList: projectFormsReducer,
errors: errorReducer,
form: formReducer
});
const store = redux.createStore(reducer, redux.applyMiddleware(thunk));
return store;
};

react-router-redux is deprecated so I did not want to add it to my project. I followed this post and made some modification to how routing was set up and it now works.

Related

FormContext from react-hook-forms causing error in testing-library-react

Got an odd error with FormContext when trying to run my tests. All I'm trying to do is render a component.
So this is the error that I am getting and this is the test that I have written.
import React from 'react';
import UserReportQuestion from './UserReportQuestion';
import { render } from '#testing-library/react';
import { useForm, FormContext } from 'react-hook-form';
describe('(Component) UserReport', () => {
let UserReportQuestionRender;
// jest.mock('react-hook-form', () => ({
// FormContext: jest.fn(),
// useForm: jest.fn(),
// }));
beforeEach(() => {
const form = useForm();
UserReportQuestionRender = render(
<FormContext {...form}>
<UserReportQuestion />
</FormContext>
)
});
it('Should render without crashing', () => {
expect(UserReportQuestionRender);
});
});
In the component I am testing I am using FormContext and passing it useForm as its methods. I've commented everything else out so it is just the FormContext in the component to make sure that it is 100% this that is causing the error.
Wondering if anyone has any ideas on how to work round this?
Update with component
import { useForm, FormContext } from "react-hook-form";
const UserReportQuestion = ({ text }) => {
const { t } = useTranslation();
const [isModalOpen, setModal] = useState(false);
const methods = useForm();
return (
<>
<Question>
{t('UserReport.criteria')}:
{' '}
{/* Placeholder text */}
{/* {text} */}
Find new, creative ways of completing tasks
<ModalIcon onClick={() => setModal(true)}>
<Icon
icon="info"
color="#a3a3a3"
modifiers={['size-large', 'solid']}
/>
</ModalIcon>
</Question>
<InfoModal
isModalOpen={isModalOpen}
closeModal={() => setModal(false)}
title={t('UserReport.modalTitle')}
>
<FormContext {...methods}>
{/* <form onSubmit={methods.handleSubmit()}>
<InfoModalParagraph>
{t('UserReport.modalDescription')}
</InfoModalParagraph>
<FeedbackQuestions
questions={mockData}
selfAssessment={false}
submitButtonDisabled
noSubmitButton
/>
</form> */}
</FormContext>
</InfoModal>
</>
);
}
I think you're importing a component which doesn't exist, in the react-hook-form documentation we have a FormProvider if we want to use useFormContext.
read More about it here: https://react-hook-form.com/api/#useFormContext
but in the mean time you need to change
import {FormContext} from 'react-hook-form'
to this:
import { FormProvider } from 'react-hook-form'

Reactjs how to get value from selected element

so I have this code for posting to my backend API. Normal form perfectly fine; I managed to post to my database. So I add a Cascader from Ant Design CSS Framework, and every time I selected the value, it produced an error
TypeError: Cannot read property 'value' of undefined
Here is the code:
import React from 'react';
import axios from 'axios';
import { Button, Cascader, Form, Input, Modal } from 'antd';
const FormProduct = Form.Item;
const computerType = [
{
value: 'computer',
label: 'Computer',
},
{
value: 'laptop',
label: 'Laptop',
}
]
export default class FormInventory extends React.Component {
state = {
category: '',
productname: '',
};
handleCategoryChange = event => { this.setState({ category: event.target.value }) }
handleProductNameChange = event => { this.setState({ productname: event.target.value }) }
handleSubmit = event => {
event.preventDefault();
axios.post('myapi',
{
category: this.state.category,
productname: this.state.productname,
})
.then(
function success() {
const modal = Modal.success({
title: 'Success',
content: 'Data successfully add',
});
setTimeout(() => modal.destroy(), 2000);
}
)
}
render() {
return (
<Form onSubmit={this.handleSubmit}>
<FormProduct {...formProductLayout} label="Computer Category">
<Cascader options={computerType} category={this.state.value} onChange={this.handleCategoryChange} />
</FormProduct>
<FormProduct {...formProductLayout} label="Product Name">
<Input type="text" productname={this.state.productname} onChange={this.handleProductNameChange} />
</FormProduct>
<FormProduct wrapperCol={{ span: 12, offset: 2 }}>
<Button type="primary" htmlType="submit">
Add Item
</Button>
</FormProduct>
</Form>
)
}
}
You need to either bind your event handlers in the constructor or use arrow function.
Option 1: Bind
constructor(props) {
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
Option 2: Arrow function
<Input onChange={(e) => this.handleChange(e)} />
According to antd docs you don't need event.target.
https://ant.design/components/cascader/
handleCategoryChange = category => { this.setState({ category }) }
The code above will work fine.

FlatList not displaying with fetched json object

I'm currently following Facebook's React Native Tutorial and I'm having trouble fetching a json object from https://facebook.github.io/react-native/movies.json and displaying it in a FlatList. Here is my code:
import React, { Component } from 'react';
import {FlatList, ActivityIndicator, ListView, View } from 'react-native';
import { Card, CardItem, Text, Container, Header, Title, Content, Footer, FooterTab, Button, Left, Right, Body, Icon } from 'native-base';
class JobPost extends Component {
render(){
return(
<Card>
<CardItem header>
<Text>{this.props.header}</Text>
</CardItem>
<CardItem>
<Body>
<Text>
{this.props.body}
</Text>
</Body>
</CardItem>
</Card>
)
}
}
export default class Movies extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource: ''
}
}
componentDidMount() {
return fetch('https://facebook.github.io/react-native/movies.json')
.then((response) => response.json())
.then((responseJson) => {
console.log(responseJson);
this.setState({
isLoading: false,
dataSource: responseJson,
});
})
.catch((error) => {
console.error(error);
});
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
return (
<View style={{flex: 1, paddingTop: 20}}>
<FlatList
data={this.state.dataSource}
renderItem={({item}) => <Card header={item.title} body={item.releaseYear}/>}
keyExtractor={item => item.title}
/>
</View>
);
}
}
The console log to print out the json object works correctly. However, I just get a blank screen when I run it in the IOS Simulator.
The issue was it wasn't retrieving an array from the fetch, so I was supposed to specify dataSource: responseJson.movies
That did the trick.
Flat list will not update date if you don't set extraData.
You should use extraData for Flat list if want to update data (`extraData={this.state} ): https://facebook.github.io/react-native/docs/0.48/flatlist.html#extradata

How can I use props to auto-populate editable redux-form fields in React?

I'm new to React so I've tried to show as much code as possible here to hopefully figure this out! Basically I just want to fill form fields with properties from an object that I fetched from another API. The object is stored in the autoFill reducer. For example, I would like to fill an input with autoFill.volumeInfo.title, where the user can change the value before submitting if they want.
I used mapDispatchtoProps from the autoFill action creator, but this.props.autoFill is still appearing as undefined in the FillForm component. I'm also confused about how to then use props again to submit the form. Thanks!
My reducer:
import { AUTO_FILL } from '../actions/index';
export default function(state = null, action) {
switch(action.type) {
case AUTO_FILL:
return action.payload;
}
return state;
}
Action creator:
export const AUTO_FILL = 'AUTO_FILL';
export function autoFill(data) {
return {
type: AUTO_FILL,
payload: data
}
}
Calling the autoFill action creator:
class SelectBook extends Component {
render() {
return (
....
<button
className="btn btn-primary"
onClick={() => this.props.autoFill(this.props.result)}>
Next
</button>
);
}
}
....
function mapDispatchToProps(dispatch) {
return bindActionCreators({ autoFill }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(SelectBook);
And here is the actual Form where the issues lie:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import { createBook } from '../actions/index;
class FillForm extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
onSubmit(props) {
this.props.createBook(props)
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { fields: { title }, handleSubmit } = this.props;
return (
<form {...initialValues} onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<input type="text" className="form-control" name="title" {...title} />
<button type="submit">Submit</button>
</form>
)
}
export default reduxForm({
form: 'AutoForm',
fields: ['title']
},
state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
}), {createBook})(FillForm)
I think you're mixing up connect and reduxForm decorators in the actual form component. Currently your code looks like this (annotations added by me):
export default reduxForm({
// redux form options
form: 'AutoForm',
fields: ['title']
},
// is this supposed to be mapStateToProps?
state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
}),
/// and is this mapDispatchToProps?
{createBook})(FillForm)
If this is the case, then the fix should be as simple as using the connect decorator as it should be (I also recommend separating this connect props to their own variables to minimize confusions like this):
const mapStateToProps = state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
})
const mapDispatchToProps = { createBook }
export default connect(mapStateToProps, mapDispatchToProps)(
reduxForm({ form: 'AutoForm', fields: ['title'] })(FillForm)
)
Hope this helps!

React/Redux clear form elements values after submit

I have problems with clearing values from input and select form elements in react form after successful submit through axios library. Just want to mention that i do not use redux-form.
I don't know if I am on the right track here, this is my workflow by far: I wrote a form with react-bootstrap, give every input and select value through props and I access and update the state through these props. I have wrote actions and reducers for updating input values, and one action is dispatched in my component, but the second action and the reducer that is supposed to clear values after submit doesn't work as expected. This is the main problem, I'm not sure if I dispatch FORM_RESET action form in the right place, because I call it in the action that is responsible for posting data to server, and on success callback I dispatch FORM_RESET.
Below is the code relevant for this problem.
/* actionRegister.js */
let _registerUserFailure = (payload) => {
return {
type: types.SAVE_USER_FAILURE,
payload
};
};
let _registerUserSuccess = (payload) => {
return {
type: types.SAVE_USER_SUCCESS,
payload,
is_Active: 0,
isLoading:true
};
};
let _hideNotification = (payload) => {
return {
type: types.HIDE_NOTIFICATION,
payload: ''
};
};
//asynchronous helpers
export function registerUser({ //use redux-thunk for asynchronous dispatch
timezone,
password,
passwordConfirmation,
email,
name
}) {
return dispatch => {
axios.all([axios.post('/auth/signup', {
timezone,
password,
passwordConfirmation,
email,
name,
is_Active: 0
})
// axios.post('/send', {email})
])
.then(axios.spread(res => {
dispatch(_registerUserSuccess(res.data.message));
dispatch(formReset()); //here I dispatch clearing form data
setTimeout(() => {
dispatch(_hideNotification(res.data.message));
}, 10000);
}))
.catch(res => {
dispatch(_registerUserFailure(res.data.message)); //BE validation and passport error message
setTimeout(() => {
dispatch(_hideNotification(res.data.message));
}, 10000);
});
};
}
/* actionForm.js */
//synchronous action creators
export function formUpdate(name, value) {
return {
type: types.FORM_UPDATE_VALUE,
name, //shorthand from name:name introduced in ES2016
value
};
}
export function formReset() {
return {
type: types.FORM_RESET
};
}
/* reducerRegister.js */
const INITIAL_STATE = {
error:{},
is_Active:false,
isLoading:false
};
const reducerSignup = (state = INITIAL_STATE , action) => {
switch(action.type) {
case types.SAVE_USER_SUCCESS:
return { ...state, is_Active:false, isLoading: true, error: { register: action.payload }};
case types.SAVE_USER_FAILURE:
return { ...state, error: { register: action.payload }};
case types.HIDE_NOTIFICATION:
return { ...state , error:{} };
}
return state;
};
export default reducerSignup;
/* reducerForm.js */
const INITIAL_STATE = {
values: {}
};
const reducerUpdate = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.FORM_UPDATE_VALUE:
return Object.assign({}, state, {
values: Object.assign({}, state.values, {
[action.name]: action.value,
})
});
case types.FORM_RESET:
return INITIAL_STATE;
//here I need isLoading value from reducerRegister.js
}
return state;
};
export default reducerUpdate;
/* SignupForm.js */
import React, {Component} from 'react';
import {reduxForm} from 'redux-form';
import {connect} from 'react-redux';
import map from 'lodash/map';
import timezones from '../../data/timezones';
import styles from '../formElements/formElements.scss';
import {registerUser} from '../../actions/actionRegister';
import {formUpdate} from '../../actions/actionForm';
import FieldGroup from '../formElements/FieldGroup';
import { Form, FormControl, Col, Checkbox, Button, FormGroup } from 'react-bootstrap';
// {... props} passing large number of props wrap in object with spread notation
class SignupForm extends Component { //if component have state it needs to be class
constructor(props) {
super(props);
this.state = {
errors: { //this errors are irrelevant for now
name:'',
email: '',
password: '',
passwordConfirmation:'',
timezone:''
},
};
}
onChange = (event, index, value) => {
this.props.onChange(event.target.name, event.target.value);
};
onSave = (event) => {
event.preventDefault();
this.props.onSave(this.props.values);
}
render() {
let isLoading = this.props.isLoading;
return (
// this.props.handleSubmit is created by reduxForm()
// if the form is valid, it will call this.props.onSubmit
<Form onSubmit={this.onSave} horizontal>
<FieldGroup
id="formControlsName"
type="text"
label="Name"
name="name"
placeholder="Enter Name"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.name}
/>
<FieldGroup
id="formControlsEmail"
type="text"
label="Email"
name="email"
placeholder="Enter Email"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.email}
/>
<FieldGroup
id="formControlsPassword"
type="password"
label="Password"
name="password"
placeholder="Enter Password"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.password}
/>
<FieldGroup
id="formControlsPasswordConfirmation"
type="password"
label="Password Confirmation"
name="passwordConfirmation"
placeholder="Enter Password"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.passwordConfirmation}
/>
<FieldGroup
id="formControlsTimezone"
label="Time Zone"
name="timezone"
placeholder="Select Time Zone"
componentClass="select"
defaultValue="Select Your Timezone"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.timezone}
>
<option value="Select Your Timezone">Select Your Timezone</option>
{
map(timezones, (key, value) =>
<option key={key} value={key}>{value}</option>)
}
</FieldGroup>
<FormGroup>
<Col smOffset={4} sm={8}>
<Checkbox>Remember me</Checkbox>
</Col>
</FormGroup>
<FormGroup>
<Col smOffset={4} sm={8}>
<Button type="submit" disabled={isLoading}
onClick={!isLoading ? isLoading : null}
>
{ isLoading ? 'Creating...' : 'Create New Account'}
</Button>
</Col>
</FormGroup>
{this.props.errorMessage && this.props.errorMessage.register &&
<div className="error-container">{this.props.errorMessage.register}</div>}
</Form>
//this.setState({ disabled: true });
//this.props.errorMessage.register == this.props = {errorMessage :{ register: ''}}
);
}
}
function mapStateToProps(state) {
return {
errorMessage: state.signup.error,
isLoading: state.signup.isLoading,
values: state.form.values
};
}
function mapDispatchToProps(dispatch) {
return {
onSave: (values) => dispatch(registerUser(values)),
onChange: (name, value) => dispatch(formUpdate(name, value))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SignupForm)
;
There is no need to use redux-form :-) You're on the right path and you're calling FORM_RESET action in the right place.
Couple of things:
are you sure you are importing formReset in actionRegister.js?
in reducerForm I would suggest to still return new state here:
case types.FORM_RESET:
return { ...INITIAL_STATE }; // or Object.assign({}, INITIAL_STATE)
And btw. why are you setting isLoading: true on success? I would suggest to create 3 actions instead of 2:
SAVE_USER_START (which you dispatch before sending a request),
set isLoading to true,
SAVE_USER_SUCCESS - set isLoading to false
SAVE_USER_FAILURE - set isLoading to false
I would suggest to look into redux-form library. It provides configuration option to clear fields after submit out of the box.