Called React-Redux action in prop does not give needed results - react-redux

I am implementing a component that handles an redux action to add comments but it is not working.No error is generated
I have tried calling the props from other regions in the code but that doesnt seem to work.The addComment Action should add the comments rendered in the DishDetails comments section.However no additions are made.
ActionTypes.js
export const ADD_COMMENT='ADD_COMMENT';
ActionCreators.js
import * as ActionTypes from './ActionTypes';
export const addComment=(dishId,rating, author, comment)=>({
type: ActionTypes.ADD_COMMENT,
payload: {
dishId:dishId,
rating:rating,
author:author,
comment:comment
}
});
comments.js
import { COMMENTS } from '../shared/comments';
import * as ActionTypes from './ActionTypes';
export const Comments= (state= COMMENTS, action) => {
switch(action.type){
case ActionTypes.ADD_COMMENT:
var comment= action.payload;
comment.id= state.length;
comment.date = new Date().toISOString();
return state.concat(comment);
default:
return state;
}
};
MainComponent.js
import React, { Component } from 'react';
import Header from './HeaderComponent';
import Footer from './FooterComponent';
import Menu from './MenuComponent';
import DishDetail from './DishDetail';
import Home from './HomeComponent';
import { Switch, Route, Redirect, withRouter } from 'react-router-dom';
import Contact from './ContactComponent';
import About from './AboutComponent';
import { connect } from 'react-redux';
import {addComment} from '../redux/ActionCreators';
const mapStateToProps = state =>{
return{
dishes: state.dishes,
comments: state.comments,
promotions: state.promotions,
leaders: state.leaders
}
};
const mapDispatchToProps = dispatch => ({
addComment: (dishId,rating, author, comment)=>dispatch(addComment(dishId,rating, author, comment))
});
class Main extends Component {
constructor(props) {
super(props);
}
render() {
const HomePage= ()=>{
return(
<Home dish={this.props.dishes.filter((dish)=>dish.featured)[0]}
promotion={this.props.promotions.filter((promotion)=>promotion.featured)[0]}
leader={this.props.leaders.filter((leader)=>leader.featured)[0]}
/>
);
}
const DishWithId = ({match})=>{
return(
<DishDetail dish={this.props.dishes.filter((dish)=>dish.id === parseInt(match.params.dishId,10))[0]}
comments={this.props.comments.filter((comment)=>comment.dishId=== parseInt(match.params.dishId,10))}
addComment={this.props.addComment}/>
);
}
const AboutPage = ()=>{
return(
<About leaders={this.props.leaders}/>
);
}
return (
<div>
<Header/>
<Switch>
<Route path="/home" component={HomePage} />
<Route exact path="/menu" component={()=><Menu dishes ={this.props.dishes} />} />
<Route path="/menu/:dishId" component={DishWithId}/>
<Route exact path="/aboutus" component={() => <AboutPage leaders={this.props.leaders} />} />}/>
<Route exact path="/contactus" component={Contact}/>
<Redirect to="/home"/>
</Switch>
<Footer/>
</div>
);
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Main));
DishDetail.js
import React, { Component } from 'react';
import { Card, CardImg, CardImgOverlay, CardText, CardBody, CardTitle, Breadcrumb, BreadcrumbItem , Button, Modal, ModalHeader,ModalBody, Form, FormGroup, Input, Label, Col, Row } from 'reactstrap';
import {Control, LocalForm, Errors} from 'react-redux-form';
import {Link} from 'react-router-dom';
const required = (val) =>val && val.length;
const maxLength = (len) => (val) => !(val) || (val.length <= len);
const minLength = (len) => (val) => val && (val.length >= len);
class DishDetail extends Component{
constructor(props){
super(props);
this.state={
dish:props.dish,
isCommentModalOpen: false,
};
this.toggleCommentModal=this.toggleCommentModal.bind(this);
}
toggleCommentModal(){
this.setState({
isCommentModalOpen:!this.state.isCommentModalOpen
});
}
handleSubmit(props,values){
alert("State" + JSON.stringify(props.addComment(props.dishId, values.rating, values.author, values.comment)));
// this.state.addComment(this.state.dishId, values.rating, values.author, values.comment)
}
render(){
const RenderDish=({dish})=>{
return(
<Card>
<CardImg top src={dish.image} alt={dish.name}/>
<CardBody>
<CardTitle>{dish.name}</CardTitle>
<CardText>{dish.description}</CardText>
</CardBody>
</Card>
);
}
const RenderComments=({comments})=>{
const comment_layout= comments.map((comment)=>{
if(comment.comment!=null){
return(
<div>
{comment.comment}
{comment.author}, {new Intl.DateTimeFormat('en-US',{year:'numeric',month:'short',day:'2-digit'}).format(new Date(Date.parse(comment.date)))}
</div>
);
}else{
return(
<div></div>
);
}
});
return(comment_layout);
}
const CommentForm=()=>{
return(
<Button outline onClick={this.toggleCommentModal}>
<span className="fa fa-edit fa-lg">Submit Comment</span>
</Button>
);
}
if (this.state.dish!==undefined){
return (
<div className="container">
<div className="row">
<Breadcrumb>
<BreadcrumbItem>
<Link to="/menu">Menu</Link>
</BreadcrumbItem>
<BreadcrumbItem active>{this.state.dish.name}
</BreadcrumbItem>
</Breadcrumb>
<div className="col-12">
<h3>{this.state.dish.name}</h3>
<hr/>
</div>
</div>
<div className="row ">
<div className="col-12 col-md-5 m-1">
<RenderDish dish={this.state.dish}/>
</div>
<div className="col-md-5 col-sm-12 m-1">
<h4>Comment</h4>
<RenderComments comments={this.props.comments}
/>
<CommentForm/>
</div>
</div>
<Modal isOpen={this.state.isCommentModalOpen} toggle={this.toggleCommentModal}>
<ModalHeader toggle={this.toggleCommentModal}>
Submit Comment </ModalHeader>
<ModalBody>
<div className="col-12">
<LocalForm onSubmit={(values)=>this.handleSubmit(this.props,values)}>
<Row className="form-group">
<Label htmlFor ="rating" md={2}>Rating</Label>
<Control.select model=".rating" id="rating" name="rating" className="form-control">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</Control.select>
</Row>
<Row className="form-group">
<Label htmlFor ="name" md={2}>Name</Label>
<Control.text model=".name" className="form-control" id="name" name="name" placeholder="name" validators={{required,minLength: minLength(3),maxLength:maxLength(15)}} />
<Errors className="text-danger"
model=".name"
show="touched"
messages={{
required:'Required',
minLength:'Must be greater than 2 char',
maxLength: 'Must be 15 chars or less'
}}
/>
</Row>
<Row className="form-group">
<Label htmlFor ="feedback" md={2}>Comment</Label>
<Control.textarea model=".message" className="form-control" id="message" name="message" rows="12" />
</Row>
<Row className="form-group">
<Button type="submit" color="primary">
Submit
</Button>
</Row>
</LocalForm>
</div>
</ModalBody>
</Modal>
</div>
);
}else{
return(
<div></div>
);
}
}
}
export default DishDetail;

you are not dispatching to the reducer in the action. Do it like this
export const addComment = (dishId, rating, author, comment) => {
return (dispatch) => {
dispatch({
type: ActionTypes.ADD_COMMENT,
payload: {
dishId: dishId,
rating: rating,
author: author,
comment: comment
}
})
}
};

Related

use of mapstatetoprops and mapdispatchtoprops in react final form

I have an application where I have used redux-form version 5.3.6, now I want to migrate from it to react-final-form. As I have mostly used Redux to manage form data while using redux-form, now while I am migrating, I find it very hard to integrate my redux codes with the final form implementation mentioned in the migration doc: https://final-form.org/docs/react-final-form/migration/redux-form
I am finding it difficult to use my mapStateToProps, mapDispatchToProps, and other redux-related functions with the final-form implementation.
Here is my current form container code from where form data is getting connected with redux via redux-form
import React from 'react';
import PropTypes from 'prop-types';
import { reduxForm } from 'redux-form';
import moment from 'moment';
import { startFromCart, endFromCart } from '../../lib/datesFromCart';
import { validateAllSearchParams } from './helpers/validate';
import cartOverride from './helpers/cartOverride';
import deepClone from '../../lib/deepClone';
import SearchForm from './SearchForm';
import searchAccountValues from '../../helpers/searchAccountValues';
import { createCarSearch } from '../../actions/car';
import { resetApp } from '../../actions/common';
const initialFields = [
'pickUpDate',
'dropOffDate',
'pickUpTime',
'dropOffTime',
'airportSearch',
'pickUpLocationAirportCode',
'pickUpGeoLocationAddress',
'pickUpGeoLocationLatitude',
'pickUpGeoLocationLongitude',
'dropOffLocationAirportCode',
'dropOffGeoLocationAddress',
'dropOffGeoLocationLatitude',
'dropOffGeoLocationLongitude',
'driverAge',
'vendorPreferences.codes',
'vendorPreferences.type',
];
const reduxFormConfig = {
form: 'carSearchForm',
destroyOnUnmount: false,
fields: [],
};
const mapStateToProps = (state, ownProps) => {
const { adults } = state.common.travelerCount;
const fields = deepClone(initialFields);
const cartHasCar = state.common.cart.items.some(item => item.type === 'car');
if (ownProps.searchAccounts && ownProps.searchAccounts.cars) {
Object.keys(ownProps.searchAccounts.cars).forEach(key => fields.push(`accountSettings.${key}`));
}
const startDateFromCart = startFromCart(state);
const endDateFromCart = endFromCart(state);
let vendorsPreferences = []
if (ownProps.globalPolicyPreferences.carRentalCompanies.length > 0) {
vendorsPreferences = ownProps.globalPolicyPreferences.carRentalCompanies
}
const defaultSearchParams = {
accountSettings: searchAccountValues(ownProps.searchAccounts.cars),
airportSearch: false,
driverAge: ownProps.currentUserAge || 32,
pickUpDate:
startDateFromCart ||
state.cars.defaultCarSearchParams.pickUpDate ||
moment()
.add(7, 'days')
.startOf('day')
.format('YYYY-MM-DD'),
dropOffDate: endDateFromCart || state.cars.defaultCarSearchParams.dropOffDate,
pickUpTime: '12:00',
dropOffTime: '11:00',
vendorPreferences: state.cars.defaultCarSearchParams.vendorPreferences || {
codes: vendorsPreferences,
type: 'exclusive',
},
};
const searchForm = state.form.carSearchForm;
const preselectTravelers = state.common.preselectTravelers.travelers;
const cartHasItems = state.common.cart.items.length > 0;
return {
fields,
cartHasCar,
initialValues: {
...defaultSearchParams,
...ownProps.searchParams,
...state.cars.defaultCarSearchParams,
...cartOverride(adults),
},
validate: validateAllSearchParams(ownProps.availableChannels.cars, ownProps.minHoursInFuture),
defaultSearchParams,
preselectTravelers,
searchForm,
cartHasItems,
travelerLoggedIn: ownProps.travelerLoggedIn,
};
};
const mapDispatchToProps = dispatch => ({
createCarSearch: (params, callbacks) => dispatch(createCarSearch(params, callbacks)),
resetApp: () => dispatch(resetApp('/cars')),
});
const ReduxForm = reduxForm(reduxFormConfig, mapStateToProps, mapDispatchToProps)(SearchForm);
const SearchFormWithContext = (props, context) => <ReduxForm {...props} {...context} />;
SearchFormWithContext.contextTypes = {
availableChannels: PropTypes.object,
searchAccounts: PropTypes.object,
laymanMode: PropTypes.bool,
currency: PropTypes.string,
callbackParams: PropTypes.object,
minHoursInFuture: PropTypes.number.isRequired,
currentUserAge: PropTypes.number.isRequired,
globalPolicyPreferences: PropTypes.shape({
airlines: PropTypes.arrayOf(PropTypes.number).isRequired,
carRentalCompanies: PropTypes.arrayOf(PropTypes.number).isRequired,
}).isRequired,
};
export default SearchFormWithContext;
The component is
import React, { useEffect, Fragment, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import moment from 'moment';
import { I18nText } from '#wtag/react-comp-lib';
import { Menu, MenuItem } from '#wtag/rcl-menu';
import Alert from '#wtag/rcl-alert';
import SelectBox from '#wtag/rcl-select-box';
import Input from '#wtag/rcl-input';
import Button from '#wtag/rcl-button';
import Icon from '#wtag/rcl-icon';
import LocationSearchInput from 'sharedWebpack/LocationSearchInput';
import DatePickerWithFocusedStateWrapper from 'sharedWebpack/DatePickerWithFocusedStateWrapper';
import TimeSelectBox from 'sharedWebpack/TimeSelectBox';
import availableChannelsShape from '../../shapes/availableChannels';
import noChannelSelected from '../../lib/helpers/noChannelSelected';
import showInvalidDateError from '../../helpers/showInvalidDateError';
import history from '../../lib/history';
import { showError } from '../../actions/common';
import AirportAutoComplete from '../AirportAutoComplete';
import VendorSelectBox from './VendorSelectBox';
import SearchAccounts from '../../components/SearchAccounts';
import searchAccountsShape from '../../shapes/searchAccounts';
import PreselectTravelers from '../PreselectTravelers';
import preparePreselectTravelers from '../PreselectTravelers/preparePreselectTravelers';
import useSearchFormContainer from '../../helpers/hooks/useSearchFormContainer';
const SearchForm = props => {
const {
fields: {
airportSearch,
pickUpLocationAirportCode,
dropOffLocationAirportCode,
pickUpGeoLocationAddress,
pickUpGeoLocationLatitude,
pickUpGeoLocationLongitude,
dropOffGeoLocationAddress,
dropOffGeoLocationLatitude,
dropOffGeoLocationLongitude,
pickUpDate,
dropOffDate,
pickUpTime,
dropOffTime,
driverAge,
vendorPreferences,
accountSettings,
},
submitting,
handleSubmit,
laymanMode,
availableChannels,
cartHasCar,
createCarSearch,
defaultSearchParams,
initializeForm,
globalPolicyPreferences,
resetApp,
searchForm,
preselectTravelers,
setIsCollapsed,
cartHasItems,
formWithoutChildren,
resetBookingAppContext,
travelerLoggedIn,
travelArrangerPresent,
} = props;
const [isLocationMenuVisible, setIsLocationMenuVisible] = useState(false);
const [isVendorMenuVisible, setIsVendorMenuVisible] = useState(false);
const [isAgeMenuVisible, setIsAgeMenuVisible] = useState(false);
const onLocationMenuOutsideClick = () => {
setIsLocationMenuVisible(false);
};
const onVendorMenuOutsideClick = () => {
setIsVendorMenuVisible(false);
};
const onAgeMenuOutsideClick = () => {
setIsAgeMenuVisible(false);
};
const showCarFormContainer = useSearchFormContainer();
const filterTypeOptions = [
{
value: 'preferred',
label: I18n.t('components.ibe.search_form.car.label.vendor_preferences_type.preferred'),
isDisabled: laymanMode
? availableChannels.cars.includes('galileo')
: accountSettings && accountSettings.galileo && accountSettings.galileo.value.length > 0,
},
{
value: 'exclusive',
label: I18n.t('components.ibe.search_form.car.label.vendor_preferences_type.exclusive'),
},
];
const disableVendorPreference = globalPolicyPreferences.carRentalCompanies.length > 0;
const isNoChannelSelected = noChannelSelected(
laymanMode,
availableChannels,
accountSettings,
'cars',
);
const onPickUpLocationSelect = (address, latitude, longitude) => {
if (!dropOffGeoLocationAddress.touched) {
dropOffGeoLocationAddress.onChange(address);
dropOffGeoLocationLatitude.onChange(latitude);
dropOffGeoLocationLongitude.onChange(longitude);
}
};
const onPickUpAirportChange = airport => {
pickUpLocationAirportCode.onChange(airport);
if (!dropOffLocationAirportCode.touched) {
dropOffLocationAirportCode.onChange(airport);
}
};
const onPickUpDateChange = date => {
if (date === '') {
pickUpDate.onChange('');
} else {
const pickUpDateValue = moment.utc(date);
pickUpDate.onChange(pickUpDateValue.format());
if (!dropOffDate.touched) {
const nextValue = pickUpDateValue.add(7, 'days').format();
dropOffDate.onChange(nextValue);
}
}
};
const handleFormSubmission = (values, dispatch, ownProps) => {
history.push('/cars/searching');
createCarSearch(
{
...values,
currency: ownProps.currency,
preselectTravelers: preparePreselectTravelers(preselectTravelers),
},
ownProps.callbackParams,
)
.then(data => data && data.id && history.push(`/cars/searches/${data.id}`))
.catch(error => dispatch(showError('Search failed for unknown reasons', error)));
setIsCollapsed();
};
const resetToDefaultSearchParams = () => {
initializeForm(defaultSearchParams);
};
const findTimeSelectBoxValue = value => {
if (value === 'anytime') {
return { label: <I18nText id="departure_times.anytime" />, value };
}
return { label: value, value };
};
const onDropOffDateChange = date => {
if (date === '') {
dropOffDate.onChange('');
} else if (date) {
dropOffDate.onChange(moment.utc(date).format());
}
};
const minDropOffDate = () => {
if (pickUpDate.value) return moment(pickUpDate.value).add(1, 'days');
return moment()
.add(1, 'days')
.format('YYYY/MM/DD');
};
const updateDriverAgeFromPreselectedTraveler = () => {
const driver = preselectTravelers[0];
if (driver.traveler.value.age) {
driverAge.onChange(driver.traveler.value.age);
}
};
const loggedInTravellerConditions = travelerLoggedIn && travelArrangerPresent;
const guestTravellerConditions = !travelerLoggedIn && !cartHasItems && formWithoutChildren;
const showPreselectTravellerButton = () => {
if (
laymanMode &&
(loggedInTravellerConditions || guestTravellerConditions) &&
!props.forceAdvancedSearch
) {
return <PreselectTravelers type="car" />;
}
return null;
};
useEffect(() => {
const vendorCodes = globalPolicyPreferences.carRentalCompanies;
if (vendorCodes.length > 0) {
vendorPreferences.codes.onChange(vendorCodes);
vendorPreferences.type.onChange('exclusive');
}
}, []);
useEffect(() => {
if (!searchForm) {
resetToDefaultSearchParams();
}
}, [searchForm]);
useEffect(() => {
if (!driverAge.touched && preselectTravelers.length > 0) {
updateDriverAgeFromPreselectedTraveler();
}
}, [preselectTravelers]);
return (
<div
className={classNames('grid car-form__container', {
'car-form__container--show': showCarFormContainer,
})}
>
<div className="col-grid direction-row col-12 car-form__specifier-container wrap">
<div className="search-menu__container search-menu__container--location">
<Menu
className="search-menu__container--location-dropdown"
isVisible={isLocationMenuVisible}
size="small"
popOverDirection="bottom-left"
onOutsideClick={onLocationMenuOutsideClick}
label={
<div className="search-menu__menu-label">
{airportSearch.value
? I18n.t('components.ibe.search_form.car.label.search_by.airport')
: I18n.t('components.ibe.search_form.car.label.search_by.location')}
<div
className="search-menu__menu-label--icon"
onClick={() => setIsLocationMenuVisible(prevState => !prevState)}
role="button"
tabIndex={0}
onKeyPress={() => setIsLocationMenuVisible(prevState => !prevState)}
>
<Icon name="iconDownChevron" size="tiny" />
</div>
</div>
}
>
<MenuItem
onClick={() => {
airportSearch.onChange(false);
setIsLocationMenuVisible(false);
}}
>
{I18n.t('components.ibe.search_form.car.label.search_by.location')}
</MenuItem>
<MenuItem
onClick={() => {
airportSearch.onChange(true);
setIsLocationMenuVisible(false);
}}
>
{I18n.t('components.ibe.search_form.car.label.search_by.airport')}
</MenuItem>
</Menu>
</div>
<div className="search-menu__container search-menu__container--vendors">
<Menu
className="search-menu__container--vendors-dropdown"
isVisible={isVendorMenuVisible}
size="small"
popOverDirection="bottom-center"
onOutsideClick={onVendorMenuOutsideClick}
label={
<div className="search-menu__menu-label">
{I18n.t('components.ibe.search_form.car.label.vendor_preferences')}
<div
className="search-menu__menu-label--icon"
onClick={() => setIsVendorMenuVisible(prevState => !prevState)}
role="button"
tabIndex={0}
onKeyPress={() => setIsVendorMenuVisible(prevState => !prevState)}
>
<Icon name="iconDownChevron" size="tiny" />
</div>
</div>
}
>
<div className="search-menu-item__container">
<div className="search-menu-item__specifier">
<div className="col-12 col-bleed">
<div className="search-menu-item__specifier--add-padding-bottom">
<VendorSelectBox
value={vendorPreferences.codes.value}
onChange={value => vendorPreferences.codes.onChange(value || [])}
errorMsg={vendorPreferences.codes.error && vendorPreferences.codes.error[0]}
isMulti={true}
width="full"
size="tiny"
placeholderText={I18n.t('components.ibe.search_form.car.label.choose_vendor')}
isDisabled={disableVendorPreference}
/>
</div>
<SelectBox
width="full"
size="tiny"
value={filterTypeOptions.find(
filter => filter.value === vendorPreferences.type.value,
)}
isDisabled={
disableVendorPreference || vendorPreferences.codes.value.length <= 0
}
options={filterTypeOptions}
onChange={selectedOption =>
vendorPreferences.type.onChange(selectedOption ? selectedOption.value : '')
}
placeholderText={I18n.t('components.ibe.search_form.car.label.filter_type')}
isClearable={false}
/>
</div>
</div>
</div>
</Menu>
</div>
<div className="search-menu__container search-menu__container--drivers-age">
<Menu
className="search-menu__container--drivers-age-dropdown"
isVisible={isAgeMenuVisible}
size="small"
popOverDirection="bottom-center"
onOutsideClick={onAgeMenuOutsideClick}
label={
<div
className={classNames('search-menu__menu-label', {
'search-menu__label--error': driverAge.touched && !driverAge.valid,
})}
>
{I18n.t('components.ibe.search_form.car.label.driver_age')}
<div
className="search-menu__menu-label--icon"
onClick={() => setIsAgeMenuVisible(prevState => !prevState)}
role="button"
tabIndex={0}
onKeyPress={() => setIsAgeMenuVisible(prevState => !prevState)}
>
<Icon name="iconDownChevron" size="tiny" />
</div>
</div>
}
>
<div className="search-menu-item__container">
<div className="search-menu-item__specifier">
<div className="col-12 col-bleed">
<Input
placeholder={I18n.t('components.ibe.search_form.car.label.driver_age')}
size="tiny"
type="number"
{...driverAge}
disabled={cartHasCar}
/>
</div>
</div>
<Alert
className="col-12 col-bleed car-form__error"
hideClose={true}
isLocationMenuVisible={cartHasCar}
type="warning"
>
{I18n.t('components.ibe.search_form.error.change_number_of_travelers')}
</Alert>
</div>
</Menu>
</div>
</div>
<div className="col-12 col-bleed-y">
{airportSearch.value ? (
<Fragment>
<div className="col-12 col-sm-6 col-bleed car-form__field--add-padding-right">
<AirportAutoComplete
title={I18n.t('components.ibe.search_form.car.label.pick_up.airport')}
error={pickUpLocationAirportCode.touched && pickUpLocationAirportCode.error}
currentAirport={pickUpLocationAirportCode.value}
{...pickUpLocationAirportCode}
onChange={onPickUpAirportChange}
fullWidth={true}
size="tiny"
/>
</div>
<div className="col-12 col-sm-6 col-bleed car-form__field--add-padding-left">
<AirportAutoComplete
title={I18n.t('components.ibe.search_form.car.label.drop_off.airport')}
error={dropOffLocationAirportCode.error}
currentAirport={dropOffLocationAirportCode.value}
{...dropOffLocationAirportCode}
fullWidth={true}
/>
</div>
</Fragment>
) : (
<Fragment>
<div className="col-12 col-sm-6 col-bleed car-form__field--add-padding-right">
<LocationSearchInput
address={pickUpGeoLocationAddress.value}
placeholder={I18n.t('components.ibe.search_form.car.label.pick_up.location')}
fullWidth={true}
onAddressChange={pickUpGeoLocationAddress.onChange}
onLatitudeChange={pickUpGeoLocationLatitude.onChange}
onLongitudeChange={pickUpGeoLocationLongitude.onChange}
onSelect={onPickUpLocationSelect}
size="small"
touched={pickUpGeoLocationAddress.touched}
error={pickUpGeoLocationAddress.error}
/>
</div>
<div className="col-12 col-sm-6 col-bleed car-form__field--add-padding-left">
<LocationSearchInput
address={dropOffGeoLocationAddress.value}
placeholder={I18n.t('components.ibe.search_form.car.label.drop_off.location')}
fullWidth={true}
onAddressChange={dropOffGeoLocationAddress.onChange}
onLatitudeChange={dropOffGeoLocationLatitude.onChange}
onLongitudeChange={dropOffGeoLocationLongitude.onChange}
size="small"
touched={dropOffGeoLocationAddress.touched}
error={dropOffGeoLocationAddress.error}
/>
</div>
</Fragment>
)}
</div>
<div className="col-12">
<div className="col-12 col-sm-6 col-bleed car-form__field--add-padding-right">
<DatePickerWithFocusedStateWrapper
pastYearsCount={0}
futureYearsCount={1}
size="small"
hideMonthsAndYearsWithNoActiveDates={true}
id={Math.random()}
placeholder={I18n.t('components.ibe.search_form.car.label.pick_up.date')}
onChange={onPickUpDateChange}
fullWidth={true}
error={showInvalidDateError(pickUpDate)}
minDate={moment()
.add(1, 'days')
.format('YYYY/MM/DD')}
date={pickUpDate.value}
locale={moment().locale()}
showClearDate={true}
hideNavButtons={false}
/>
</div>
<div className="col-12 col-sm-6 col-bleed car-form__field--add-padding-left">
<TimeSelectBox
placeholderText={I18n.t('components.ibe.search_form.car.label.pick_up.time')}
width="full"
onChange={selectedOption => selectedOption && pickUpTime.onChange(selectedOption.value)}
errorMsg={pickUpTime.touched && pickUpTime.error}
value={findTimeSelectBoxValue(pickUpTime.value)}
isClearable={false}
size="small"
/>
</div>
</div>
<div className="col-12 col-bleed-y">
<div className="col-12 col-sm-6 col-bleed car-form__field--add-padding-right">
<DatePickerWithFocusedStateWrapper
pastYearsCount={0}
futureYearsCount={1}
size="small"
hideMonthsAndYearsWithNoActiveDates={true}
id={Math.random()}
placeholder={I18n.t('components.ibe.search_form.car.label.drop_off.date')}
onChange={onDropOffDateChange}
fullWidth={true}
error={showInvalidDateError(dropOffDate)}
minDate={minDropOffDate()}
date={dropOffDate.value}
locale={moment().locale()}
showClearDate={true}
hideNavButtons={false}
/>
</div>
<div className="col-12 col-sm-6 col-bleed car-form__field--add-padding-left">
<TimeSelectBox
placeholderText={I18n.t('components.ibe.search_form.car.label.drop_off.time')}
width="full"
onChange={selectedOption =>
selectedOption && dropOffTime.onChange(selectedOption.value)
}
errorMsg={dropOffTime.touched && dropOffTime.error}
value={findTimeSelectBoxValue(dropOffTime.value)}
isClearable={false}
size="small"
/>
</div>
</div>
{!props.laymanMode && (
<div className="col-12 car-form__search-accounts">
<div className="accounts">
<SearchAccounts
searchAccounts={props.searchAccounts.cars}
selectedAccounts={accountSettings}
disabled={false}
selectAll={props.selectAll}
toggleSelectAll={props.toggleSelectAll}
selectGds={props.selectGds}
toggleSelectGds={props.toggleSelectGds}
selectDirect={props.selectDirect}
toggleSelectDirect={props.toggleSelectDirect}
/>
</div>
</div>
)}
<Alert
className="col-12 col-bleed hotel-form__error"
hideClose={true}
isLocationMenuVisible={isNoChannelSelected}
type="danger"
>
{I18n.t('components.ibe.search_form.error.no_channel_selected')}
</Alert>
<div className="col-grid direction-row align-center justify-space-between hotel-form__action-wrapper">
<div className="car-form__preselect-traveler">{showPreselectTravellerButton()}</div>
<div className="car-form__action">
<div className="hotel-form__field--add-padding-right">
<Button
label={I18n.t('components.ibe.search_form.label.reset')}
size="normal"
version="v2"
onClick={() => {
resetApp();
resetBookingAppContext();
}}
/>
</div>
<div className="hotel-form__field--add-padding-left">
<Button
label={I18n.t('components.ibe.search_form.label.search')}
size="normal"
version="v2"
disabled={!!submitting || isNoChannelSelected}
onClick={handleSubmit(handleFormSubmission)}
type="primary"
/>
</div>
</div>
</div>
</div>
);
};
export default SearchForm;
the corresponding reducer is
import { combineReducers } from 'redux';
import defaultCarSearchParams from './defaultCarSearchParams';
import searchFilterParamsBySearchId from './searchFilterParamsBySearchId';
import searchParamsBySearchId from './searchParamsBySearchId';
import searchResultsBySearchId from './searchResultsBySearchId';
import searchStatsBySearchId from './searchStatsBySearchId';
import totalResultsBySearchId from './totalResultsBySearchId';
import vendorsByCode from './vendorsByCode';
import totalResultsPerSupplier from './totalResultsPerSupplier'
const carsReducer = combineReducers({
defaultCarSearchParams,
searchFilterParamsBySearchId,
searchParamsBySearchId,
searchResultsBySearchId,
searchStatsBySearchId,
totalResultsBySearchId,
vendorsByCode,
totalResultsPerSupplier,
});
export default carsReducer;
Now I am not sure how to make migration among these from redux-form to react-final-form using my Redux codes.

Using react-bootstrap in redux form

I am trying to integrate react-bootstrap Dropdown component in redux form. This is my code
import React from 'react'
import {Input,Form,FormGroup,FormControl,ControlLabel,Button,ButtonToolbar,Panel,Checkbox,Radio} from 'react-bootstrap'
import { reduxForm,Field,reset } from 'redux-form'
const FieldInput = ({ input, meta, type, placeholder, min, max }) => {
return (
<FormControl
type={type} onChange={input.onChange} />
)
}
class dropDown extends React.Component {
render() {
return (<FormControl componentClass="select" {...this.props} {...this.input} />)
}
}
let Gri = props => {
const { handleSubmit,pristine,submitting} = props
return (
<form onSubmit={handleSubmit}>
<div>
<FormGroup controlId="formInlineName">
<ControlLabel>User Name:</ControlLabel>
<Field name="firstname" component={FieldInput} type="text"/>
</FormGroup>
</div>
<div>
<FormGroup controlId="formControlsSelect">
<ControlLabel>Category:</ControlLabel>
<Field name="category_id" component={dropDown}>
<option value="1">Health</option>
<option value="2">Municipality Roads</option>
<option value="3">Women and Child Development</option>
</Field>
</FormGroup>
</div>
<Button bsStyle="success" type="submit" disabled={submitting}>SUBMIT</Button>
</form>
)
}
const onSubmit = values => {
console.log(values)
}
Gri= reduxForm({
form: 'gri',
onSubmit,
})(Gri)
export default Gri
In the output the dropdown list is shown but the value is not returned when I click submit. Please help me finding the solution

Uploading images to state in react

I'm working on uploading images on my react app, the idea is to save the images in my object, send it to my back end and upload them to clodinary but right now i cant seem to put the files in my object array:
Component:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import Dropzone from 'react-dropzone';
import {
Grid,
Row,
Col,
FormControl,
FormGroup,
Button,
ButtonToolbar,
Label
} from 'react-bootstrap';
import {Dwelling} from '../../../model';
import {requestSaveDwelling} from '../../../actions';
class New3 extends Component {
static propTypes = {
requestSaveDwelling: PropTypes.func.isRequired,
history: PropTypes.shape({
push: PropTypes.func.isRequired
}).isRequired,
dwelling: PropTypes.shape({})
};
static defaultProps = {
dwelling: new Dwelling()
};
constructor(props) {
super(props);
this.state = {dwelling: new Dwelling()};
if (this.props.dwelling) {
this.state = this.props;
}
}
handleChange({target: {id, value}}) {
this.setState(
state => ({
dwelling: (Object.assign(state.dwelling, {[id]: value}))
})
);
}
handleDrop(file) {
this.setState(
state => ({
dwelling: (Object.assign(state.dwelling.images, file))
})
);
}
handleSubmit() {
const {dwelling} = this.state;
this.props.requestSaveDwelling(dwelling);
this.props.history.push('/dwellings/latest');
}
render() {
const {dwelling} = this.state;
console.log(dwelling);
return (
<Grid className="animated fadeIn">
<Row>
<Col sm={12}>
<h2>Carga de Imágenes</h2>
<Dropzone
onDrop={this.handleDrop}
multiple
accept="image/*"
>
<p>Arrastre Imagenes aquí, o haga click para seleccionar imagenes.</p>
</Dropzone>
{this.state.dwelling.images.length > 0 ?
<div>
<h2>Uploading {this.state.dwelling.images.length} files...</h2>
<div>{this.state.dwelling.images.map(file => <img src={file.preview}/>)}</div>
</div> : null}
</Col>
</Row>
<Row>
<Col sm={12}>
<FormGroup controlId="occupationStatus">
<Label>Estado Ocupacional</Label>
<FormControl
componentClass="select"
value={dwelling.occupationStatus}
placeholder="Seleccione"
onChange={e => this.handleChange(e)}
>
<option disabled label="Seleccione"/>
<option value="Disponible" label="Disponible"/>
<option value="Alquilada" label="Alquilada"/>
<option value="Vendida" label="Vendida"/>
<option value="Reservada" label="Reservada"/>
<option value="Suspendida" label="Suspendida"/>
</FormControl>
</FormGroup>
</Col>
</Row>
<Row>
<Col sm={12}>
<h2>Descripción General</h2>
<FormGroup controlId="generalDescription">
<FormControl
componentClass="textarea"
value={dwelling.generalDescription}
onChange={e => this.handleChange(e)}
placeholder="Escriba una Descripcion general"
/>
</FormGroup>
</Col>
</Row>
<Row>
<Col sm={12}>
<h2>Descripción Privada</h2>
<FormGroup controlId="privateDescription">
<FormControl
componentClass="textarea"
value={dwelling.privateDescription}
onChange={e => this.handleChange(e)}
placeholder="Escriba una Descripcion privada"
/>
</FormGroup>
</Col>
</Row>
<Row>
<Col sm={6}>
<ButtonToolbar className="pull-left">
<Button href="#/dwellings/new2">Atrás</Button>
</ButtonToolbar>
</Col>
<Col sm={6}>
<ButtonToolbar className="pull-right">
<Button onClick={() => this.handleSubmit()}>Guardar</Button>
</ButtonToolbar>
</Col>
</Row>
</Grid>
);
}
}
export default connect(
state => ({
dwelling: state.dwelling.dwelling
}),
dispatch => ({
requestSaveDwelling: dwelling => dispatch(requestSaveDwelling(dwelling))
})
)(New3);
object model:
export default class Dwelling {
_id = undefined;
publicationType = '';
address = {
address: '',
latitude: null,
longitude: null
}
images = [];
constructor(obj) {
Object.assign(this, obj);
}
}
when i try to upload an image i get this error:
New3.js:51 Uncaught TypeError: Cannot read property 'images' of undefined
at Dropzone. (New3.js:51)
This is my example of my previewer with FileReader. However, this is only one file so you might want to adjust it for your app.
class Preview extends React.Component {
constructor(props) {
super(props);
this.state = {
previewSrc: ''
}
}
handlePreview = (e) => {
e.preventDefault();
let file = e.target.files[0];
let reader = new FileReader();
if (e.target.files.length === 0) {
return;
}
reader.onloadend = (e) => {
this.setState({
previewSrc: [reader.result]
});
}
reader.readAsDataURL(file);
}
render() {
return (
<div id="previewer">
<input type="file" onChange={this.handlePreview} />
<h1>Preview</h1>
<img src={this.state.previewSrc} alt="" />
</div>
);
}
}
ReactDOM.render(<Preview/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.min.js"></script>
<div id="root"></div>
References:
https://developer.mozilla.org/en-US/docs/Web/API/FileReader

redux-form data is not passed to handleSubmit

I'm having trouble again with redux-form. I'm calling the handleSubmit function from the parent, and the windows.alert() is calledcorrectly, but the data is not passed to the function. What am I doing wrong?
import React, {Component, PropTypes} from 'react';
import {reduxForm} from 'redux-form';
import memberValidation from './memberValidation';
class DashboardAdding extends Component {
static propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired
}
render() {
const {
fields: { pseudo, email},
handleSubmit,
resetForm
} = this.props;
return (
<div>
<form className="form-horizontal" onSubmit={handleSubmit.bind(this)}>
<div className={'form-group' + (pseudo.error && pseudo.touched ? ' has-error' : '')}>
<label className="col-sm-2">Pseudo</label>
<div className={'col-sm-8 '}>
<input type="text" className="form-control" id="pseudo" {...pseudo}/>
{pseudo.error && pseudo.touched && <div className="text-danger">{pseudo.error}</div>}
</div>
</div>
<div className={'form-group' + (email.error && email.touched ? ' has-error' : '')}>
<label className="col-sm-2">Email</label>
<div className={'col-sm-8 '}>
<input type="text" className="form-control" id="email" {...email}/>
{email.error && email.touched && <div className="text-danger">{email.error}</div>}
</div>
</div>
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button className="btn btn-success" onClick={handleSubmit}>
<i className="fa fa-paper-plane"/> Submit
</button>
<button className="btn btn-warning" onClick={resetForm} style={{marginLeft: 15}}>
<i className="fa fa-undo"/> Reset
</button>
</div>
</div>
</form>
</div>
);
}
}
export default reduxForm({
form: 'dashboardForm',
fields: ['pseudo', 'email'],
validate: memberValidation,
asyncBlurFields: ['email']
})(DashboardAdding);
...and the parent calling the handleSubmit:
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import Helmet from 'react-helmet';
import {bindActionCreators} from 'redux';
import {initialize} from 'redux-form';
import {isLoaded, loadMembers} from 'redux/modules/members/members';
import * as addActions from 'redux/modules/members/addSingleMember';
import {addMember} from 'redux/modules/members/addSingleMember';
import { DashboardList } from 'components';
import { DashboardHeader } from 'components';
import { DashboardAdding } from 'components';
import { asyncConnect } from 'redux-async-connect';
#asyncConnect([{
deferred: true,
promise: ({store: {dispatch, getState}}) => {
if (!isLoaded(getState())) {
return dispatch(loadMembers());
}
}
}])
class Dashboard extends Component {
static propTypes = {
members: PropTypes.array,
error: PropTypes.string,
loading: PropTypes.bool,
addMember: PropTypes.func,
initialize: PropTypes.func.isRequired
}
handleSubmit = (data, dispatch) => {
window.alert(data);
dispatch(addMember(JSON.stringify(data)));
this.props.initialize('dashboardForm', {});
}
handleInitialize = () => {
this.props.initialize('dashboardForm', {
pseudo: 'Pibo',
email: 'pibirino#gmail.com'
});
}
render() {
const {members} = this.props;
return (
<div className="container">
<h1>Dashboard</h1>
<Helmet title="Dashboard"/>
<DashboardHeader />
<div>
<DashboardList members={members}/>
<h3>Ici commence le form</h3>
<div style={{textAlign: 'center', margin: 15}}>
<button className="btn btn-primary" onClick={this.handleInitialize}>
<i className="fa fa-pencil"/> Initialize Form
</button>
</div>
</div>
<DashboardAdding onSubmit={this.handleSubmit}/>
</div>
);
}
}
function mapStateToProps(state) {
return {
members: state.members.data,
error: state.members.error,
loading: state.members.loading
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
addActions,
addMember,
initialize: initialize
}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(Dashboard);
The redux-documentation says:
"You are upgrading from a previous version of redux-form that required that {valid: true} be returned".
I suspect that the problem is that, but I really don't understand what that could mean!
My version -> "redux-form": "^3.0.0"
Thanks!
Find the solution where I didn't expect... The form gives the data in json format. JSON.stringify() messed it up.
I hope it can help somebody. Bye

how to call handleSubmit from parent in redux-form

I have a problem I can't solve trying to use redux-form. I'm trying the Erikras boilerplate. I want the form to be a component and the parent to call handleSubmit (for the moment with a console.log just to confirm it works). Here, the two:
import React, {Component, PropTypes} from 'react';
import Helmet from 'react-helmet';
import {initialize} from 'redux-form';
import {connect} from 'react-redux';
import * as membersActions from 'redux/modules/members';
import {isLoaded, loadMembers} from 'redux/modules/members';
import { DashboardList } from 'components';
import { DashboardHeader } from 'components';
import { DashboardAdding } from 'components';
import { asyncConnect } from 'redux-async-connect';
#asyncConnect([{
deferred: true,
promise: ({store: {dispatch, getState}}) => {
if (!isLoaded(getState())) {
return dispatch(loadMembers());
}
}
}])
#connect(
state => ({
members: state.members.data,
error: state.members.error,
loading: state.members.loading
}),
{...membersActions, initialize })
export default class Dashboard extends Component {
static propTypes = {
initialize: PropTypes.func.isRequired,
members: PropTypes.array,
loadMembers: PropTypes.func.isRequired
}
handleSubmit = (data) => {
console.log(data);
this.props.initialize('dashAdding', {});
}
handleInitialize = () => {
this.props.initialize('dashAdding', {
pseudo: 'Pibo',
email: 'pibirino#gmail.com'
});
}
render() {
const {members} = this.props;
return (
<div className="container">
<h1>Dashboard</h1>
<Helmet title="Dashboard"/>
<DashboardHeader />
<div>
<DashboardList members={members}/>
<h3>Ici commence le form</h3>
<div style={{textAlign: 'center', margin: 15}}>
<button className="btn btn-primary" onClick={this.handleInitialize}>
<i className="fa fa-pencil"/> Initialize Form
</button>
</div>
</div>
<DashboardAdding onSubmit={this.handleSubmit}/>
<p>Bleeeeah!!</p>
</div>
);
}
}
and here the child:
import React, {Component, PropTypes} from 'react';
import {reduxForm} from 'redux-form';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import memberValidation from './memberValidation';
#reduxForm({
form: 'dashAdding',
fields: ['pseudo', 'email'],
validate: memberValidation
})
export default class DashboardAdding extends Component {
static propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired
}
render() {
const {
fields: { pseudo, email},
handleSubmit,
resetForm
} = this.props;
const renderInput = (field, label) =>
<div className={'form-group' + (field.error && field.touched ? ' has-error' : '')}>
<label htmlFor={field.name} className="col-sm-2">{label}</label>
<div className={'col-sm-8 '}>
<input type="text" className="form-control" id={field.name} {...field}/>
</div>
</div>;
return (
<div>
<form className="form-horizontal" onSubmit={handleSubmit}>
{renderInput(pseudo, 'Full Name')}
{renderInput(email, 'Email', true)}
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button className="btn btn-success" onClick={handleSubmit}>
<i className="fa fa-paper-plane"/> Submit
</button>
<button className="btn btn-warning" onClick={resetForm} style={{marginLeft: 15}}>
<i className="fa fa-undo"/> Reset
</button>
</div>
</div>
</form>
</div>
);
}
}
So... it doesn't work I think I'm missing some important knowledge. I thought that the reason is because the form component is dumb, and it doesn't have the dispatch function. So, I tryed to add this (several times in several different ways) importing the action creator from the specific folder:
#connect(() => ({}),
dispatch => actionCreators( dispatch)
)
But I don't still get what I want. What's the problem?
So, finally I found the answer by myself. In fact, I decided to not use #connect, whitch is deprecated (despite it's still used in the boilerplate) and to use connect, as in the example of the redux-form documentation. The only change concerned the parent, but I'll post them both in case I missed something. The following code works good.
Here is the code:
import React, {Component, PropTypes} from 'react';
import Helmet from 'react-helmet';
import {initialize} from 'redux-form';
import {connect} from 'react-redux';
import * as membersActions from 'redux/modules/members';
import {isLoaded, loadMembers} from 'redux/modules/members';
import { DashboardList } from 'components';
import { DashboardHeader } from 'components';
import { DashboardAdding } from 'components';
import { asyncConnect } from 'redux-async-connect';
#asyncConnect([{
deferred: true,
promise: ({store: {dispatch, getState}}) => {
if (!isLoaded(getState())) {
return dispatch(loadMembers());
}
}
}])
class Dashboard extends Component {
static propTypes = {
members: PropTypes.array,
error: PropTypes.string,
loading: PropTypes.bool,
addMember: PropTypes.func,
initialize: PropTypes.func.isRequired,
newMemberData: PropTypes.object
}
handleSubmit = (data, dispatch) => {
dispatch(addMember(JSON.stringify(data)));
this.props.initialize('dashboardForm', {});
}
handleInitialize = () => {
this.props.initialize('dashboardForm', {
pseudo: 'Pibo',
email: 'pibirino#gmail.com'
});
}
render() {
const {members} = this.props;
return (
<div className="container">
<h1>Dashboard</h1>
<Helmet title="Dashboard"/>
<DashboardHeader />
<div>
<DashboardList members={members}/>
<h3>Ici commence le form</h3>
<div style={{textAlign: 'center', margin: 15}}>
<button className="btn btn-primary" onClick={this.handleInitialize}>
<i className="fa fa-pencil"/> Initialize Form
</button>
</div>
</div>
<DashboardAdding onSubmit={this.handleSubmit}/>
</div>
);
}
}
function mapStateToProps(state) {
return {
members: state.members.data,
error: state.members.error,
loading: state.members.loading,
newMemberData: state.addSingleMember.data
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
addActions,
initialize: initialize
}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(Dashboard);
...and the child component:
import React, {Component, PropTypes} from 'react';
import {reduxForm} from 'redux-form';
import memberValidation from './memberValidation';
class DashboardAdding extends Component {
static propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired
}
render() {
const {
fields: { pseudo, email},
handleSubmit,
resetForm
} = this.props;
const renderInput = (field, label) =>
<div className={'form-group' + (field.error && field.touched ? ' has-error' : '')}>
<label htmlFor={field.name} className="col-sm-2">{label}</label>
<div className={'col-sm-8 '}>
<input type="text" className="form-control" id={field.name} {...field}/>
{field.error && field.touched && <div className="text-danger">{field.error}</div>}
</div>
</div>;
return (
<div>
<form className="form-horizontal" onSubmit={handleSubmit.bind(this)}>
{renderInput(pseudo, 'Pseudo')}
{renderInput(email, 'Email', true)}
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button className="btn btn-success" onClick={handleSubmit}>
<i className="fa fa-paper-plane"/> Submit
</button>
<button className="btn btn-warning" onClick={resetForm} style={{marginLeft: 15}}>
<i className="fa fa-undo"/> Reset
</button>
</div>
</div>
</form>
</div>
);
}
}
export default reduxForm({
form: 'dashboardForm',
fields: ['pseudo', 'email'],
validate: memberValidation,
asyncBlurFields: ['email']
})(DashboardAdding);
The addMember function contains obviously a promise.
I hope it will helps somebody :-)

Resources