i try to figure out what's wrong in my code. can someone help me find out whats wrong. i use webstorm for text editing, and it's show "unresolve variable" in
this.props.loginUser(value);
is there any related to this?
this is my code :
import React, { Component } from 'react'
import {reduxForm, Field} from 'redux-form';
import { loginUser } from '../actions/index';
import { Stores } from '../Stores';
import {connect} from 'react-redux';
const validate = values => {
const errors = {};
if (!values.email) {
errors.email = 'Required'
} else if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address'
}
if (!values.password) {
errors.password = 'Required'
}
return errors
};
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} className="form-control"/>
{touched && error && <span className="alert alert-danger">{error}</span>}
</div>
</div>
);
function submit(value){
console.log(value);
this.props.loginUser(value); //didn't work
// Stores.dispatch(loginUser({email,password})); //this method work
}
class LoginV6 extends Component{
render() {
const {handleSubmit, pristine, reset, submitting} = this.props;
return (
<div className="row">
<div className="col-md-6">
<form onSubmit={handleSubmit(submit)}>
<div className="form-group">
<Field
name="email"
type="text"
component={renderField}
label="Email"
/>
</div>
<div className="form-group">
<Field
name="password"
type="password"
component={renderField}
label="Password"
/>
</div>
<div>
<button type="submit" className="btn btn-primary" disabled={pristine||submitting}>
Login
</button>
<button type="button" className="btn btn-primary" disabled={pristine || submitting}
onClick={reset}>
Clear Values
</button>
</div>
</form>
</div>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
loginUser() {
dispatch({loginUser(value));
}
}
};
function mapStateToProps(state) {
return {
errorMessage: state.auth.error,
authenticated:state.auth.authenticated
}
}
LoginV6 = reduxForm({
form:'LoginV6',
validate
})(LoginV6);
export default LoginV6 = connect(mapStateToProps, mapDispatchToProps)(LoginV6);
this is my action code :
import axios from 'axios';
import jwtdecode from 'jwt-decode';
import {browserHistory} from 'react-router';
import {
AUTH_USER,
AUTH_ERROR,
USER_INFO_SUCCESS,
USER_INFO,
LOGOUT_USER,
GET_TABPANEL,
GET_SETUP_TABTITLES,
} from './types';
const ROOT_URL = 'http://localhost:8000';
// User and Auth actions
//
export function loginUser({email,password}){
return function(dispatch){
axios.post(`${ROOT_URL}/api/auth/login`,{email,password})
.then(response => {
dispatch({type: AUTH_USER,
payload:response.data.token
});
localStorage.setItem('laravel_user_token',response.data.token);
console.log('Login Success');
browserHistory.push("/");
}).catch(()=>{
dispatch(authError("Empty Required Field"));
});
}
}
if i use Stores.dispatch(loginUser(value)); it works.
Change your code like this:
class LoginV6 extends Component{
constructor(){
this.submit = this.submit.bind(this); //Because ES6 is not auto bind
}
submit(value){
//...your code
this.props.loginUser(value); //it will work
// or "loginUser" is an action
this.props.dispatch(loginUser(value)); //see the new function mapDispatchToProps
}
render(){
//...your code
}
}
// ....
// instead map a new object, we map dispatch directly
// with this function, we avoid duplicated/confused about actions in case you have many actions
const mapDispatchToProps = (dispatch) => {
return {
dispatch
}
};
i already fix this by change my code to this :
class LoginV6 extends Component{
submit = (data) => {
console.log(data);
this.props.loginUser(data);
}
render() {
const {handleSubmit, pristine, reset, submitting} = this.props;
return (
<div className="row">
<div className="col-md-6">
<form onSubmit={handleSubmit(this.submit)}>
..............................the rest of code..............................................
const mapDispatchToProps = (dispatch) => {
return {
loginUser: (data) => {
dispatch(loginUser(data))
}
}
}
function mapStateToProps(state) {
return {
errorMessage: state.auth.error,
authenticated:state.auth.authenticated
}
}
LoginV6 = reduxForm({
form:'LoginV6',
validate
})(LoginV6);
export default LoginV6 = connect(mapStateToProps, mapDispatchToProps)(LoginV6);
Related
I'm trying to make an edit user profile form in React + Redux Form + Firebase, my problem is that I can't enter any text in generated form in Redux Form. I try to enter some text to this but it's disabled...
Actually, initially I fetch user profile data (and it works). Then I'm trying to build edit form for editing profile.
class Settings extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchProfile();
}
renderField = field => {
const {
input,
label,
type,
meta: { touched, error }
} = field;
return (
<div className="formItem">
<p>{label}</p>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
);
};
onSubmit = values => {
const { uid } = this.props.room;
this.props.updateProfile(uid, values);
};
render() {
const { handleSubmit, pristine, submitting, invalid, room } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.onSubmit)}>
<Field
label="Your name"
name="name"
type="text"
value={user.name}
component={this.renderField}
/>
<div className="formBtn">
<input
type="submit"
className="btn btnPrimary"
value="Save Changes"
disabled={pristine || submitting || invalid}
/>
</div>
</form>
</div>
);
}
}
const validate = values => {
const errors = {};
console.log(values);
if (!values.name) errors.name = "Enter your name";
return errors;
};
const mapDispatchToProps = { fetchProfile, updateProfile };
function mapStateToProps({ users }) {
return { user: users };
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(
reduxForm({ validate, form: "profileForm", enableReinitialize: true })(
Settings
)
);
You do not need to provide value to Field component, instead you need to pass values as initialValues in mapStateToProps:
function mapStateToProps({ users }) {
return {
user: users,
initialValues: {
name: users.name,
},
};
}
import { createStore, applyMiddleware, combineReducers } from "redux";
import reduxThunk from "redux-thunk";
import { reducer as formReducer } from "redux-form";
const rootReducer = combineReducers({
form: formReducer
});
const store = createStore(rootReducer, applyMiddleware(reduxThunk));
export default store;
I have added formReducer, it works.
I crafted a reactjs crud app with help of a tutorial and it works great now. Now i am trying to merge two form together so that same form should be used for both add and update operation.
This is my allpost.js file
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Post from '../components/Post';
import EditComponent from '../components/editComponent';
class AllPost extends Component {
render() {
return (
<div>
<h1>All Posts</h1>
{this.props.posts.map((post) => (
<div key={post.id}>
{post.editing ? <EditComponent post={post} key={post.id} /> :
<Post key={post.id} post={post} />}
</div>
))}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
posts: state
}
}
export default connect(mapStateToProps)(AllPost);
and this is my postForm.js file:
import React, { Component } from 'react';
import { connect } from 'react-redux'
class PostForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
const title = this.getTitle.value;
const message = this.getMessage.value;
const data = {
id: new Date(),
title,
message,
editing: false
}
console.log(data)
this.props.dispatch({
type: 'ADD_POST',
data,
});
this.getTitle.value = '';
this.getMessage.value = '';
}
render() {
return (
<div>
<h1>Create Post</h1>
<form onSubmit={this.handleSubmit}>
<input required type="text" ref={(input)=>this.getTitle = input}
placeholder="Enter Post Title"/>
<br /><br />
<textarea required rows="5" ref={(input)=>this.getMessage = input} cols="28"
placeholder="Enter Post" />
<br /><br />
<button>Post</button>
</form>
</div>
);
}
}
export default connect()(PostForm);
and this is my editComponent.js file
import React, { Component } from 'react';
import { connect } from 'react-redux';
class EditComponent extends Component {
handleEdit = (e) => {
e.preventDefault();
const newTitle = this.getTitle.value;
const newMessage = this.getMessage.value;
const data = {
newTitle,
newMessage
}
this.props.dispatch({ type: 'UPDATE', id: this.props.post.id, data: data })
}
render() {
return (
<div>
<form onSubmit={this.handleEdit}>
<input required type="text" ref={(input) => this.getTitle = input}
defaultValue={this.props.post.title} placeholder="Enter Post Title" /><br /><br />
<textarea required rows="5" ref={(input) => this.getMessage = input}
defaultValue={this.props.post.message} cols="28" placeholder="Enter Post" /><br /><br />
<button>Update</button>
</form>
</div>
);
}
}
export default connect()(EditComponent);
and this is my post.js file:
import React, { Component } from 'react';
import { connect } from 'react-redux'
class Post extends Component {
render() {
return (
<div>
<h2>{this.props.post.title}</h2>
<p>{this.props.post.message}</p>
<button onClick={() => this.props.dispatch({type: 'EDIT_POST', id: this.props.post.id})}>EDIT
</button>
<button onClick={ () => this.props.dispatch({type: 'DELETE_POST', id: this.props.post.id}) }>DELETE
</button>
</div>
);
}
}
export default connect()(Post);
and this is my postReducer.js file:
const postReducer = (state = [], action) => {
switch(action.type) {
case 'ADD_POST':
return state.concat([action.data]);
case 'DELETE_POST':
return state.filter((post)=>post.id !== action.id);
case 'EDIT_POST':
return state.map((post)=>post.id === action.id ? {...post,editing:!post.editing}:post)
case 'UPDATE':
return state.map((post)=>{
if(post.id === action.id) {
return {
...post,
title:action.data.newTitle,
message:action.data.newMessage,
editing: !post.editing
}
} else return post;
})
default:
return state;
}
}
export default postReducer;
Can anyone please help me to achieve this? I tried a lot to use same form form for both add and update and i failed to achieve this.
I think it's better you create separate component for rendering form data(FormComponent) and separate components for edit(EditComponent) and add(AddComponent).
This way there will not be clutter in one component and no if/else conditions for different modes like edit or add, or in future copy mode.
This approach will add flexibility and enhances compositional pattern of react.
1) AddComponent
import React, { Component } from 'react';
import { connect } from 'react-redux'
class AddComponent extends Component {
handleSubmit = (title, message) => {
const data = {
id: new Date(),
title,
message,
editing: false
}
this.props.dispatch({
type: 'ADD_POST',
data,
});
}
render() {
return (
<div>
<h1>Create Post</h1>
<FormComponent
buttonLabel='Post'
handleSubmit={this.handleSubmit}
/>
</div>
);
}
}
export default connect()(AddComponent);
2) EditComponent
import React, { Component } from 'react';
import { connect } from 'react-redux';
class EditComponent extends Component {
handleSubmit = (newTitle, newMessage) => {
const data = {
newTitle,
newMessage
}
this.props.dispatch({ type: 'UPDATE', id: this.props.post.id, data: data })
}
render() {
return (
<div>
<FormComponent
buttonLabel='Update'
handleSubmit={this.handleSubmit}
/>
</div>
);
}
}
export default connect()(EditComponent);
3) FormComponent
import React, { Component } from 'react';
class FormComponent extends Component {
handleSubmit = (e) => {
e.preventDefault();
const title = this.getTitle.value;
const message = this.getMessage.value;
this.props.handleSubmit(title, message);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input required type="text" ref={(input) => this.getTitle = input}
defaultValue={this.props.post.title} placeholder="Enter Post Title" /><br /><br />
<textarea required rows="5" ref={(input) => this.getMessage = input}
defaultValue={this.props.post.message} cols="28" placeholder="Enter Post" /><br /><br />
<button>{this.props.buttonLabel}</button>
</form>
);
}
}
export default FormComponent;
Hope that helps!!!
You can create your own Form component with a prop of editMode to control whether it's Create or Update.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
class Form extends Component {
handleSubmit = e => {
e.preventDefault();
const {editMode, post} = this.props;
const title = this.titleRef.value;
const body = this.bodyRef.value;
if (editMode){
const data = {
title,
body
}
this.props.dispatch({type: 'UPDATE', id: post.id, data})
}
else {
const data = {
id: new Date(),
title,
message,
editing: false
}
this.props.dispatch({type: 'ADD_POST', data});
}
}
render() {
const {editMode, post} = this.props;
const pageTitle = editMode ? 'Edit Post' : 'Create Post';
const buttonTitle = editMode ? 'Update' : 'Post';
return (
<div>
<h1>{pageTitle}</h1>
<form onSubmit={this.handleSubmit}>
<input
required
type="text"
ref={input => this.titleRef = input}
placeholder="Enter Post Title"
defaultValue={post.title}
/>
<textarea
required
rows="5"
ref={input => this.bodyRef = input}
cols="28"
placeholder="Enter Post"
defaultValue={post.body}
/>
<button>{buttonTitle}</button>
</form>
</div>
);
}
}
Form.propTypes = {
editMode: PropTypes.bool,
post: PropTypes.object
}
Form.defaultProps = {
editMode: false, // false: Create mode, true: Edit mode
post: {
title: "",
body: ""
} // Pass defined Post object in create mode in order not to get undefined objects in 'defaultValue's of inputs.
}
export default Form;
It would be on create mode by default but if you wanna update the post you should pass editMode={true} to your form component.
I'm testing a reduxForm component with jest and enzyme and I don't know how i can test methods within this component.
I'm exporting the component and testing it itself, I have access to the methods but the component don't recognize the reduxForm field props like meta, touched, error, input e.t.c.
How I can mock these or test my component with reduxForm, not as a separate one
The tested component is Login component and must render 2 fields.
This is the Login component:
import React, { Component } from 'react'
//import { Link } from 'react-router-dom'
import { Field, reduxForm } from 'redux-form'
import '../../style/style.css'
export class Login extends Component {
renderField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="username_field" placeholder={field.label} type="text" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
renderPasswordField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="password_field" placeholder={field.label} type="password" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
onSubmit(values) {
this.props.history.push('/landing')
}
render() {
const { handleSubmit } = this.props
return (
<div>
<div className="login-form">
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<h2 className="text-center">TQI Log in</h2>
<div className="form-group">
<Field id="username" name="username" label="username" component={this.renderField} />
</div>
<div className="form-group">
<Field id="password" name="password" label="password" component={this.renderPasswordField} />
</div>
<div className="form-group">
<button id="login_button" type="submit" className="btn btn-primary btn-block btn-clicked">Login </button>
</div>
</form>
</div>
</div>
);
}
}
export const validate = (values) => {
const errors = {}
const dummyData = {
username: 'admin',
password: 'admin'
}
// Validate the inputs from values
if(!values.username) {
errors.username = "Enter a username"
} else if(values.username !== dummyData.username){
errors.username = "Wrong username"
}
if(!values.password) {
errors.password = "Enter a password"
} else if( values.username === dummyData.username && values.password !== dummyData.password){
errors.password = "Wrong password"
}
// if errors is empty, the form is fine to submit
// If errors has *any* properties, redux forms assumes form is invalid
return errors
}
export default reduxForm({
validate,
form: 'LoginForm'
})(Login)
This is the testing file:
import React from 'react'
import { shallow, mount } from 'enzyme'
import { Login } from '../../components/login/login'
import { validate } from '../../components/login/login'
const mockHandleSubmit = jest.fn()
const renderField = jest.fn()
const mockRenderPasswordField = jest.fn()
const wrapper = shallow(<Login handleSubmit={mockHandleSubmit} />)
const loginButton = wrapper.find('#login_button')
describe('<Login /> render', () => {
it('render the <Login /> component without crashing', () => {
expect(wrapper.length).toEqual(1)
})
it(' call handlesSubmit when submit button is clicked', () => {
loginButton.simulate('click')
expect(mockHandleSubmit).toHaveBeenCalled()
})
})
describe('renderField and renderPasswordField functions', () => {
it('renderField', () => {
expect(wrapper.instance().renderField()).toEqual(1)
})
})
And the error it gives me
To mount a reduxForm wrapped component, you need to wrap your component in
mount(
<Provider store={store}>
<Login {...props}/>
</Provider>
)
note that you need to create a custom store beforehand like this
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
store = createStore(combineReducers({ form: formReducer }))
You may refer to this repo for how to test redux form properly with integration and unit tests.
https://github.com/tylercollier/redux-form-test
Im struggling to find a solution when testing a redux-form component. The problem is that only when I test simply if the component is rendering it gives me an error: "TypeError: handleSubmit is not a function", but the app is working fine, as expected.
I've tried to solve it just to make handleSubmit a function and not taking it from props, but then the app doesn't work. When the submit form is correct it must navigate to /landing page, but instead just re-render the login component.
The component:
import React, { Component } from 'react'
//import { Link } from 'react-router-dom'
import { Field, reduxForm } from 'redux-form'
import '../../style/style.css'
export class Login extends Component {
renderField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="username_field" placeholder={field.label} type="text" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
renderPasswordField(field) {
const { meta: { touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<input className="form-control" id="password_field" placeholder={field.label} type="password" {...field.input} />
<div className="text-help">
{ field.meta.touched ? field.meta.error : '' }
</div>
</div>
)
}
onSubmit(values) {
this.props.history.push('/landing')
}
// DOESN'T WORK!!!
// handleSubmit(formValues){
// //console.log(formValues);
// }
render() {
const { handleSubmit } = this.props
return (
<div>
<div className="login-form">
<form onSubmit={ /*this.*/handleSubmit(this.onSubmit.bind(this))}>
<h2 className="text-center">TQI Log in</h2>
<div className="form-group">
<Field id="username" name="username" label="username" component={this.renderField} />
</div>
<div className="form-group">
<Field id="password" name="password" label="password" component={this.renderPasswordField} />
</div>
<div className="form-group">
<button id="login_button" type="submit" className="btn btn-primary btn-block">Login </button>
</div>
</form>
</div>
</div>
);
}
}
function validate(values) {
const errors = {}
const dummyData = {
username: 'admin',
password: '123'
}
// Validate the inputs from values
if(!values.username) {
errors.username = "Enter a username"
} else if(values.username !== dummyData.username){
errors.username = "Wrong username"
}
if(!values.password) {
errors.password = "Enter a password"
} else if( values.username === dummyData.username && values.password !== dummyData.password){
errors.password = "Wrong password"
}
// if errors is empty, the form is fine to submit
// If errors has *any* properties, redux forms assumes form is invalid
return errors
}
export default reduxForm({
validate,
form: 'LoginForm'
})(Login)
The testing file:
import React from 'react'
import { shallow } from 'enzyme'
import { Login } from './login'
describe('<Login />', () => {
it('render the <Login /> component without crashing', () => {
const wrapper = shallow(<Login />)
expect(wrapper.length).toEqual(1)
})
// it('navigate to /landing page when the form is submit correctly', () => {
// })
})
You are consuming a prop function from your HOC reduxForm on your render method. But on the test file, you are importing the component without the HOC on top of it, which means that prop/function is not available. You have to provide Login with a mock handleSubmit prop function.
Try:
import React from 'react'
import { shallow } from 'enzyme'
import { Login } from './login'
describe('<Login />', () => {
it('render the <Login /> component without crashing', () => {
const wrapper = shallow(<Login handleSubmit={() => {}} />)
expect(wrapper.length).toEqual(1)
})
})
you need pass onSubmit on initialization:
export default reduxForm({
validate,
form: 'LoginForm',
onSubmit: this.onSubmit // here
})(Login)
or in props:
<Component>
<Login onSubmit={this.onSubmit}/>
</Component>
First export the redux-form decorated class
export const DecoratedLogin = reduxForm({
validate,
form: 'LoginForm'
})(Login);
Then use it instead of the plain class.
import { DecoratedLogin } from './login'
and you should be able to access the props from redux-form.
I am using react/redux with redux-form and for some reason the input values are not showing on my edit form.
I console.log my post and it shows that they are there, but for some reason it is not working. I will post code below.
edit component:
import React , { Component } from 'react';
import * as actions from '../../actions/posts_actions';
import { reduxForm, Field } from 'redux-form';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
class EditPost extends Component {
componentDidMount() {
const {id} = this.props.match.params;
this.props.getOnePost(id);
}
renderField(field) {
const { meta: {touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<label><strong>{field.label}:</strong></label>
<input
className="form-control"
type={field.type}
value={field.value}
{...field.input}
/>
<div className="text-help">
{ touched ? error : ''}
</div>
</div>
)
}
onSubmit(values) {
const {id} = this.props.match.params;
this.props.updatePost(values, id, () => {
this.props.history.push(`/posts/${id}`);
});
}
render() {
const {handleSubmit} = this.props;
const {post} = this.props;
if(!post) {
return <div> Loading... </div>;
}
console.log(post);
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label="Title"
name="title"
type="text"
value={post.title}
component={this.renderField}
/>
<Field
label="Content"
name="content"
type="text"
value={post.content}
component={this.renderField}
/>
<button type="submit" className="btn btn-success">Submit</button>
<Link to={`/posts/${post._id}`} className="btn btn-danger">Cancel</Link>
</form>
);
}
}
function validate(values) {
const errors = {};
if(!values.title) {
errors.title = "Enter a title!";
}
if(!values.content) {
errors.content = "Enter some content please!";
}
return errors;
}
function mapStateToProps({ posts }, ownProps) {
return { post: posts[ownProps.match.params.id] };
}
export default reduxForm({
validate,
form: 'editform'
})(connect(mapStateToProps, actions)(EditPost));
Instead of setting the value directly, pass an initialValues prop:
function mapStateToProps({ posts }, ownProps) {
const post = posts[ownProps.match.params.id]
return {
post,
initialValues: {
...post
}
};
}
remove value props from Fields in your form. So long as the properties on your Post object are the same as the name of the field you want to populate, you can just use object spread. If they are different, you'll have to map them appropriately
initialValues: {
contentField: post.content
}
<Field name="contentField" />
initial values have to be set before you use the reduxForm enhancer. Also, because I know it'll come up (and trust me, it always comes up eventually), if you want your form values to update if your model updates, you'll have add enableReinitialize: true to reduxForm's config