Formsy-material-ui do not validate initial render - reactjs

Is there any way, one can delay first validation of components in formsy-material-ui so that validations like isNotEmpty do not fire on first render of the form and mess the UX? I am using controlled components, therefore setting value from state on each render.
<FormsyText
name="name"
value={this.state.name}
floatingLabelText="Name"
onChange={partial(this._changeInputValue, ['name'])}
validations={{ isNotEmpty }}
validationError="Field shoud not be empty"
/>

I needed this solution too. I've been looking into the source code of formsy-material-ui, and it seems that the text field is setting its value right before it's mounted. That's why the field is marked changed (aka not pristine) when the rendering happens, so the validation error is shown.
Anyways, I wrote a hackish solution using a higher order component. I've been testing with text fields only, but should work with any fields having this problem. The core concept: if the formsy field doesn't have a "validationErrors" prop, it's not showing any errors.
import React, { Component, PropTypes } from 'react';
export const preventFirstValidation = (FormsyField) => {
return class extends Component {
static propTypes = { preventFirstValidation: PropTypes.bool };
static defaultProps = { preventFirstValidation: true };
constructor(props) {
super(props);
this.state = { isChanged: false };
}
render() {
const { preventFirstValidation, ...fieldProps } = this.props;
return (
<FormsyField
{...fieldProps}
onChange={(evt, val) => {
if (!this.state.isChanged) this.setState({ isChanged: true });
if (this.props.onChange) this.props.onChange(evt, val);
}}
validationErrors={(this.state.isChanged || !preventFirstValidation) ? this.props.validationErrors : undefined}
/>
);
}
};
};
How to use it:
import { Form } from 'formsy-react';
import FormsyTextField from 'formsy-material-ui/lib/FormsyText';
const TextField = preventFirstValidation(FormsyTextField);
const MyForm = () => (
<Form>
{/* You're using the transformed field, exactly like before */}
<TextField
name = "some_field"
validationErrors={{ isRequired: 'This is really required!' }}
required
preventFirstValidation={ /* you can enable or disable this feature */ }
/>
</Form>
);

Related

Dynamically creating a new component based off another component's state

First timer here on StackOverflow! I'm trying to emulate a terminal interface for one of my portfolio projects. The way I have envisioned the terminal is that each terminal box has a state object with a few key/value pairs. Ideally, when someone enters text into the terminal box input form, the input becomes disabled and a new terminal box is rendered on the screen with a dynamic response based upon the userInput text which has been saved in the state. Where I'm stuck:
Once userInput state has been updated, how do I get a new terminal box to render beneath the prior box on the screen?
Prior to rendering, how do I set the initial state of the newly-rendered terminal box back to default with the exception of the "output" which would be re-valued to an appropriate response that I set?
How do I access the state in the prior terminal box so I can "read" the userInput stored there so I can determine what the appropriate response to that input would be?
I've included copies of each of the components below:
App.js
import React from "react";
import Terminal from "./components/Terminal";
import "./App.css";
class App extends React.Component {
render() {
return (
<div>
<Terminal />
</div>
);
}
}
export default App;
Terminal.js
import React, { Component } from "react";
import Form from "./Form";
import Falcon from "./Falcon";
import Messages from "./Alerts/Messages";
class Terminal extends Component {
state = {
output: Messages.intro,
userInput: "",
isComplete: false,
isDisabled: "",
};
markComplete = () => {
this.setState({
isComplete: true,
});
};
onSubmit = (event, userInput) => {
event.preventDefault();
this.setState({
userInput: userInput,
isDisabled: "disabled",
});
};
render() {
return (
<div>
<Falcon
output={this.state.output}
markComplete={this.markComplete}
isComplete={this.state.isComplete}
/>
<p />
<Form
input={this.state.userInput}
onSubmit={this.onSubmit}
isComplete={this.state.isComplete}
isDisabled={this.state.isDisabled}
/>
<p />
</div>
);
}
}
export default Terminal;
Falcon.js (Note: You'll see that there is a component "Typed" below - that is part of Matt Boldt's Typed.js (of which react-typed is an offshoot package) package which I'm using to simulate typing.)
import React, { Component } from 'react'
import Typed from 'react-typed'
class Falcon extends Component {
state = {
output: this.props.output,
};
render() {
return (
<div>
<Typed
strings={[this.state.output]}
typeSpeed={40}
onComplete={(self) => {
self.cursor.remove();
this.props.markComplete();
}}
/>
</div>
);
}
}
export default Falcon;
Form.js
import React from "react";
class Form extends React.Component {
state = {
input: this.props.input,
};
render() {
return (
<form
style={{
display: this.props.isComplete === false ? "none" : "",
}}
onSubmit={(event) => {
this.props.onSubmit(event, this.state.input);
}}
>
{"> "}
<input
ref={(input) => input && input.focus()}
type="text"
disabled={this.props.isDisabled}
style={{
border: "none",
outline: "none",
backgroundColor: "#FFF",
color: "#000",
}}
value={this.state.input}
onChange={(event) => this.setState({ input: event.target.value })}
/>
</form>
);
}
}
export default Form;
Any insight or guidance you can offer would be much appreciated! Thank you for helping this "first-timer" out!
Welcome to StackOverflow! I made a codesandbox demo with a few changes.
When developing React applications, it's a good practice to model the UI (the DOM elements) as a function of your internal state. You update the state and the UI changes automatically, it reacts to updates.
That said, you probably want to consider using the form only for the actual input element at the bottom of the terminal. The "past buffer" is just an array that only increases its content with user input and program output. Another good practice (actually a commandment!) is to never mutate the state. So, if you want to update the array, you create a new one from scratch, as in:
this.setState((state) => ({
conversation: [
...state.conversation, // we spread the previous state into the new one
{ text: state.userInput, id: faker.random.uuid(), type: "input" } // the last element is appended
]
}));}
Notice how setState (in class components) just schedules an update for the fields that you used. As your app scales, you will probably want to limit the length of this array.
The terminal component could be like:
class Terminal extends Component {
state = {
output: "Messages.intro",
userInput: "",
isComplete: false,
isDisabled: "",
conversation: [] // couldn't think of a nice name :(
};
markComplete = () => {
this.setState({
isComplete: true
});
};
onChange = (event) => {
this.setState({ userInput: event.target.value });
};
onSubmit = (event) => {
event.preventDefault();
this.setState((state) => ({
userInput: "",
conversation: [
...state.conversation,
{ text: state.userInput, id: faker.random.uuid(), type: "input" }
]
}));
};
render() {
const { conversation, userInput, output, isComplete } = this.state;
return (
<div>
<Falcon
output={output}
markComplete={this.markComplete}
isComplete={isComplete}
/>
<p />
// This is not really a form. Should be changed to another readonly element
{conversation.map((item, index) => (
<Form
key={item.id}
input={item.text}
onSubmit={this.onSubmit}
isComplete={isComplete}
isDisabled
/>
))}
<p />
<Form
input={userInput}
onSubmit={this.onSubmit}
isComplete={isComplete}
isDisabled={false}
onChange={this.onChange}
/>
<p />
</div>
);
}
}

How to use props of a component out of the component in react with typescript?

As i am new to react i have a question.I have a react component and its properties. And I want to reach one of these properties from the page where i used my component.
type BranchProps = {
SelectedBranch : string
}
class Branch extends React.Component<BranchProps, BranchState> {
constructor(props: BranchProps) {
super(props);
}
render() {
return (
<SelectBox></SelectBox>
)
}
}
export default Branch ;
ParentPage.tsx
import Branch...
class Page extends.... {
ctor..
const test:string = () => {
Branch.SelectedBranch ???
}
}
And i want to get "SelectedBranch" from my ParentPage.
Note: SelectedBranch is changing on change event. Should i make my SelectedBranch a const and export it or what should i do ?
I have created this Input.js child component with different props
const Input = ({ placeholder, label, value, onChangeText, secureTextEntry }) => {
return (
<View >
<Text >{ label }</Text>
<TextInput
secureTextEntry={secureTextEntry}
placeholder={placeholder}
autoCorrect={false}
value={value}
onChangeText={onChangeText}
style={inputStyles}
/>
</View>
);
};
Once I import it to be used on a page, this is how the manipulation of the content is being done. the value is been passed on by simply quoting the specific prop
<Input
secureTextEntry
placeholder={'password'}
label={'Password'}
value={this.state.password}
onChangeText={password => this.setState({ password })}
/>
Here the 'password' is been assigned to the component by using the state of the parent. something like this, you can assign the value as you see fit.
state = { email: '', password: '', error: '', loading: false };
A far better way exist by using the Redux approach. would be advisable to have a look.
Firstly, you should understand the difference between state and props inside a component. Props shouldn't be updated, it's the state's role.
You can't directly access component's props outside of it.
In pure react (without librabry like redux) the right way should be to use callbacks to return the element to the parent.
class Branch extends React.Component<BranchProps, BranchState> {
state = {
'selectedBranch': ''
}
constructor(props: BranchProps) {
super(props);
}
handleOnChange = (e) => {
this.setState({'selectedBranch': e.target.value})
this.props.parentHandleChangeBranch(this.state.selectedBranch);
}
render() {
return (
<SelectBox value={this.state.selectedBranch} onChange="{this.handleOnChange}"></SelectBox>
)
}
}
class Page extends React.Component {
state = {
'branch': null
}
parentHandleChangeBranch = (branch) => {
this.setState({'branch': branch};
}
render () {
<div>
<Branch parentHandleChangeBranch={this.parentHandleChangeBranch} />
</div>
}
}
You can declare a function in the parent component and pass it as prop to the child. Then, call this callback whenever you want inside the child.

react office js textfield controlled component

i tried to use officejs react component in it and for osme reason i cant get it to work properly..effective here is the code. it works in codepen but when i put hte same code in excel addin project, i cant get the value in the textfields.
Code in codepen(it works):
[https://codepen.io/manish_shukla01/pen/ReWWmM][1]
Code in my project in app file(does not work in the sense that handlechange events are not getting fired i believe so value of my state.value1 remains blank even when i input anything):
import * as React from 'react';
import { Button, ButtonType } from 'office-ui-fabric-react';
import Header from './Header';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import * as OfficeHelpers from '#microsoft/office-js-helpers';
export default class App extends React.Component<any,any,any>{
constructor(props) {
super(props);
this.state = {
value1: '',
value2:'',
message:'Helloooo'
};
this.handleChange1 = this.handleChange1.bind(this);
this.handleChange2 = this.handleChange2.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange1(event) {
this.setState({value1: event.target.value});
}
handleChange2(event) {
this.setState({value2: event.target.value});
}
handleSubmit = async () => {
event.preventDefault();
this.setState({message: 'i got clicked'});
try {
//event.preventDefault();
await Excel.run(async context => {
/**
* Insert your Excel code here
*/
const range = context.workbook.getSelectedRange();
// Read the range address
range.load('address');
// Update the fill color
range.format.fill.color = 'blue';
range.values = [[this.state.value2]];
await context.sync();
console.log(`The range address was ${range.address}.`);
});
} catch(error) {
OfficeHelpers.UI.notify(error);
OfficeHelpers.Utilities.log(error);
};
}
render() {
return (
<form className='ms-welcome' onSubmit={this.handleSubmit}>
<Header logo='assets/logo-filled.png' title='Excel Analytics' message={this.state.message} />
<TextField label="field1"
value={this.state.value1} onChange={this.handleChange1}
required
/>
<Button className='ms-welcome__action' buttonType={ButtonType.primary}
onClick={this.handleSubmit}>Run
</Button>
</form>
);
}
}
I also faced same issue with Office UI Fabric TextField's "onChange" event and solved it using "onChanged" instead, which is deprecated as they say. But it worked for me.
First, add onChanged handler to TextField as below:
<TextField name="fieldName" label="field1" value={this.state.value1} onChanged={val => this.handleChange1("fieldName", val)} />
Also, note that "name" attribute is added to identify control in handleChange1.
Now change handler implementation as below:
handleChange1(name, value) {
this.setState(prevState => ({
result: {
...prevState.result,
[name]: value
}
}));
}
Hope this helps. Thanks!

Can't get attributes of material-ui SelectField in react

I'm using SelectField of material-ui for my react project.
I have tried many ways from this answer Can't get the target attributes of material-ui select react component
.
But they don't work.My target.id always equals ""
How can I get the attributes (like id).
Here is my code:
constructor(props) {
super(props);
this.state = {
form: {
resident_city: ''
},
ret_code: '',
ret_msg: ''
};
this.handleList = this.handleList.bind(this);
}
handleList(event, index, value) {
event.persist()
const field = event.target.id;
const form = this.state.form;
form[field] = value;
console.log(event, value)
this.setState({
form
});
console.log(form);
}
<form>
<SelectField
style={style}
id="city"
value={this.state.form.resident_city}
onChange={this.handleList}
maxHeight={200}
>
{cities}
</SelectField>
</form>
Update
I tried to use SelectField without form,and I still can't get the id attributes.It is really confusing me.
On the main component you define a prop name for select the form component let say your city component is called : cityForm
in your cityForm component
render() {
return (
<SelectField
style={style}
value={this.props.city}
onChange={(e, index, value) => this.props.selectChange(e, index, value, this.props.cityName)}
maxHeight={200}
>
{cities}
</SelectField>
);
}
}
In your main comp you will have let say (code is cutted some part omitted)
handleSelectChange(e, index, value, name){
this.setState({
[name] : value,
});
}
render(){
return (
<cityForm cityName = "city1" city={this.state.city1} selectChange={this.handleSelectChange}/>
);
}
}
Im building a dynamic form generator and it did the trick for me =).
If a React class component is used, selected value can be accessed through its state object.
Alternatively, with help of Redux the Select's onChange method can dispatch an action and update the value in the main application state.
In cases when updating the main state isn't feasible and function component is chosen instead of a class component, getting the value from the component outside the function becomes cumbersome.
An easy way to fix it would be to add a hidden input referencing the same value as select uses. Consider the following piece of code. It uses Selector field1 to update value through internal component state and binds it to the hidden field theField. The value can be further read an outside function during dispatch just like any other input field value in the form.
import React, { useState } from 'react';
import { Button, FormControl, MenuItem, Select } from '#material-ui/core';
import { connect } from 'react-redux';
const mapStateToProps = () => ({
});
const mapDispatchToProps = (dispatch, ownProps) => ({
submitForm: (event) => {
event.preventDefault();
console.log(event.target.theField.value);
dispatch({
type: 'UPDATE_THE_FIELD',
theField: event.target.theField.value,
});
}
});
function Main(props) {
const [field1, setField1] = useState("v1");
return (
<>
<form onSubmit={(event) => { props.submitForm(event, props) }}>
<FormControl>
<Select
value={field1}
onChange={(event) => setField1(event.target.value)}
>
<MenuItem value="v1">V1</MenuItem>
<MenuItem value="v2">V2</MenuItem>
</Select>
</FormControl>
<Button type="submit">Update</Button>
<input type="hidden" name="theField" value={field1} />
</form>
</>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Main);

How to onFocus and onBlur a React/Redux form field that's connected to React Date Picker?

I've got this simple v6 redux-form with an input that renders a custom component that populates its value using react-day-picker.
https://github.com/gpbl/react-day-picker
I chose react-day-picker over others because it doesn't depend on moment.js and works with my current set up.
When I focus the field, I want the datepicker to pop up, but if I click anywhere that's not the datepicker, I want it to disappear.
Essentially, I want my React datepicker to work like the jQueryUI one in:
https://jqueryui.com/datepicker/
The three scenarios I end up with are:
I click the field, the datepicker pops up, but will not disappear unless I select a date or click the field again (this is too rigid for our needs).
I click the field, the datepicker pops up, but will disappear TOO quickly if I click anywhere, as the input field's onBlur gets called before it processes the click event for the datepicker to populate the field with the chosen date.
I click the field, the datepicker pops up, gets auto-focused, blurs properly, except when I click anything that's not < body> or the datepicker.
I first tried to use a sibling empty div that wraps the whole page, so when I click the empty div, it'll toggle the datepicker properly. This worked OK with z-indexes and position: fixed until I changed the datepicker's month, which seems to re-render the datepicker and messed with the order of the clicking, which led to situation 2) again.
My most current attempt is to auto-focus the datepicker div when it pops up, so when I blur anything that's not the datepicker, it will toggle the datepicker. This worked in theory, except the datepicker is a component with many nested < div>'s inside it to control day, week, month, disabled days... so when I click a 'day', it registers a blur because I'm clicking the 'day' <div>, not the root 'datepicker' <div>, which is what was initially focused.
The solution to the above was to tweak 'datepicker' <div>'s onBlur such that it will only toggle the datepicker when document.activeElement is < body>, but that only works if I don't click another form field.
WizardFormPageOne.js:
function WizardFormPageOne({ handleSubmit }) {
return (
<form onSubmit={handleSubmit} className="col-xs-6">
<h1>WizardFormPageOne</h1>
<div className="card">
<div className="card-block">
<div className="form-group">
<label htmlFor="first">Label 1</label>
<Field type="text" name="first" component={DateInput} className="form-control" />
</div>
...
export default reduxForm({
form: 'wizardForm',
destroyOnUnmount: false,
})(WizardFormPageOne);
DateInput.js:
import React from 'react';
import styles from './styles.css';
import DatePicker from '../DatePicker';
class DateInput extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.state = {
dateValue: new Date(),
activeDateWidget: false,
};
}
changeDate = (date) => {
this.setState({
dateValue: date,
});
}
changeActiveDateWidget = (e) => {
e.stopPropagation();
this.setState({
activeDateWidget: !this.state.activeDateWidget,
});
}
render() {
const { input, meta } = this.props;
const { dateValue, activeDateWidget } = this.state;
return (
<div className={styles.dateInput}>
<input
{...input}
className="form-control"
type="text"
value={dateValue}
onClick={this.changeActiveDateWidget}
// onBlur={this.changeActiveDateWidget}
/>
{activeDateWidget ? (
<div>
<DatePicker
changeActiveDateWidget={this.changeActiveDateWidget}
changeDate={this.changeDate}
dateValue={dateValue}
/>
</div>
) : (
<div></div>
)}
</div>
);
}
}
export default DateInput;
DatePicker.js:
import React from 'react';
import 'react-day-picker/lib/style.css';
import DayPicker, { DateUtils } from 'react-day-picker';
import styles from './styles.css';
import disabledDays from './disabledDays';
class DatePicker extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.state = {
selectedDay: new Date(),
};
}
componentDidMount() {
if (this._input) {
this._input.focus();
}
}
handleDayClick = (e, day, { disabled }) => {
e.stopPropagation();
if (disabled) {
return;
}
this.setState({ selectedDay: day }, () => {
this.props.changeDate(day);
this.props.changeActiveDateWidget();
});
}
focusThisComponent = (e) => {
if (e) {
this._input = e;
}
}
focusIt = () => {
console.log('focusing');
}
blurIt = () => {
console.log('blurring');
setTimeout(() => {
if (document.activeElement == document.body) {
this.props.changeActiveDateWidget();
}
}, 1);
}
render() {
const { changeActiveDateWidget } = this.props;
const { selectedDay } = this.state;
return (
<div
className={styles.datePicker}
ref={this.focusThisComponent}
tabIndex="1"
onFocus={this.focusIt}
onBlur={this.blurIt}
>
<DayPicker
id="THISTHING"
initialMonth={selectedDay}
disabledDays={disabledDays}
selectedDays={(day) => DateUtils.isSameDay(selectedDay, day)}
onDayClick={this.handleDayClick}
/>
</div>
);
}
}
export default DatePicker;
Here's a screencast of the issue I'm having now:
http://screencast.com/t/kZuIwUzl
The datepicker toggles properly, except when clicking on another field, at which point it stops blurring/toggling properly. All my tinkering either led me make to one of the three scenarios listed above.
Bit old question but I couldn't find simple answer so here is what I did:
onBlur={(e) => {
var picker = document.querySelector(".DayPicker")
if(!picker.contains(e.relatedTarget)){
this.setState({showDayPicker: false})
}
}}
I'm setting a flag that hides DayPicker if the blur doesn't come from clicking on the DayPicker, otherwise it keeps the DayPicker displayed
You can take this example http://react-day-picker.js.org/examples/?overlay and do some small modifications to make it redux-form v6 compatible. Instead of using local state you should use this.props.input.value provided by redux-form Field component inside your render function. Additionally in the handleInputChange event handler you have to call this.props.input.onChange(e.target.value) instead of this.setState({ value: e.target.value }) and in handleInputBlur event call this.props.input.onBlur(e.target.value). That's all you have to do to make it work as a redux-form Field component.
I couldn't get Steffen's answer to work for my scenario, and the example in http://react-day-picker.js.org/examples/?overlay doesn't blur properly if you open the date picker, click the non-active parts of the date picker, then click outside the date picker. I may be nitpicking at this point, and his solution is probably far easier to implement, but here's what I did to solve it:
In DatePicker.js, set an empty array that will serve as a collection of valid < div>'s. When onBlur is triggered, invoke a recursive function that takes the root DatePicker < div>, parses all it's children, and add them to the empty array. After that, check document.activeElement to see if it's in the array. If not, then toggle the DatePicker widget, else, do nothing.
Note that the check for document.activeElement must be done one tick after the blur, or else activeElement will be < body>.
Related links:
Get the newly focussed element (if any) from the onBlur event.
Which HTML elements can receive focus?
/**
*
* DatePicker
*
*/
import React from 'react';
import 'react-day-picker/lib/style.css';
import DayPicker, { DateUtils } from 'react-day-picker';
import styles from './styles.css';
import disabledDays from './disabledDays';
class DatePicker extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.state = {
selectedDay: new Date(),
};
this.validElements = [];
}
componentDidMount() {
// Once DatePicker successfully sets a ref, component will mount
// and autofocus onto DatePicker's wrapper div.
if (this.refComponent) {
this.refComponent.focus();
}
}
setRefComponent = (e) => {
if (e) {
this.refComponent = e;
}
}
findDatePickerDOMNodes = (element) => {
if (element.hasChildNodes()) {
this.validElements.push(element);
const children = element.childNodes;
for (let i = 0; i < children.length; i++) {
this.validElements.push(children[i]);
this.findDatePickerDOMNodes(children[i]);
}
return;
}
}
handleDayClick = (e, day, { disabled }) => {
if (disabled) {
return;
}
this.setState({ selectedDay: day }, () => {
this.props.changeDate(day);
this.props.changeActiveDateWidget();
});
}
handleBlur = () => {
// Since DatePicker's wrapper div has been autofocused on mount, all
// that needs to be done is to blur on anything that's not the DatePicker.
// DatePicker has many child divs that handle things like day, week, month...
// invoke a recursive function to gather all children of root DatePicker div, then run a test against valid DatePicker elements. If test fails, then changeActiveDateWidget.
setTimeout(() => {
const rootDatePickerElement = document.getElementById('datePickerWidget');
this.findDatePickerDOMNodes(rootDatePickerElement);
if (!this.validElements.includes(document.activeElement)) {
this.props.changeActiveDateWidget();
}
}, 1);
}
render() {
const { selectedDay } = this.state;
return (
<div
className={styles.datePicker}
onBlur={this.handleBlur}
// tabIndex necessary for element to be auto-focused.
tabIndex="1"
ref={this.setRefComponent}
>
<DayPicker
initialMonth={selectedDay}
disabledDays={disabledDays}
selectedDays={(day) => DateUtils.isSameDay(selectedDay, day)}
onDayClick={this.handleDayClick}
id="datePickerWidget"
/>
</div>
);
}
}
DatePicker.propTypes = {
changeDate: React.PropTypes.func,
changeActiveDateWidget: React.PropTypes.func,
};
export default DatePicker;
and in DateInput.js, clicking the input may cause the toggle to trigger twice, so i just set it to always toggle true if clicking the input:
render() {
const { input, meta } = this.props;
const { dateValue, activeDateWidget } = this.state;
return (
<div className={styles.dateInput}>
<input
{...input}
className="form-control"
type="text"
value={dateValue}
onClick={() => { this.setState({ activeDateWidget: true }); }}
/>

Resources