Reset after save bug - Not restoring to correct initialValues - redux-form

I am having a very weird bug. I have reproduced the simplest test case scenario here: https://codesandbox.io/s/PNyPwyWP2
I have also uploaded a screencast explaining, the screencast is on youtube here - https://youtu.be/iILiFieO-gk
My goal is that I have a form with a single field, a button "Reset" and a button "Save". Clicking "Save" saves the form values into a reducer in my store called save. Clicking "Reset" should reset the form values to the last "pristine" values (the values in initialValues).
However my issue is, after saving the form, the "Reset" button should reset it to the "pristine" value (the newly saved value, the value in initialValues) but it is reseting it to the "old pristine value"
Here is my full app code:
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, combineReducers } from 'redux'
import { connect } from 'react-redux'
import { Field, reduxForm, reducer as form } from 'redux-form'
// ACTION & ACTION CREATOR
const SAVE_FORM = 'SAVE_FORM';
function saveForm(values) {
return {
type: SAVE_FORM,
values
}
}
// REDUCER - save
const INITIAL = { url:'hiiii' };
function save(state=INITIAL, action) {
switch(action.type) {
case SAVE_FORM: return action.values;
default: return state;
}
}
// STORE
const reducers = combineReducers({ form, save });
const store = createStore(reducers);
// MY FORM COMPONENT
class MyFormDumb extends React.Component {
handleReset = e => {
e.preventDefault();
this.props.reset();
}
render() {
console.log('MyFormDumb :: pristine:', this.props.pristine, 'initialValues:', this.props.initialValues);
return (
<form onSubmit={this.props.handleSubmit}>
<label htmlFor="url">URL</label>
<Field name="url" component="input" type="text" />
<button onClick={this.handleReset}>Reset</button>
<button type="submit">Save</button>
</form>
)
}
}
const MyFormControlled = reduxForm({ form:'my-form' });
const MyFormSmart = connect(
function(state) {
return {
initialValues: state.save
}
}
);
const MyForm = MyFormSmart(MyFormControlled(MyFormDumb));
// MY APP COMPONENT
class App extends React.PureComponent {
submitHandler = (values, dispatch, formProps) => {
dispatch(saveForm(values));
}
render() {
return (
<Provider store={store}>
<div className="app">
<MyForm onSubmit={this.submitHandler} />
</div>
</Provider>
)
}
}
// RENDER
ReactDOM.render(<App />, document.getElementById('app'))

Please use enableReinitialize: true flag on your reduxForm component, as per the docs.

Related

React.js using context api to implement the dark/light theme, is it possible to get data in App component when I am using contextprovider?

I am using the context api to give my application the toggling ability between the dark/light mode, I managed to toggle the mode in all the children components of App component but when I tried to implement it to the component itself I failed I guess this related the fact the I am using the contextProvider within this component, code below for :
import React from 'react'
import styles from './App.module.css'
import { Card, CountryPicker, Chart } from './components/index'
import { fetchData } from './api/index'
import ThemeContextProvider, { ThemeContext } from './contexts/ThemeContext'
import ToggleTheme from './components/ToggleTheme/ToggleTheme'
export default class App extends React.Component {
static contextType = ThemeContext
state = {
data: {},
country: '',
}
handleCountryChange = async (country) => {
// console.log(country)
const Data = await fetchData(country)
// console.log(Data)
this.setState({ data: Data, country })
// console.log(this.state.data, this.state.country)
}
async componentDidMount() {
const data = await fetchData();
this.setState({ data })
}
render() {
const { data, country } = this.state;
// problem here
const { isLightTheme, dark, light } = this.context;
return (
<ThemeContextProvider>
<div className={styles.container} >
<ToggleTheme />
<Card data={data} />
<CountryPicker handleCountryChange={this.handleCountryChange} />
<Chart data={data} country={country} />
</div>
</ThemeContextProvider>
)
}
}
I figured it out,the solution was very simple, just importing the App component into other componentMain and wrapping it with <ContextProvider></ContextProvider> and import in the index.js

My state in store not getting updated , rather state attribute gets disappeared once action fires as checked in Redux dev tool

I have created an input field and am dispatching an action in the onChange method of the input field. i have a state attribute text:'' which should get updated once user starts typing in the input field but same is not happening , instead text attribute in the state disappears once action is fired as checked in Redux Dev tool.
Also have below 2 queries - read the docs , but still not clear
Why do we have to pass initialState in createStore as I have already passed in state to reducers , though have passed empty initialState to createStore .
Do I need to use mapStateToProps in my case as I am not making use of any state in my component
Action file - searchActions.js
import { SEARCH_MOVIE } from "./types";
export const searchMovie = text => ({
type: SEARCH_MOVIE,
payload: text
});
Reducer file - searchReducer.js
import { SEARCH_MOVIE } from "../actions/types";
const initialState = {
text: ""
};
const searchReducer = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case SEARCH_MOVIE:
return {
...state,
text: action.payload
};
default:
return state;
}
};
export default searchReducer;
Root reducer file - index.js
import { combineReducers } from "redux";
import searchReducer from "./searchReducer";
const rootReducer = combineReducers({
searchReducer
});
export default rootReducer;
Store file - store.js
import { createStore } from "redux";
import rootReducer from "./reducers";
const initialState = {};
const store = createStore(
rootReducer,
initialState,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
Form containing the input field - SearchForm.js
import React, { Component } from "react";
import { searchMovie } from "../../actions/searchActions";
import { connect } from "react-redux";
export class SearchForm extends Component {
onChange = e => {
this.props.searchMovie(e.target.value);
};
render() {
return (
<div className="jumbotron jumbotron-fluid mt-5 text-center">
<form className="form-group">
<input
type="text"
className="form-control"
placeholder="Search Movies"
onChange={e => this.onChange(e)}
></input>
<button type="submit" className="btn btn-primary mt-3 btn-bg">
Search
</button>
</form>
</div>
);
}
}
const mapStateToProps = state => {
return {
text: state.text
};
};
const mapDispatchToProps = dispatch => ({
searchMovie: () => dispatch(searchMovie())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(SearchForm);
Entry file - App.js
import React, { Component } from "react";
import { Provider } from "react-redux";
import SearchForm from "./SearchForm";
import store from "./store";
import "./App.css";
class App extends Component {
render() {
return (
<Provider store={store}>
<SearchForm />
</Provider>
);
}
}
export default App;
You call this function with a parameter
this.props.searchMovie(e.target.value);
However in here you do not provide any parameter
const mapDispatchToProps = dispatch => ({
searchMovie: () => dispatch(searchMovie())
});
should be
const mapDispatchToProps = dispatch => ({
searchMovie: movie => dispatch(searchMovie(movie))
});
For your questions
1. Why do we have to pass initialState in createStore as I have already passed in state to reducers , though have passed empty initialState to createStore .
You haven't passed initialState to reducer, in fact you can't. This is different const searchReducer = (state = initialState, action) => {. It's a javascript syntax which acts as a default value for a parameter if you don't provide one.
2. Do I need to use mapStateToProps in my case as I am not making use of any state in my component
Yes you don't have to. You can set is as undefined

React form handleChange is not updating state

Form input onChange is not updating state. The action and reducer fire properly and backend rails API is updated, but the state does not change and nothing renders in the DOM.
I have used console logs to ensure that the action and reducer are working properly.
import React, { Component } from 'react';
import { Button, Form } from 'semantic-ui-react'
import { connect } from 'react-redux'
class TaskInput extends Component {
constructor() {
super()
this.state = {
name: ""
}
}
handleChange = (e) => {
this.setState({
name: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault()
this.props.addTask({name: this.state.name}, this.props.goal.id)
this.setState({
name: ''
})
}
render() {
return (
<Form className="new-task-form" onSubmit={(e) =>this.handleSubmit(e)}>
<Form.Field>
<label className="form-label">Add Task</label>
<input id="name" required value={this.state.name} onChange={(e) => this.handleChange(e)} />
</Form.Field>
<Button basic color='purple' type="submit">Add Task</Button>
<hr/>
</Form>
)
}
}
export default connect()(TaskInput)
import React, { Component } from 'react'
import { addTask, deleteTask } from '../actions/tasksActions'
import { connect } from 'react-redux'
import { fetchGoal } from '../actions/goalsActions'
import Tasks from '../components/Tasks/Tasks';
import TaskInput from '../components/Tasks/TaskInput';
class TasksContainer extends Component {
componentDidMount() {
this.props.fetchGoal(this.props.goal.id)
}
render(){
return(
<div>
<TaskInput
goal={this.props.goal}
addTask={this.props.addTask}
/>
<strong>Tasks:</strong>
<Tasks
key={this.props.goal.id}
tasks={this.props.goal.tasks}
deleteTask={this.props.deleteTask}
/>
</div>
)
}
}
const mapStateToProps = state => ({
tasks: state.tasksData
})
export default connect(mapStateToProps, { addTask, deleteTask, fetchGoal })(TasksContainer);
export default function taskReducer(state = {
loading: false,
tasksData: []
},action){
switch(action.type){
case 'FETCH_TASKS':
return {...state, tasksData: action.payload.tasks}
case 'LOADING_TASKS':
return {...state, loading: true}
case 'CREATE_TASK':
console.log('CREATE Task', action.payload )
return {...state, tasksData:[...state.tasksData, action.payload.task]}
case 'DELETE_TASK':
return {...state, loading: false, tasksData: state.tasksData.filter(task => task.id !== action.payload.id )}
default:
return state;
}
}
handleSubmit calls the action to addTask. handleChange updates state and renders the new task in DOM. handleSubmit is working. handleChange is not.

Redux state changed but component props is not updating

I am using redux to control an Ant Design Modal component with a boolean state. Basically it has a button that dispatch action to change the state, and the component will read the state value.
The state is changed properly but the component props value is not updating accordingly. Not sure why it is not working.
I have tried different approaches in reducer like creating a new boolean object to avoid mutating the state but no luck.
myAction.js
export const modalVisibilityOn = () => ({
type: 'MODAL_ON'
})
export const modalVisibilityOff = () => ({
type: 'MODAL_OFF'
})
myReducer.js
const modalVisibility = (state = false, action) => {
switch (action.type){
case 'MODAL_ON':
return true
case 'MODAL_OFF':
return false
default:
return state
}
}
export default modalVisibility
myRootReducer.js
import { combineReducers } from 'redux'
import modalVisibility from './signPage/myReducer'
export default combineReducers({
modalVisibility
})
myModal.js
import React from "react";
import PropTypes from 'prop-types'
import { Modal, Input } from 'antd';
import { connect } from 'react-redux'
import { modalVisibilityOff } from '../../reducers/signPage/myAction'
class myModal extends React.Component {
render() {
const { visibility, handleOk, handleCancel } = this.props;
myModal.propTypes = {
visibility: PropTypes.bool.isRequired,
handleOk: PropTypes.func.isRequired,
handleCancel: PropTypes.func.isRequired,
}
return (
<Modal
title="Sign"
visible={visibility}
onOk={handleOk}
onCancel={handleCancel}
closable={false}
>
<p>Please input your staff ID</p>
<Input addonBefore="Staff ID" />
</Modal>
);
}
}
const mapStateToProps = state => {
return {
visibility: state.modalVisibility
}
}
const mapDispatchToProps = dispatch => ({
handleOk: () => dispatch(modalVisibilityOff()),
handleCancel: () => dispatch(modalVisibilityOff()),
})
export default connect(
mapStateToProps,mapDispatchToProps
)(myModal)
myModalContainer.js
import React from "react";
import { Input } from "antd";
import { Button } from 'antd';
import { Row, Col } from 'antd';
import { Typography } from 'antd';
import PropTypes from 'prop-types'
import myModal from '../../dialogs/signPage/myModal';
import { connect } from 'react-redux'
import { modalVisibilityOn } from '../../reducers/signPage/myAction'
class myModalContainer extends React.Component {
render() {
const { Title } = Typography;
const { onClick } = this.props;
myModalContainer.propTypes = {
onClick: PropTypes.func.isRequired
}
return (
<div className="search-container-parent">
<Row className="search-container">
<Col className="search-col1" xs={24} sm={12}>
<Input size="large" style={{width:'40%'}} id="issueReturnNo" placeholder="QR code here"/>
<Button size="large">SEARCH</Button>
<div className="signBtn-div">
<Button size="large" type="primary" onClick={onClick} >SIGN</Button>
<myModal />
</div>
</Col>
<Col xs={24} sm={12}>
<Title className="issueLog-title" level={3} style={{color:"#F08080"}}>Issue</Title>
</Col>
</Row>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(modalVisibilityOn())
})
export default connect(
null, mapDispatchToProps
)(myModalContainer);
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import rootReducer from './myRootReducer'
const store = createStore(rootReducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
serviceWorker.unregister();
I expect the visibility props on myModal.js would be true when the sign button on myModalContainer.js is clicked, but the it keep showing false.
Any help would be appreciated. Thanks!
After lots of researches and trials, It turns out that my code has no problem..
The reason why it is not working as expected, is due to the redux and react-redux version. After switching package.json dependencies versions back to the one that redux official tutorial are using, the application is running without any problem.
In case anyone have the same problem, here is the version I am using now for my app in npm:
redux: ^3.5.2
react-redux: ^5.0.7
Update:
Just found out that the cause of the problem comes from conflicts between older version modules and newer version modules.
Therefore by updating all the dependencies in package.json to the latest version, the app can also run smoothly. It is not necessary to downgrade react-redux and redux.

Redux store error: <Provider> does not support changing `store` on the fly

I am trying to setup my first react/redux/rails app. I am using react_on_rails gem to pass in my current_user and gyms props.
Everything appears to work ok so far except my console shows error:
<Provider> does not support changing `store` on the fly. It is most likely that you see this error because you updated to Redux 2.x and React Redux 2.x which no longer hot reload reducers automatically. See https://github.com/reactjs/react-redux/releases/tag/v2.0.0 for the migration instructions.
Googling gives me hints that this can happen if you try to create a store within a render method, which causes store to get recreated. I don't see that issue here. Where am I going wrong?
//App.js
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from '../store/gymStore';
import Gym from '../components/Gym';
const App = props => (
<Provider store={configureStore(props)}>
<Gym />
</Provider>
);
export default App;
../store/gymStore.jsx
//the store creation.
/*
// my original way
import { createStore } from 'redux';
import gymReducer from '../reducers/';
const configureStore = railsProps => createStore(gymReducer, railsProps);
export default configureStore;
*/
/* possible fix: https://github.com/reactjs/react-redux/releases/tag/v2.0.0 */
/* but adding below does not resolve error */
import { createStore } from 'redux';
import rootReducer from '../reducers/index';
export default function configureStore(railsProps) {
const store = createStore(rootReducer, railsProps);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept(() => {
const nextRootReducer = require('../reducers').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
}
I am not sure my rendered component is necessary but in case it is:
//compenents/Gym.jsx
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import LeftMenu from './LeftMenu';
class Gym extends React.Component {
static propTypes = {
//name: PropTypes.string.isRequired // this is passed from the Rails view
};
/**
* #param props - Comes from your rails view.
*/
constructor(props) {
super(props);
this.state = {
current_user: this.props.current_user,
gyms: JSON.parse(this.props.gyms),
active_gym: 1, //JSON.parse(this.props.gyms)[0],
name: 'sean',
title: 'Gym Overview'
};
}
updateName = name => {
this.setState({ name });
};
isLoggedIn = () => {
if (this.state.current_user.id != '0') {
return <span className="text-success"> Logged In!</span>;
} else {
return <span className="text-danger"> Must Log In</span>;
}
};
isActive = id => {
if (this.state.active_gym == id) {
return 'text-success';
}
};
render() {
return (
<div className="content">
<h2 className="content-header">{this.state.title}</h2>
{LeftMenu()}
{this.state.current_user.id != '0' ? <span>Welcome </span> : ''}
{this.state.current_user.first_name}
<h3 className="content-header">Your Gyms</h3>
<ul>
{this.state.gyms.map((gym, key) => (
<li key={key} className={this.isActive(gym.id)}>
{gym.name}
</li>
))}
</ul>
{this.isLoggedIn()}
<hr />
{/*
<form>
<label htmlFor="name">Say hello to:</label>
<input
id="name"
type="text"
value={this.state.name}
onChange={e => this.updateName(e.target.value)}
/>
</form>
*/}
</div>
);
}
}
function mapStateToProps(state) {
return {
current_user: state.current_user,
gyms: state.gyms,
active_gym: state.active_gym
};
}
export default connect(mapStateToProps)(Gym);

Resources