Mapping redux state to props - state wiped out? - reactjs

I am using redux, redux-form and react. I fill in the form fields, submit it and then I would like to display entered values in the Result component.
Result.jsx
import React from 'react';
import { connect } from 'react-redux';
const Result = props => {
console.log(props);
return (
<div>
test
</div>
);
};
const mapStateToProps = state => {
return {
formData: state.form
};
}
export default connect(mapStateToProps)(Result);
EDIT:
import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './state/store';
import MainForm from './containers/MainForm';
import Result from './components/Result';
class App extends Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Route path="/" component={MainForm} exact />
<Route path="/submitted" component={Result} />
</div>
</BrowserRouter>
</Provider>
);
}
}
export default App;
EDIT2:
class MainForm extends Component {
state = {
submit: false
};
handleSubmit = data => {
this.setState({ submit: true });
};
render() {
const { handleSubmit, pristine, submitting, reset } = this.props;
const { submit } = this.state;
if (submit) {
return <Redirect to="/submitted" />;
}
return (
<Wrapper>
<h2>Some header</h2>
<FormWrapper onSubmit={handleSubmit(this.handleSubmit)}>
// FORM FIELDS..
<Button variant="contained" color="primary" type="submit" disabled={submitting}>
Submit
</Button>
<Button variant="contained" disabled={pristine || submitting} onClick={reset}>
Clear Values
</Button>
</FormWrapper>
</Wrapper>
);
}
}
MainForm = reduxForm({
form: 'main'
})(MainForm);
export default MainForm;
I console.log the props to check its value. It gets fired twice. The first time the props contain the form values I hoped for under props.formData.main.values. Therefore I assume the mapStateToProps worked fine. But then the console.log gets fired for the second time and props.formData is an empty Object. I also use the Redux chrome extension and I can clearly see that after form submit the data in the store are wiped out.
Could anyone explain to me this behaviour and how can I fix it? Thank you.

You're not 'consuming' submited data (dispatch action to save data in redux store?) in handler.
Probably form data are cleared after redirect for some reasons, f.e. reseting state, not holding old data for a new form on next page/url etc.

I got it fixed. The default behaviour of Redux-form is to throw away the form data once the form is unmounted. To prevent this default behaviour you have to set destroyOnUnmount to false like this:
MainForm = reduxForm({
form: 'main',
destroyOnUnmount: false // default is true
})(MainForm);
I hope this helps someone with the same problem.

Related

How to prevent component from being re-rendered unnecessarily

I'll start with the code. I have a stateless functional component that resembles this
export const Edit Topic = (_title, _text) {
const [title, setTitle] = useState(_title)
const [text, setText] = useState(_text)
return (
<>
<InputText props={{ fieldName:"Title:", value:title, setValue:setTitle, placeHolder:"Topic Title"}}/>
<InputTextArea props={{ fieldName:"Markdown Text:", text, setText }}/>
<PreviewBox text={text}/>
</>
)
}
I have PreviewBox when it's on, page rendering takes a bit longer because text can be quite long. PreviewBox needs to re-render each time I change text in InputTextArea and that's fine.
The problem I'm having is when I change the value of title it's also updating <PreviewBox/> which is undesired.
How can I make sure that <PreviewBox/> only updates when text changes and not when title changes?
The reason why I believe the re-rendering is occuring is because if I toggle off PreviewBox, there's no lag in when updating title but when PreviewBox is visible the updating the title lags.
import style from "../styles/CreateTopic.module.css"
import { Component } from "react"
import Markdown from "./Markdown";
export class PreviewBox extends Component {
constructor(props) {
super(props)
this.state = {
isShow: true
}
}
toggleShow = () => {
console.log("begin isShow", this.state)
this.setState(state => ({ isShow: !state.isShow}))
}
render() {
return (
<>
<div className={style.wrptoggle}>
<button className={style.btn} onClick={this.toggleShow}>Preview</button>
</div>
{this.state.isShow ?
<div className={style.wrppreviewbox}>
<div className={style.previewbox}>
<Markdown text={this.props.text}/>
</div>
</div>
: null}
</>
)
}
}
Since the above also contains <Markdown/> here's that component:
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";
const Markdown = ({text}) => {
return (
<div>
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
children={text}
/>
</div>
);
}
export default Markdown;
I don't see any complexity in PreviewBox that would cause any rendering delay so I might assume it's the Markdown component that may take some time "working" when it's rerendered since you say "toggle off PreviewBox, there's no lag in when updating title".
Solution
You can use the memo Higher Order Component to decorate the Markdown component and provide a custom areEqual props compare function.
import { memo } from 'react';
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";
const Markdown = ({ text }) => {
return (
<div>
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
children={text}
/>
</div>
);
};
export default memo(Markdown);
By default it will only shallowly compare complex objects in the props
object. If you want control over the comparison, you can also provide
a custom comparison function as the second argument.
const areEqual = (prevProps, nextProps) => {
return prevProps.text === nextProps.text;
};
export default memo(Markdown, areEqual);

using prompt of react-router in componentWillUnmount

i have a parent component which renders a custom form, i am using redux form on the custom form, what i am trying to do is whenever the child component gets unmounted if there are any values on the form i need to make a prompt to the user do you want to save it or not.
So below is the code, the prompt is not happening, i am using the state and updating the state using dirty(this.props) of redux form.
so after componentWillUnmount the render is not happening. What i am missing, i think after componentWillUnmount the render won't happen.
Please see the below code.
**//APP.js**
import React from 'react';
class App extends Component{
state ={
data = []// array of data
}
render(){
return(
<div>
<CustomForm
data={this.state.data}
/>
</div>
)
}
}
export default App
**//CustomForm.js**
import React from 'react';
import {connect} from 'react-redux';
import CustomField from './CustomField';
import { Field, reduxForm } from 'redux-form';
import {Prompt} from 'react-router'
class CustomForm extends Component{
state ={
formChanged: false
}
onSubmit = (values) => {
console.log(values)
}
componentWillUnmount(){
if(this.props.dirty){
this.setState({
formChanged: true
})
}
}
render(){
return(
<div>
{
this.state.formChanged?
<Prompt
when={shouldBlockNavigation}
message='You have unsaved changes, are you sure you want to leave?'
/>
:
null
}
<Form onSubmit={handleSubmit(this.onSubmit)}>
<Field
name="field1"
type="text"
component={CustomField}
label="field1"
/>
</Form>
</div>
)
}
}
export default reduxForm({
form: "NewForm",
destroyOnUnmount: true,
forceUnregisterOnUnmount: false
})(connect(mapStateToProps, mapDispatchToProps)(CustomForm))

My mapStateToProps is not passing props to component

I am really newbie in Redux development. Just started two days ago
Before, I used props - state pattern but I am going to change some parts of state - props pattern to Redux.
First, I will show my codes. index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import ItemList from './itemList';
import SearchList from './searchList';
import {Provider,connect} from 'react-redux';
import {store} from './storeReducer';
import {backToMain} from './reduxAction';
class App extends React.Component{
// not important some codes
render(){
return(
<div>
<section id="Search">
<form action="//localhost:80/ReactStudy/travelReduxApp/public/server/itemList.php" id="Search" className="search" method="GET" onSubmit={this.submitForm}>
<input ref={'search'} placeholder="search country, attraction names" type="text" name="search"/>
<button type="submit">SEARCH</button>
</form>
<p className={this.state.validateError.display}>
{this.state.validateError.type=='invalid value'?
'Only english letters are available in this input'
: (this.state.validateError.type=='not found')?
'Nothing was found with this keyword. Try again'
:'Empty text field'
}
</p>
</section>
<ItemContainer initializeSearch={this.initializeSearch} searchList={this.state.searchItem}/>
</div>
)
}
}
class ItemContainer extends React.Component{
state={
viewMain:true
}
//some codes
showMain=()=>{
this.setState({
viewMain:true
})
this.props.initializeSearch();
store.dispatch(backToMain());
}
render(){
console.log('Item container');
console.log(this.props);
return(
<section id="ItemContainer">
{
this.props.searchList!=''?
<SearchList searchList={this.props.searchList} mainVisible={this.state.viewMain}/>
:
<ItemList toggleView={this.toggleView} mainVisible={this.state.viewMain}/>
}
<button onClick={this.showMain}>Back to main</button>
</section>
)
}
}
const mapStateToProps =(state)=>{
console.log('working'); //it doesn't show it.
return{
visible:state.visible
}
};
const wrappedSearchList = connect(mapStateToProps,null)(ItemContainer);
const Root = () => (
<Provider store={store}>
<App/>
</Provider>
);
ReactDOM.render(
<Root/>,
document.getElementById('App')
)
reduxAction.js
export function backToMain(){
return{
type:'BACK_TO_MAIN'
}
}
storeReducer.js
import {createStore} from 'redux';
import {backToMain} from './reduxAction';
export const initialState = {
visible:true
}
export const store = createStore(viewMainReducer,initialState);
export function viewMainReducer(state=initialState,action){
switch(action.type){
case 'BACK_TO_MAIN':
console.log('Back..');
return{
...state,
visible:true
}
break;
default: return state;
}
}
I am really newbie in Redux so maybe I did not understand official document perfectly but I know mapStateToProps must pass state to the component as props. In my case, the component should be ItemContainer component.
Problem is when ItemContainer component is rendered.
If I check with
console.log(this.props)
in ItemContainer's render method , in console I see only initializeSearch and searchList are passed to the component. I cannot figure out why my visible property of initialState object is not passed to the component. What could be the reason? What should I do to pass visible property to ItemContainer component?
React / Redux: mapStateToProps not actually mapping state to props
read this thread but I just did not understand ...
Try using wrappedSearchList instead of ItemContainer
<wrappedSearchList initializeSearch={this.initializeSearch} searchList={this.state.searchItem}/>**strong text**

How to access redux form values in another component

I using Redux-Form 7.3.0. I am trying to get the values of my form in another component. I read the instruction at the website of Redux form but didn't work.
this is the code of my componenet:
import React from 'react'
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { formValueSelector } from 'redux-form';
class Test extends React.Component {
render() {
console.log(this.props);
return (
<div>
test
{this.props.title}
</div>
);
}
}
const selector = formValueSelector('NewPostForm');
Test = connect(
state => ({
title: selector(state, 'title')
})
)(Test)
export default Test;
This is my form component:
import React from 'react';
import { Field, reduxForm } from 'redux-form';
class NewPost extends React.Component {
renderField(field) {
return (
<div>
<label>{field.label}</label>
<input type="text" {...field.input} />
</div>
);
}
showResults(values) {
window.alert(`You submitted:\n\n${JSON.stringify(values, null, 2)}`);
}
render() {
const { pristine, submitting, handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.showResults)} >
<div>
<Field
label='Title'
name='title'
component={this.renderField}
/>
<button type='submit' disabled={submitting}>
Submit the from :)
</button>
</div>
</form>
);
}
}
export default reduxForm({ form: 'NewPostForm'})(NewPost);
but I always get
title:undefined
I found the same question here but it did not help me.
Your Test component has two imports from "redux-form". Please make it just one, like this:
import { Field, reduxForm, formValueSelector } from 'redux-form'
If your NewPost component gets unmounted at any moment, maybe by changing view or something during a navigation, the state of the form gets destroyed. You can avoid such default behavior by adding destroyOnUnmount attribute with a false value to your reduxForm wrapper:
export default reduxForm({
form: 'NewPostForm',
destroyOnUnmount: false
})(NewPost)
If this doesn't help you, please provide a better context of how you're using your components.
UPDATE: I made an example where I define 4 components:
NewPost.js: It's the form connected to the store with redux-form.
ShowPost.js: Shows what was captured (the post) by the form when you hit the submit button. This data is set to the NewPost internal state, and then it's passed as prop.
ShowPostFromSelector.js: Shows what is being captured by the form, this due to the use of the selector formValueSelector.
App.js: It's the container of the 3 components above, where the onSubmit function is defined.
Here it is: https://codesandbox.io/s/w06kn56wqk

can't type in input fields when using material-ui-redux-form due to re rendering

I'm using material-ui with redux. For some reason I can't type in my input fields whenever I follow the example provided at http://redux-form.com/6.2.0/examples/material-ui/ .
After using chrome redux dev tool I noticed that the state of the inputs is changing when I type but then it's re-rendering the entire component whenever something is typed, which makes it seem like nothing is being typed. Oddly enough, this only occurs when I use the Field component, as is used in the examples. If I just use material-ui components, the form allows typing and it doesn't re render. I've included the entire code to my component. Any help is much appreciated! What am I doing wrong?
import React, { Component } from 'react'
import {Field, reduxForm} from 'redux-form'
import { TextField } from 'redux-form-material-ui'
import RaisedButton from 'material-ui/RaisedButton'
class Login extends Component {
constructor (props) {
super(props)
this.handleFormSubmit = this.handleFormSubmit.bind(this)
}
componentDidMount () {
console.log(this.refs)
this.refs.username // the Field
.getRenderedComponent() // on Field, returns ReduxFormMaterialUITextField
.getRenderedComponent() // on ReduxFormMaterialUITextField, returns TextField
.focus() // on TextField
}
handleFormSubmit ({ username, password }) {
console.log(username, password)
}
render () {
const {
handleSubmit,
pristine,
submitting,
input,
fields: { username, password }
} = this.props
return (
<div className='loginWrapper'>
<form onSubmit={handleSubmit(this.handleFormSubmit)}>
<div id='loginNotch' />
<h1 className='loginHeader'>Login</h1>
<div>
<Field
component={TextField}
name='username'
floatingLabelText='Username'
ref='username' withRef />
</div>
<div>
<Field
component={TextField}
type='password'
name='password'
floatingLabelText='Password'
ref='password' withRef />
</div>
<div>
<RaisedButton
label='Go'
primary />
</div>
</form>
</div>
)
}
}
// TODO: keep property names consistent with server
export default reduxForm({
form: 'login',
fields: ['username', 'password']
})(Login)
Update: I took a look at the docs and removed fields from the export, and it is still not working.
You can clone project from here https://bitbucket.org/kvoth3/loanpayments.git
it's just a simple login screen
Try changing your reducer to
const rootReducer = combineReducers({
form: authReducer
})
ReduxForm expects your redux state structure to be
{
form: {
formName: {}
}
}
If you need to use a different name other than form, you need to provide a getFormState(state) to the reduxForm() decorator.

Resources