Redux form does not reset - reactjs

i have a component which based upon props renders a form with different components.
class Feedback extends Component {
submitMyForm(data) {
const { onSubmit, reset } = this.props;
reset();
return onSubmit(data);
//
// do other success stuff
}
render() {
const { handleSubmit } = this.props;
let component;
if(this.props.data.feedbackType == "likert")
component = Likert;
else if(this.props.data.feedbackType == "single choice")
component = SingleChoice;
else if(this.props.data.feedbackType == "multiple choice")
component = MultipleChoice;
return (
<div>
<h1>Feedback zu Aufgabe {this.props.id}</h1>
<form onSubmit={handleSubmit(this.submitMyForm.bind(this))}>
<Field
name="feedback"
component={component}
heading={this.props.data.description}
items={this.props.data.options}
required={true}
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
// Decorate the form component
Feedback = reduxForm({
form: 'feedback', // a unique name for this form,
validate,
enableReinitialize:true
})(Feedback);
function validate(formProps) {
const errors = {};
if (!formProps.feedback) {
errors.feedback = 'please select an option';
}
return errors;
}
export default Feedback;
import React, { PropTypes } from 'react';
const SingleChoice = ({ input, disabled, heading, required, className, items, name, meta: { touched, error } }) => (
<fieldset className={`form__field ${className || ''}`}>
<legend className="form__label">
{heading}{required ? (<span>*</span>) : null}
{ (touched && error) ? (
<span className="form__error"> {error}</span>
) : null }
</legend>
<div>
{ items.map((item, i) => (
<div className="form__segmented-control width-1/2#small" key={ i }>
<input
{...input}
name={ name }
type="radio"
value={ item.value }
disabled={ disabled }
className="segmented-control__input u-option-bg-current"
id={ `${name}-${item.value}` }
/>
<label className="segmented-control__label u-adjacent-current" htmlFor={ `${name}-${item.value}` }>
{item.label}
</label>
</div>
))
}
</div>
</fieldset>
);
SingleChoice.propTypes = {
input: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
className: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
})).isRequired,
heading: PropTypes.string,
meta: PropTypes.object,
required: PropTypes.bool,
disabled: PropTypes.bool,
};
export default SingleChoice;
The first time the form renders everything is fine. All radio buttons are unchecked and if i try to submit it i get an validation error as intended. But when my Feeback component receives new props and the form is updated. The old values still remain selected when the form component for the new props is the same as the one for the old props.
When the form component for the new props is different all values are not selected as intended, but i can submit the form without selecting anything, which should be prevented by validation.
I hope you got any suggestions, i am totally out of ideas at this point.

I searched for hours trying to find a resolution to this problem. The best way I could fix it was by using the plugin() API to teach the redux-form reducer to respond to the action dispatched when your submission succeeds. Exactly like the first step here How can I clear my form after my submission succeeds?
const reducers = {
// ... your other reducers here ...
form: formReducer.plugin({
nameOfTheForm: (state, action) => { // <- 'nameOfTheForm' is name of form
switch(action.type) {
case ACCOUNT_SAVE_SUCCESS:
const values = undefined;
const fields = {
fields: {
input_field_name: {
visited: false,
touched: false
}
// repeat for each input field
}
};
const newState = { ...state, values, fields };
return newState;
default:
return state;
}
}
})
}
You will have to change a couple things in your component.
onSubmit(values) {
this.props.postForm(values, () => {});
}
render(){
const { handleSubmit } = this.props;
}
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}></form>
)
In your actions file:
export function postForm(values, callback) {
const request = axios.get(`${ROOT_URL}`, config).then(() => callback());
return {
type: ACCOUNT_SAVE_SUCCESS,
payload: request
};
}

The best way I found:
import 'initialize'...
import {initialize} from 'redux-form';
and then when you call the action, call another action right after passing an empty object to the 'initialize' function...
yourFunction = (data) => {
this.props.dispatch(yourAction(data))
.then(
result => {
if (result) {
this.props.dispatch(initialize('nameOfTheForm', {}));
}
}
);

when my Feeback component receives new props and the form is updated, the old values still remain selected when the form component for the new props is the same as the one for the old props.
This is because the values for the feedback form are stored in your Redux store.
You should implement componentWillReceiveProps and test whether your form should be reset or not.
class Feedback extends Component {
componentWillReceiveProps ( nextProps ) {
if ( nextProps.blabla !== this.props.blabla ) {
// oh cool, props changed, let's reset the form
// checkout more available form props at http://redux-form.com/6.4.3/docs/api/ReduxForm.md/
this.props.reset();
}
}
render () {
// your normal rendering function
}
}

Related

A Child component multiple times in A Parent component is not working in React + Redux

A Child component is used 3 times in A Parent component in React. A Child component includes form element and the application should save each different type of documents but in the same collections in Firestore.
The issue is all forms are changed when I try to edit the first form. The second one and the third one should not be updated at the same time. And this.props.fetchItem(id) is updated to all child components. I want to set up each value in each Child component.
A Child component (item.js)
class Item extends Component {
componentDidMount() {
const { id } = this.props;
this.props.fetchItem(id);
}
onSubmit = values => {
const { item } = this.props;
this.props.updateItem(item.uid, values);
this.setState({ success: true });
};
render() {
const {
handleSubmit, item
} = this.props;
return (
<div>
<form className="formItem" onSubmit={handleSubmit(this.onSubmit)}>
<Field
label={"Item"}
name="content"
value={item.content}
component={ItemForm}
/>
<div className="formBtn">
<button type="submit">
Save Changes
</button>
</div>
</form>
</div>
);
}
}
const mapDispatchToProps = { fetchItem, updateItem };
function mapStateToProps({ items }) {
return {
item: items,
initialValues: {
content: items.content
}
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(reduxForm({ form: "itemsForm", enableReinitialize: true })(Item));
Render Form component (item_form.js)
const ItemForm = ({
input,
label,
meta: { touched, error }
}) => {
return (
<div>
<p>{label}</p>
<textarea {...input} placeholder={placeholder} rows="6" />
<p className="errorForm">{touched && error && <span>{error}</span>}</p>
</div>
);
};
export default ItemForm;
A Parent Component (edit.js)
class Settings extends Component {
render() {
<div>
<Item id="1" />
<Item id="2" />
<Item id="3" />
</div>
}
}
Redux action
// Fetch item
export function fetchItem(id) {
return dispatch => {
firebase.firestore().collection('items')
.where("id", "==", Number(id))
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
dispatch({ type: FETCH_ITEM, payload: doc.data() });
});
});
});
};
}
// Update item
export function updateItem(uid, values) {
return dispatch => {
firebase.firestore().collection('items')
.where("id", "==", Number(id))
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.ref.update({
content: values.content
});
dispatch({ type: UPDATE_ITEM });
});
});
};
}
TL;DR:
You use the same form property for each form, which instead must be a unique identifier for each form. The solution is to remove form property from reduxForm parameters and pass it as a prop to your form component.
Detailed answer is here

Can't type in React input text field in Todo app

I am making a todo app in React.js but stuck in this part. I can not type in input field. Please help me.
import React, { Component } from 'react';
export default class AddItem extends Component {
state =
{
title: "",
done: false
}
changeTitle = (e) =>{
e.preventDefault();
this.setState = ({
title: e.target.value
});
}
addNewItem = (item) => {
item.preventDefault();
let newTask = {
title: this.state.title,
done: false
};
this.props.additem(newTask);
this.setState = ({
title: ""
});
}
render() {
return (
<div>
<form>
<input
type="text"
placeholder="add task name."
value={this.state.title}
onChange= {this.changeTitle}
/>
<button type = "button" onClick= {this.addNewItem} >submit</button>
</form>
</div>
)
}
}
this.setState is a function that is called with an object containing changes in state. The code you are using here is an assignment not a function call:
this.setState = ({
title: e.target.value // Wrong
});
Instead, call the setState function with the changes/updates in state. the changes are shallow merged and only title is updated here.
this.setState({title:e.target.value});
You will have a similar problem inside addNewItem.

Checkbox does not work well after resetting

I have a checkbox component, it works well when checking and unchecking. But if I reset, then select the checkbox again, it does not respond the click and only works after a few clicks. Sometimes it throws the error message "TypeError: this.setState is not a function". What is the problem here? Thanks.
App.js
const campusData = [
{ id: 1, value:'A',name: 'A' },
{ id: 2, value:'B',name: 'B' },
{ id: 3, value:'C',name: 'C' }
]
class App extends Component{
state={checkedCampusItems:[]};
onReset=()=>{
this.setState({checkedCampusItems:[]});
handleCampusChkChange=(id,name,value, checked)=> {
const checkedCampusItems = this.state.checkedCampusItems;
let index;
// check if the check box is checked or unchecked
if (checked) {
// add the numerical value of the checkbox to checkedItems array
checkedCampusItems.push(value);
} else {
index = checkedCampusItems.indexOf(value);
checkedCampusItems.splice(index, 1);
}
// update the state with the new array of options
this.setState({checkedCampusItems: checkedCampusItems });
console.log("checkedCampusItems array",checkedCampusItems);
}
render(){
return(<div><input type="reset" value="Reset Search" onClick={this.onReset}></input>
{campusData.map(item =>
<CampusChk id="campus" key={item.id} {...item} onChange={this.handleCampusChkChange} />
)}
</div>
)
}
}
export default App;
campusChk.js
import React from 'react';
const CampusChk = ({ id, name,value, onChange }) => {
return(
<div>
<input
type="checkbox"
onChange={(event) => onChange(id,name,value, event.target.checked) }
/>
<label>{name}</label>
</div>
);
}
export default CampusChk;
Change your reset function to update the state:
onReset=()=>{
this.setState({ checkedCampusItems: []})
}
You're also mutating state. This may not be causing the problem, but you should change your temporary array to this in your change handler:
const checkedCampusItems = [...this.state.checkedCampusItems];

React: this.setState() working in some functions, not in others within the same component

I am trying to develop a simple little survey component with its own local state. I've already successfully used this.setState() in my handleChange function to update which radio button is selected. I can see that it's working in console logs and on the live page. However, I am now trying to use the state to display a modal and highlight incomplete questions if the user tries to submit the survey without completing it. The functions I have to do this execute, but the state does not update. I am having trouble seeing any difference between where I updated state successfully and where I didn't.
Here's my container where I'm controlling state and defining the functions. I don't think any other code is relevant, but let me know if I need to include something else:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Redirect } from 'react-router'
import { history } from '../history'
import { connect } from 'react-redux'
import _ from 'lodash'
import { postPhq, sumPhq } from '../store'
import { Phq } from '.'
import { UnfinishedModal } from '.'
/**
* CONTAINER
*/
class PhqContainer extends Component {
constructor(props) {
super(props)
this.state = {
phq: {
q1: false, q2: false, q3: false, q4: false, q5: false,
q6: false, q7: false, q8: false, q9: false, q10: false
},
hilitRows: [],
modalOpen: false
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.makeButtons = this.makeButtons.bind(this)
this.toggleModal = this.toggleModal.bind(this)
}
//handles clicks on radio buttons
handleChange(question, event) {
var newPhq = this.state.phq
newPhq[question] = event.target.value
this.setState({phq: newPhq})
console.log("radio button", this.state)
}
// on submit, first check that PHQ-9 is entirely completed
// then score the PHQ-9 and dispatch the result to the store
// as well as posting the results to the database and
// dispatching them to the store
// if the PHQ-9 is not complete, open a modal to alert the user
handleSubmit(event) {
event.preventDefault()
if (this.checkCompletion()) {
this.props.sumPhq(this.sum())
this.props.postPhq(this.state.phq)
this.props.history.push('/results')
} else {
console.log(this.unfinishedRows())
this.toggleModal()
this.setState({hilitRows: this.unfinishedRows()})
}
}
// returns true if user has completed the PHQ-9 with no missing values
checkCompletion() {
return !this.unfinishedRows().length || this.oneToNine().every(val => val === 0)
}
unfinishedRows() {
return Object.keys(_.pickBy(this.state.phq, (val) => val === false))
}
oneToNine() {
var qs1to9 = Object.assign({}, this.state.phq)
delete qs1to9.q10
return Object.values(qs1to9)
}
toggleModal() {
console.log("I'm about to toggle the modal", this.state)
this.setState({modalOpen: !this.state.modalOpen})
console.log("I toggled the modal", this.state)
}
// scores the PHQ-9
sum() {
return this.oneToNine
.map(val => parseInt(val))
.reduce((total, num) => total + num)
}
makeButtons = (num, labels) => {
var question = 'q' + (num + 1).toString()
return (
<div style={rowStyle}>
{labels.map((label, i) => {
return (
<td style={{border: 'none'}}>
<div className="col radio center-text" >
<input type="radio"
value={i}
onChange={(event) => {this.handleChange(question, event)}}
checked={this.state.phq[question] && parseInt(this.state.phq[question]) == i} />
<label>{label}</label>
</div>
</td>
)
})}
</div>
)
}
render() {
return (
<div>
<Phq {...this.state} {...this.props}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
makeButtons={this.makeButtons} />
<UnfinishedModal show={this.state.modalOpen} toggleModal={this.toggleModal} />
</div>
)
}
}
const mapDispatch = (dispatch) => {
return {
postPhq: (qs) => {
dispatch(postPhq(qs))
},
sumPhq: (total) => {
dispatch(sumPhq(total))
}
}
}
export default connect(() => { return {} }, mapDispatch)(PhqContainer)
const rowStyle = {
paddingRight: 15,
borderWidth: 0,
margin: 0,
border: 'none',
borderTop: 0
}
This line this.setState({phq: newPhq}) updates the state. This line this.setState({hilitRows: this.unfinishedRows()}) and this line this.setState({modalOpen: !this.state.modalOpen}) do not.
I had the same issue happen in my project like setState is not working in some functions but its perfect for other functions in the same component. First of all you should understand, As mentioned in the React documentation, the setState being fired synchronously. So you can check like below value being updated.
this.setState({mykey: myValue}, () => {
console.log(this.state.mykey);
});
In my case, I was tried to update the value of an event onChange with an input, in every keyUp it's trying to update, Its causes the problem for me. So I have tried to used a debounce(delay) for the function so its working fine for me. Hope this hint will help you or someone else.
this.myFunction = debounce(this.myFunction,750);
<Input onChange={this.myFunction} />

React re-renders whole app after rendering a component

I use react and redux in my web app. It's the simple app which has 4 components, one reducer and 3 actions. After I add a new entry to list, react renders component of list (the listItem), then re-renders the whole app. What is the cause of re-rendering whole app after rendering one component?
Updated:
App container:
class App extends Component {
static propTypes = {
groups: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
render() {
return (<div>
<Header addGroup={this.props.actions.addGroup} />
<List groups={this.props.groups} />
</div>
);
}
}
function mapStateToProps(state) {
return { groups: state.groups };
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(AppActions, dispatch) };
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Reduser:
export default function groupDiseases(state = initialState, action){
switch (action.type) {
case ADD_GROUP:
return [
{
id: '',
name: action.name
},
...state
];
case DELETE_GROUP:
return state.filter(group =>
group.id !== action.id
);
case EDIT_GROUP:
return state.map(group => (group.id === action.id ? { id: action.id, name: action.name } : group));
default:
return state;
}
}
Components:
export default class Add extends Component {
static propTypes = {
addGroup: PropTypes.func.isRequired
}
componentDidMount() {
this.textInput.focus();
}
handleAdd = () => {
const name = this.textInput.value.trim();
if (name.length !== 0) {
this.props.addGroup(name);
this.textInput.value = '';
}
}
render() {
return (
<form className="add_form">
<input
type="text"
className="add__name"
defaultValue=""
ref={(input) => this.textInput = input}
placeholder="Name" />
<button
className="add__btn"
ref="add_button"
onClick={this.handleAdd}>
Add
</button>
</form>
);
}
}
export default class ListGroups extends Component {
static propTypes = {
groups: PropTypes.array.isRequired
};
render() {
let data = this.props.groups;
let groupTemplate = <div> Группы отсутствуют. </div>;
if (data.length) {
groupTemplate = data.map((item, index) => {
return (
<div key={index}>
<Item item={item} />
</div>
);
});
}
return (
<div className="groups">
{groupTemplate}
<strong
className={'group__count ' + (data.length > 0 ? '' : 'none')}>
Всего групп: {data.length}
</strong>
</div>
);
}
}
It's likely due to the fact that you are letting the <form> continue its default behavior, which is to submit to a targeted action. Take a look at the w3c spec for buttons:
http://w3c.github.io/html-reference/button.html
Specifically, a button with no type attribute will default to submit.
So your button is telling the form to submit, with the target being the current page since none is provided. In your handleAdd method, you can do something like:
handleAdd = (event) => {
event.preventDefault(); // prevent default form submission behavior
const name = this.textInput.value.trim();
if (name.length !== 0) {
this.props.addGroup(name);
this.textInput.value = '';
}
}
Or you can modify your button to have type="button".

Resources