I am trying to use react-dates with redux-form. Did a render thing for it. I have handled text input and select fields pretty much the same way. Those are working fine.
Getting a funky error on either DateRangePicker or even SingleDatePicker, which I cannot make sense of. Any ideas/suggestions are greatly appreciated.
Did a render component as:
const renderDateRangePicker = ({
input,
focusedInput,
onFocusChange,
startDatePlaceholderText,
endDatePlaceholderText
}) => (
<DateRangePicker
onDatesChange={(start, end) => input.onChange(start, end)}
onFocusChange={onFocusChange}
startDatePlaceholderText={startDatePlaceholderText}
endDatePlaceholderText={endDatePlaceholderText}
focusedInput={focusedInput}
startDate={(input.value && input.value.startDate) || null}
startDateId="startDateId"
endDateId="endDateId"
endDate={(input.value && input.value.endDate) || null}
minimumNights={0}
/>
)
My class is just a form as:
class ActivityForm extends Component {
// values: ['startDate', 'endDate']
state = {
focusedInput: null
}
onFocusChange(focusedInput) {
this.setState({ focusedInput });
}
render () {
const { focusedInput } = this.state
const { handleSubmit, teams } = this.props
return (
<form onSubmit={handleSubmit} className="activity__form">
<div className="activity__form_row">
<Field
name="name"
label="Activity name"
component={renderTextField}
margin="normal"
validate={[required]}
className="activity__form_field_name"
InputLabelProps={{
shrink: true,
}}
/>
<div className="activity__form_spacer"/>
<Field
name="daterange"
onFocusChange={this.onFocusChange}
focusedInput={focusedInput}
component={renderDateRangePicker}
/>
<div className="activity__form_spacer"/>
<Button className="activity__form_button" type="submit">Save</Button>
</div>
</form>
)
}
}
export default reduxForm({ form: 'activity' })(ActivityForm)
For some reason, DateRangePicker causes a strange error: Uncaught TypeError: Cannot read property 'createLTR' of undefined.
What am I missing?
I believe this error is caused by missing or misplaced import of the initialization of react-dates, you can take a look at the Initialize section in (https://github.com/airbnb/react-dates)
import 'react-dates/initialize';
It also looks like there is an update to DateRangePicker:
So include starteDateId and endDateId as props to the DateRangePicker component.
<DateRangePicker
startDateId="2" // PropTypes.string.isRequired,
endDateId="1" // PropTypes.string.isRequired,
startDate={this.props.filters.startDate}
endDate={this.props.filters.endDate}
onDatesChange={this.onDatesChange}
focusedInput={this.state.calendarFocused}
onFocusChange={this.onFocusChange}
showClearDates={true}
numberOfMonths={1}
isOutsideRange={() => false}
/>
It worked for me.
Related
I am struggling with some React functionality. My goal is to create a form where a day template can be added (for context - like a training club can make up a template of trainings for the day and then schedule them regularly). For that I wanted to add a button which onClick will create a smaller block with 2 form fields - time and training info. And I need user to add several of those, as much as they want.
The thing is, while I understand a bit how react works, it seems to me I am just banging my head against the wall with this, as one thing is to render a component, but another to generate a bunch of same, completely new ones and connected to the form somehow, so I can send the data when clicking submit button.
Here is repository with this component:
https://github.com/badgerwannabe/spacefitness-test-client
Here is path to this component
spacefitness-test-client/src/components/template-components/addTemplateForm.js
Here below how it looks rendered
UPDATE 1 here is the full component here:
import React, {useState} from "react";
import {useMutation } from "#apollo/client";
import {useForm} from '../../utils/hooks'
import { Button, Form } from "semantic-ui-react";
import {FETCH_TEMPLATES_QUERY, FETCH_TRAININGS_QUERY,ADD_TEMPLATES_MUTATION} from '../../utils/graphql'
//hook for form functioning
function AddTemplateForm (props){
const {values, onChange, onSubmit} = useForm(createDayCallback,{
date:'', dayTrainings:[{
time:'testing time', training:"60e9e7580a6b113b2486113a"
},{
time:'testing2 time2', training:"61ec6a6d0f94870016f419bd"
}
]
});
//apollo hook to send data through GraphQL
const [createDay, {error}] = useMutation(ADD_TEMPLATES_MUTATION, {
errorPolicy: 'all',
variables:values,
update(proxy, result){
const data = proxy.readQuery({
query:FETCH_TEMPLATES_QUERY,
});
proxy.writeQuery({query:FETCH_TEMPLATES_QUERY,
data:{
getDays: [result.data.createDay, ...data.getDays]
}})
props.history.push('/templates')
},},
{});
function createDayCallback(){
createDay();
}
//little component I want to dynamically add each time people press a button
function addDayTraining(){
const addDayTraining = (
<>
<Form.Field>
<Form.Input
placeholder="time"
name="time"
onChange={()=>{
console.log("time")
}}
values={values.time}
error={error ? true : false}
/>
<Form.Input
placeholder="training"
name="training"
onChange={()=>{
console.log("training")
}}
values={values.training}
error={error ? true : false}
/>
</Form.Field>
</>
)
return addDayTraining
}
//Form component itself
const AddTemplateForm = (
<>
<Form onSubmit={onSubmit}>
<h2>Add a template :</h2>
<Form.Field>
<Form.Input
placeholder="date"
name="date"
onChange={onChange}
values={values.date}
error={error ? true : false}
/>
</Form.Field>
<Form.Field>
<Button type="button" onClick={
addDayTraining
}>Add training</Button>
</Form.Field>
<Button type ="submit" color="teal">Submit</Button>
</Form>
{error && (
<div className="ui error message" style={{marginBottom:20}}>
<li>{error.graphQLErrors[0].message}</li>
</div>
)}
</>
)
return AddTemplateForm
}
export default AddTemplateForm;
Can you just set up a function on the submit button which pushes an object with {time: new Date(), trainingInfo: ""} and push that object into an existing array of training objects? (obviously starting empty)
You could then map those objects into a component and when the component is updated (i.e. when the user adds a time and training details text) use a callback function to update the values in the array at the index of that object.
export default function yourIndexPage({yourprops}) {
const [trainingObjects, setTrainingObjects] = useState([]);
function addTraining(){
const newTrainingObject = {
time: new Date(), //assuming you want it to default to todays date
trainingInfo: "your placeholder text"
};
setTrainingObjects([...trainingObjects, newTrainingObject]);
}
//I am assuming your training object will be a list item here so wrapped in <ul>
return(
<div>
<div className='your list of training things'> (might need to set style as flex and add some padding etc..)
{trainingObjects.length === 0 ? <div/> : trainingObjects.map((trainingObject, index) => (
<YourTrainingObjectComponent trainingObject={trainingObject} trainingItemIndex={index} key={index}/>
))}
</div>
<Button onClick={() => {addTraining}} />
</div>
)
}
Formik's documentation states you can use a lodash type dot path to name/access nested objects (e.g. name.firstName). This is also supposed to apply to it's built in <ErrorMessage/> component. I was working with a React-Typescript tutorial app that uses formik for form inputs and it seemed to work fine under the hood when paired with backend code, but I did notice that the fields that fed nested object values would not throw any errors in the UI. The error itself would be generated, but the <ErrorMessage/> component didn't seem to want to render.
A pared down version of the app is below. The "Field is Required" error should be thrown if you exit a form field without a valid input but again it doesn't work for the nested object fields (first/last name). I was wondering if anyone else has run across this issue. It's a little annoying.
I've seen that formik seems to be paired frequently with Yup for validation, which may make this issue moot, but I haven't gotten quite that far yet. Thanks!
import React from 'react';
import ReactDOM from 'react-dom';
import { ErrorMessage, Field, FieldProps, Formik } from "formik";
import { Form, Button } from "semantic-ui-react";
interface TextProps extends FieldProps {
label: string;
placeholder: string;
}
const TextField: React.FC<TextProps> = ({
field, label, placeholder
}) => (
<Form.Field>
<label>{label}</label>
<Field placeholder={placeholder} {...field} />
<div style={{ color: "red" }}>
<ErrorMessage name={field.name} />
</div>
</Form.Field>
);
const App: React.FC = () => {
return (
<Formik
initialValues={{
name: {
firstName: "",
lastName: ""
},
job: "",
}}
onSubmit={()=>console.log("submitted")}
validate={values => {
const requiredError = "Field is required";
const errors: { [field: string]: string } = {};
if (!values.name.firstName) {
errors["name.firstName"] = requiredError;
}
if (!values.name.lastName) {
errors["name.lastName"] = requiredError;
}
if (!values.job) {
errors["job"] = requiredError;
}
return errors;
}}
>
{({ isValid, dirty}) => {
return (
<Form>
<Field label="First Name" name="name.firstName" component={TextField} />
<Field label="Last Name" name="name.lastName" component={TextField} />
<Field label="Job" name="job" component={TextField} />
<Button type="submit" disabled={!dirty || !isValid}> Add</Button>
</Form>
);
}}
</Formik>
);
};
Have you tried using formik's inbuilt function getIn()? It is used to extract values from deeply nested objects. Check this Q&A
Try changing validate function to this
validate={values => {
const requiredError = "Field is required";
const errors: any = {name: {}};
if (!values.name.firstName) {
errors.name.firstName = requiredError;
}
if (!values.name.lastName) {
errors.name.lastName = requiredError;
}
if (!values.job) {
errors["job"] = requiredError;
}
return errors;
}}
I am trying to use a switch while rendering parts of a form, however, when I do so, I get an error of e of undefined and when I console.log the inputType prop I get 3 undefined and the actual value.
I would appreciate to know what I am doing wrong.
This is the renders I am trying to do a switch into
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Input from '../InputField';
import MultipleInput from '../multipleInput';
import ImageUploader from '../ImageUploader';
import './styles.scss';
const InputMultiple = ({currentState, handleMultpleChange, removeItem})=> (
<MultipleInput
value={currentState.services}
name="services"
handleMultpleChange={() => handleMultpleChange}
removeItem={removeItem}
/>
);
const InputImageUploader = ({ setTheState, currentState }) => (
<ImageUploader
setState={setTheState}
urls={currentState.urls}
files={currentState.files}
isDragging={currentState.isDragging}
/>
);
const InputTextArea = ({OnChange})=>(
<div className="accommodation_popup_innerContainer_inputContainer_div1">
<textarea
type="text"
name="description"
id="description"
className="input accommodation_popup_innerContainer_inputContainer_div1_inputs"
onChange={OnChange}
/>
</div>
);
const InputSingle = ({OnChange})=>(
<div className="accommodation_popup_innerContainer_inputContainer_div1">
<Input
type="text"
name={name}
id={labelName}
className="input accommodation_popup_innerContainer_inputContainer_div1_inputs"
onChange={OnChange}
/>
</div>
);
This is the switch and all the props passed along side it
const TypeOfInput = (inputType) => {
console.log(inputType);
switch (inputType) {
case 'InputMultiple': {
return InputMultiple;
}
case 'InputImageUploader': {
return InputImageUploader;
}
case 'InputTextArea': {
return InputTextArea;
}
default: {
return InputSingle;
}
}
};
const LabelInput = ({
labelName,
inputType,
name,
OnChange,
currentState,
setTheState,
handleMultpleChange,
removeItem,
}) => (
<div>
<div className="accommodation_popup_innerContainer_inputContainer_text">
<label className="accommodation_popup_innerContainer_inputContainer_text_texts">
{labelName}
</label>
</div>
{TypeOfInput(
inputType,
name,
OnChange,
currentState,
setTheState,
handleMultpleChange,
removeItem
)}
</div>
);
LabelInput.propTypes = {
labelName: PropTypes.string.isRequired,
onChange: PropTypes.func,
};
export default LabelInput;
where I am calling the component
<LabelInput
labelName="Services"
name="services"
inputType="InputMultiple"
OnChange={this.handleChange}
handleMultpleChange={() => this.handleMultpleChange}
removeItem={this.removeItem}
/>
This line handleMultpleChange={() => this.handleMultpleChange} in your LabelInput AND MultipleInput components is incorrect.
You need to either call the function in that callback like this: () => this.handleMultpleChange() or set the function to be this.handleMultpleChange. I generally prefer the later since it's shorter.
So do this:
handleMultpleChange={() => this.handleMultpleChange()}
OR:
handleMultpleChange={this.handleMultpleChange}
*PS: it's spelt multiple, not multple
I already built the form in React and it shows the input fields in red borders that'll change to regular borders once someone types it in. I used this example from this React form article link So everything is working except I wanted to add the error message under the input field that displays "Please fill in the blank field" that will disappear once someone starts typing in the field. How do I do this?
Here's my code in Form.js:
import React, { Component } from 'react';
import FormField from './FormFieldBox';
function validate(name, isin) {
// true means invalid, so our conditions got reversed
return {
name: name.length === 0,
isin: isin.length === 0
};
}
export default class PopupForm extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
isin: '',
country: '',
errormessage: ''
}
}
updateInput = (e) =>{
this.setState({[e.target.name]: e.target.value})
}
closePopupSubmit = (e) => {
if (!this.canBeSubmitted()) {
e.preventDefault();
}
let security = { //1.gather security data from form submit
name: this.state.name,
isin: this.state.isin,
country: this.state.country
}
this.props.submitPopup(security); //2.closePopup function, add security data
}
canBeSubmitted() {
const errors = validate(this.state.name, this.state.isin);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return !isDisabled;
}
cancelPopupSubmit = (e) => {
e.preventDefault()
this.props.cancelPopup();
}
render() {
const errors = validate(this.state.name, this.state.isin);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return (
<div className='popup'>
<div className='popup-inner'>
<form onSubmit={this.closePopupSubmit}>
<FormField onChange={this.updateInput} className={errors.name ? "input error" : "input"} label="Name" type="text" name="name" value={this.state.name} />
<FormField onChange={this.updateInput} className={errors.isin ? "input error" : "input"} label="ISIN" type="text" name="isin" value={this.state.isin} />
<FormField onChange={this.updateInput} label="Country" type="text" name="country" value={this.state.country} />
<button type="button" onClick={this.cancelPopupSubmit} className="button">Cancel</button>
<button type="submit" className="button" disabled={isDisabled}>Submit</button>
</form>
</div>
</div>
)
}
}
And my component FormField.js
import React from "react";
const FormBox = props => {
return (
<div className="field">
<label className="label">{props.label}</label>
<div className="control">
<input onChange={props.onChange}
className={props.className}
type={props.type}
name={props.name}
value={props.value}
placeholder={props.placeholder} />
{/* {props.errormessage} */}
</div>
</div>
)
}
export default FormBox;
const FormBox = props => {
return (
<div className="field">
<label className="label">{props.label}</label>
<div className="control">
<input onChange={props.onChange}
className={props.className}
type={props.type}
name={props.name}
value={props.value}
placeholder={props.placeholder} />
</div>
{Boolean(props.value.length) || (
<div className="err-msg">
Please fill in the blank field
</div>
)}
</div>
)
}
There are two ways you can achieve this
First : oninvalid attribute in HTML5 and calling a custom function on that.
Second : along with each element name object in state have a length attribute. In validation function you can check for the length and throw a custom error that you want to display.
I have FormControl for password input with icon that toggles the mask, so u cant see the characters you are inputting. But whenever I toggle this mask I also want to re-focus the input field.
<FormGroup
className="input-text input-text--password"
controlId={id}
validationState={submitFailed && touched && error ? 'error' : validationState}
>
<FormControl
{...input}
disabled={disabled}
className="input-text__input"
autoFocus={autoFocus}
type={maskEnabled ? 'password' : 'input'}
onChange={this.handleFormControlChange}
maxLength={maxLength}
ref = { ref => this.textInput = ref }
/>
<div
className={'input-text__button ' + (maskEnabled ? 'mask-enabled' : 'mask-disabled')}
onClick={this.handleToggleInputMode}
/>
... I use this method to focus:
handleToggleInputMode() {
let newMaskEnabled = !this.state.maskEnabled;
this.setState({maskEnabled: newMaskEnabled});
this.textInput.focus();
}
But i keep getting error
Uncaught TypeError: this.textInput.focus is not a function
So i tried to put it in ComponentDidMount but then the whole componend stopped responding (didnt accept any char I typed in).
What am i missing?
Please check working demo
class App extends React.Component {
componentDidMount() {
this.textInput.focus();
}
render() {
return (
<div>
<div>
<input
defaultValue="It will not focus"
/>
</div>
<div>
<input
ref={(input) => { this.textInput = input; }}
defaultValue="with focus"
/>
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.js"></script>
<div id="app"></div>
Edit
for FormControl you should use inputRef as per documentation
<FormControl inputRef={ref => { this.input = ref; }} />
According to the current docs (https://reactjs.org/docs/refs-and-the-dom.html), use:
this.textInput.current.focus();
So it should reference to the current to get the DOM node.