I'm programatically validating an email and password inputs for simple login, here is the function that call other function that validate the email.
handleLogin(event) {
this.validateEmail();
this.validatePassword();
if (this.state.emailValid === 'error' || this.state.passwordValid === 'error') {
alert('invalid form');
return;
};
const email = ReactDOM.findDOMNode(this.refs.email).value;
const password = ReactDOM.findDOMNode(this.refs.password).value;
const creds = { email: email, password: password }
this.props.onLoginClick(creds)
}
Notice that first than all I'm calling the validateEmail() function which modifies the store that indicates if the input is correct, here's the validateEmail() source code:
validateEmail() {
const email = ReactDOM.findDOMNode(this.refs.email).value;
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
if (!validEmail) {
this.setState({
emailValid: 'error'
});
return;
}
this.setState({
emailValid: 'success'
});
}
But in the if statement the state.emailValid has not been yet updated, this is a delay in the state modifying, so the alert() is not displayed. How to get the updated state correctly?
Thanks
The thing to note here is that setState is asynchronous. It will not update the state until everything else that is synchronous in your handleLogin method has completed.
With React I like to use state as a single source of truth as often as I can. In the example above you have the html element as a source of truth and state. By changing your components to be controlled by the react state, you can validate your forms on each keystroke.
Forms and Controlled Components
Start by keeping the state of your input in state
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
emailValid: true,
};
// we bind the function in case we want to
// control text in child component
this.emailChange = this.handleEmailChange.bind(this);
}
emailChange(event) {
this.setState({email: event.target.value});
}
render() {
<textarea value={this.state.email} onChange={this.emailChange} />
}
}
Now whenever you type the state of your html input is handled in react. This will enable you to more easily check its validity. We can do this by adding another method to our class:
class LoginForm extends React.Component {
// ...all the stuff from above
validateEmail() {
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
if (!validEmail) {
// Object.assign just ensures immutability
this.setState(Object.assign({}, this.state, {
emailValid: false
}))
} else {
// If using babel, this is ensure immutable also
this.setState({
...state,
emailValid: true
})
}
}
// or....
validateEmail() {
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
this.setState({...state, emailValid: validEmail})
}
// ...render method
}
The validation will now occur on each keystroke. When you need to submit your form all you need to do is check the state if the data is valid and dont need to reference the dom. You can send the data from state.
Related
I build a simple page and it renders a list of some items. These items have a href element to open it in browser. The trouble is that when user comes back all the class used to hold local variable gets reinitialized and breaks the app functionality. What is the correct way to handle this. I want to retain the data set. Below is my code for list and the variables in a service class
<ion-content>
<div class="full-screen-bg">
<div *ngFor="let alb of core.albums">
<ion-row><ion-col no-padding text-center><img src="{{alb.image}}"></ion-col></ion-row>
<ion-row><ion-col class="sg-title-text">{{alb.name}}</ion-col></ion-row>
</div>
the service class that has variables and gets reset is
#Injectable({
providedIn: 'root'
})
export class CoreService {
loggedIn:boolean
name:string
constructor(public admob: Admob,
public sgSvc:DataServiceService) {
this.loggedIn = false
console.log("album constructor called:::" + this.loggedIn + " name:" + this.name)
}
}
The simple way is save login user (username, token and expiration time) by StorageService, and for your CoreService should be try get it from StorageService and confirm it, if token is none or already over exporation time then it is mean user should be login again.
export class CoreService {
//...
public isAdmin = false;
private token = false;
public userSubject: Subject<Object> = new Subject<Object>();
//...
constructor(/*...*/) {
this.userSubject.subscribe(user => {
if (user && user['role']) {
if (user['role'] == 'admin') {
this.isAdmin = true;
} else {
this.isAdmin = false;
}
}
});
this.getLocal('user').then(val => {
this.userSubject.next(val);
});
}
//...
}
//And for other component also subscribe same subject like
export class AppComponent /*...*/ {
constructor(coreService: CoreService /*...*/) {
this.coreService.userSubject.subscribe(user => {
// your logic there
});
}
}
Ok, so I wanted to deal with whitespaces at the beginning of the input in my register form and I have achieved this by providing plugin for redux-form reducer:
export default function(cookies, server) {
return combineReducers({
auth: auth(cookies, server),
reduxAsyncConnect,
alert,
programs,
exercises,
routing: routerReducer,
form: form.plugin(formPlugin),
profile,
spinner,
companies
});
}
The plugin is:
import {ltrim} from './ltrim';
const formPlugin = {
registerForm: (state, action) => {
switch(action.type) {
case '##redux-form/CHANGE':
if (action.meta.form === 'registerForm') {
return {
...state,
values: {
...state.values,
[action.meta.field]: ltrim(action.payload)
}
}
} else {
return state;
}
default:
return state;
}
}
}
export default formPlugin;
How can I get the same effect on all forms withouth hardcoding? Maybe I have to somehow edit redux-form CHANGE action to achieve this?
You could make a custom Field component that does this for you.
import { Field } from 'redux-form';
import {ltrim} from './ltrim';
const normalize = value => ltrim(value);
const TrimmedField = props => <Field {...props} normalize={normalize} />;
You could also give your custom normalize a callback argument if you need to do other normalization in some cases.
http://redux-form.com/6.6.3/docs/api/Field.md/#-normalize-value-previousvalue-allvalues-previousallvalues-nextvalue-optional-
Given that there is not much examples about this, I am following the docs as best as I can, but the validation is not reactive.
I declare a schema :
import { Tracker } from 'meteor/tracker';
import SimpleSchema from 'simpl-schema';
export const modelSchema = new SimpleSchema({
foo: {
type: String,
custom() {
setTimeout(() => {
this.addValidationErrors([{ name: 'foo', type: 'notUnique' }]);
}, 100); // simulate async
return false;
}
}
}, {
tracker: Tracker
});
then I use this schema in my component :
export default class InventoryItemForm extends TrackerReact(Component) {
constructor(props) {
super(props);
this.validation = modelSchema.newContext();
this.state = {
isValid: this.validation.isValid()
};
}
...
render() {
...
const errors = this.validation._validationErrors;
return (
...
)
}
}
So, whenever I try to validate foo, the asynchronous' custom function is called, and the proper addValidationErrors function is called, but the component is never re-rendered when this.validation.isValid() is supposed to be false.
What am I missing?
There are actually two errors in your code. Firstly this.addValidationErrors cannot be used asynchronously inside custom validation, as it does not refer to the correct validation context. Secondly, TrackerReact only registers reactive data sources (such as .isValid) inside the render function, so it's not sufficient to only access _validationErrors in it. Thus to get it working you need to use a named validation context, and call isValid in the render function (or some other function called by it) like this:
in the validation
custom() {
setTimeout(() => {
modelSchema.namedContext().addValidationErrors([
{ name: 'foo', type: 'notUnique' }
]);
}, 100);
}
the component
export default class InventoryItemForm extends TrackerReact(Component) {
constructor(props) {
super(props);
this.validation = modelSchema.namedContext();
}
render() {
let errors = [];
if (!this.validation.isValid()) {
errors = this.validation._validationErrors;
}
return (
...
)
}
}
See more about asynchronous validation here.
I have a wrapper around the Editor provided by Draft.js, and I would like to get the tab/shift-tab keys working like they should for the UL and OL. I have the following methods defined:
_onChange(editorState) {
this.setState({editorState});
if (this.props.onChange) {
this.props.onChange(
new CustomEvent('chimpeditor_update',
{
detail: stateToHTML(editorState.getCurrentContent())
})
);
}
}
_onTab(event) {
console.log('onTab');
this._onChange(RichUtils.onTab(event, this.state.editorState, 6));
}
Here I have a method, _onTab, which is connected to the Editor.onTab, where I call RichUtil.onTab(), which I assume returns the updated EditorState, which I then pass to a generic method that updates the EditorState and calls some callbacks. But, when I hit tab or shift-tab, nothing happens at all.
So this came up while implementing with React Hooks, and a google search had this answer as the #2 result.
I believe the code OP has is correct, and I was seeing "nothing happening" as well. The problem turned out to be not including the Draft.css styles.
import 'draft-js/dist/Draft.css'
import { Editor, RichUtils, getDefaultKeyBinding } from 'draft-js'
handleEditorChange = editorState => this.setState({ editorState })
handleKeyBindings = e => {
const { editorState } = this.state
if (e.keyCode === 9) {
const newEditorState = RichUtils.onTab(e, editorState, 6 /* maxDepth */)
if (newEditorState !== editorState) {
this.handleEditorChange(newEditorState)
}
return
}
return getDefaultKeyBinding(e)
}
render() {
return <Editor onTab={this.handleKeyBindings} />
}
The following example will inject \t into the current location, and update the state accordingly.
function custKeyBindingFn(event) {
if (event.keyCode === 9) {
let newContentState = Modifier.replaceText(
editorState.getCurrentContent(),
editorState.getSelection(),
'\t'
);
setEditorState(EditorState.push(editorState, newContentState, 'insert-characters'));
event.preventDefault(); // For good measure. (?)
return null;
}
return getDefaultKeyBinding(event);
}
Within my component, how can I access the name of the parent component it is nested inside?
So if my render is thus:
ReactDOM.render(
<RadialsDisplay data={imagedata}/>,
document.getElementById('radials-1')
);
How can I retrieve the id name #radials-1 from within the component itself?
It probably makes the most sense to pass it as a property, but if you really need to get it programmatically, and from inside the component, you can wait for the component to mount, find its DOM node, and then look at its parent.
Here's an example:
class Application extends React.Component {
constructor() {
super();
this.state = { containerId: "" };
}
componentDidMount() {
this.setState({
containerId: ReactDOM.findDOMNode(this).parentNode.getAttribute("id")
});
}
render() {
return <div>My container's ID is: {this.state.containerId}</div>;
}
}
ReactDOM.render(<Application />, document.getElementById("react-app-container"));
Working demo: https://jsbin.com/yayepa/1/edit?html,js,output
If you do this a lot, or want to be really fancy, you could utilize a higher-order component:
class ContainerIdDetector extends React.Component {
constructor() {
super();
this.state = { containerId: "" };
}
componentDidMount() {
this.setState({
containerId: ReactDOM.findDOMNode(this).parentNode.getAttribute("id")
});
}
render() {
if (!this.state.containerId) {
return <span />;
} else {
return React.cloneElement(
React.Children.only(this.props.children),
{ [this.props.property]: this.state.containerId }
);
}
}
}
ContainerIdDetector.propTypes = {
property: React.PropTypes.string.isRequired
}
// Takes an optional property name `property` and returns a function. This
// returned function takes a component class and returns a new one
// that, when rendered, automatically receives the ID of its parent
// DOM node on the property identified by `property`.
function withContainerId(property = "containerId") {
return (Component) => (props) =>
<ContainerIdDetector property={property}>
<Component {...props} />
</ContainerIdDetector>
}
Here, withContainerId is a function that takes an argument called property and returns a new function. This function can take a component type as its only argument, and returns a higher-order component. When rendered, the new component will render the passed component, with all its original props, plus an additional prop specifying the parent container's ID on the property specified by the property argument.
You can use them with ES7 decorators (as currently implemented) if you wish, or via a regular function call:
#withContainerId()
class Application extends React.Component {
render() {
return <div>My containers ID is: {this.props.containerId}</div>;
}
}
// or, if you don't use decorators:
//
// Application = withContainerId()(Application);
ReactDOM.render(<Application />, document.getElementById("react-app-container"));
Working demo: https://jsbin.com/zozumi/edit?html,js,output