currently, want to style an input element when the input element is not empty and the user has entered text. Below is the code snippet. Class is applied when !input_active but when condition is set to (!input_active && !inputEmpty) class does not apply to the input element.
constructor(props) {
super(props);
this.input_text_ref = React.createRef();
this.state = {
input_active: true,
};
}
focus = () => {
console.log("focus");
this.setState({
input_active: true
});
};
blur = () => {
console.log("blur");
this.setState({
input_active: false
});
};
handle_text_input_change = (event) => {
this.props.onChange(event);
this.setState({
text_input_value: event.target.value
});
console.log("withinhandle", this.state.text_input_value);
};
render() {
const {
value,
disabled,
...rest
} = this.props;
const {
input_active
} = this.state;
console.log(input_active);
let input_element_classes = [''];
let inputEmpty = value.length == 0;
console.log("inputempty", inputEmpty);
const inputCompiled = value.length > 0;
if (input_active && !inputEmpty) {
input_element_classes.push('blur');
}
return (
<input {...rest}
className={input_element_classes.join(' ')}
type="text"
ref={this.input_text_ref}
onChange={this.handle_text_input_change}
disabled={disabled}
onBlur={this.blur}
//onKeyDown={this.shouldBlur}
onFocus={this.focus}
/>
);
}
Could someone help me with this? Also, based on input element validation (empty, disabled, the user entering text so on) how can I change the style of the input element. From the code, it's understood that I am using an array of classes (input_element_classes) based on validation I pop the class and push some other class. Is there any other easy way to do this. thanks.
It looks a bit overload. If I got you right, you can write it this way:
export class TextInput extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isActive: props.value.length > 0
};
}
static defaultProps = {
value: "",
disabled: false
};
onFocus = () => {
this.setState({ isActive: true });
};
onBlur = ({ target }) => {
this.setState({ isActive: target.value.length > 0 });
};
render() {
const { disabled, ...rest } = this.props;
const { isActive } = this.state;
return (
<input
{...rest}
className={`text-input ${isActive ? "active" : ""}`}
type="text"
disabled={disabled}
onBlur={this.onBlur}
onFocus={this.onFocus}
/>
);
}
}
You can find the example in the sandbox https://codesandbox.io/s/zl9mmvmp33
Related
Ok, I'm new to react and mobx, and I'm experiencing some issues to manipulate the store.
When I'm typing at the input, the value gets overwritten for each char typed.
The component:
#withStore
#observer
class ConfigModel extends Component {
configModel;
constructor(props) {
super(props);
this.configModel = this.props.store.configModelStore;
}
render() {
const fieldsObj = this.configModel.modelConfig;
const fieldHelpers = this.configModel.helperModelStore.modelConfig;
const callbackOnChange = this.configModel;
const campos = merge(fieldHelpers, fieldsObj); // _.merge()
return (
<Form key={'configModelForm'}>
<>
{Object.entries(campos).map((campo) => {
if (campo[1].advanced) {
return;
}
if (campo[1].type === 'input') {
return (
<InputRender
key={campo[1].id}
field={campo[1]}
onChange={callbackOnChange.valueOnChange}
/>
);
}
})}
</>
</Form>
);
}
}
And my store define some observables (some options were omitted for simplicity, like the type evaluated at the component above):
#observable modelConfig = [{
id: 'postType',
value: '',
disabled: false,
advanced: false,
},
{
id: 'pluralName',
value: '',
disabled: false,
advanced: true,
},
...
]
And also define some actions:
#action valueOnChange = (e, {id, value}) => {
this.modelConfig.filter((config, index) => {
if (config.id === id) {
this.modelConfig[index].value = value;
console.log(this.modelConfig[index].value);
}
});
The console.log() above prints:
I truly believe that I'm forgetting some basic concept there, so can someone spot what am I doing wrong?
*EDIT:
I have another component and another store that is working correctly:
#observable name = '';
#action setName = (e) => {
this.name = e.target.value;
console.log(this.name);
}
So my question is:
Why the action that targets a specific value like this.name works fine and the action that targets a index generated value like this.modelConfig[index].value doesn't works?
The problem was at the <InputRender> component that was also receiving the #observable decorator. Just removed and it worked.
// #observer <---- REMOVED THIS
class InputRender extends Component {
render() {
const item = this.props.field;
return (
<InputField
id={item.id}
label={
<InfoLabel
label={item.label}
action={item.action}
content={item.popupContent}
/>
}
placeholder={item.placeholder}
onChange={this.props.onChange}
value={item.value}
disabled={item.disabled}
error={item.error}
throwError={item.throwError}
/>
);
}
}
basically attempting to create an Autocomplete feature for a booking engine the code is in class components want to convert it to a functional component with React Hooks.Have attempted to convert but my code is showing several warnings.can provide any code snippets if needed.
(how do you convert this.state and destructure the this keyword)
import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
class AutocompleteClass extends Component {
static propTypes = {
suggestions: PropTypes.instanceOf(Array)
};
static defaultProps = {
suggestions: []
};
constructor(props) {
super(props);
this.state = {
// The active selection's index
activeSuggestion: 0,
// The suggestions that match the user's input
filteredSuggestions: [],
// Whether or not the suggestion list is shown
showSuggestions: false,
// What the user has entered
userInput: ""
};
}
onChange = e => {
const { suggestions } = this.props;
const userInput = e.currentTarget.value;
// Filter our suggestions that don't contain the user's input
const filteredSuggestions = suggestions.filter(
suggestion =>
suggestion.toLowerCase().indexOf(userInput.toLowerCase()) > -1
);
this.setState({
activeSuggestion: 0,
filteredSuggestions,
showSuggestions: true,
userInput: e.currentTarget.value
});
};
onClick = e => {
this.setState({
activeSuggestion: 0,
filteredSuggestions: [],
showSuggestions: false,
userInput: e.currentTarget.innerText
});
};
onKeyDown = e => {
const { activeSuggestion, filteredSuggestions } = this.state;
// User pressed the enter key
if (e.keyCode === 13) {
this.setState({
activeSuggestion: 0,
showSuggestions: false,
userInput: filteredSuggestions[activeSuggestion]
});
}
// User pressed the up arrow
else if (e.keyCode === 38) {
if (activeSuggestion === 0) {
return;
}
this.setState({ activeSuggestion: activeSuggestion - 1 });
}
// User pressed the down arrow
else if (e.keyCode === 40) {
if (activeSuggestion - 1 === filteredSuggestions.length) {
return;
}
this.setState({ activeSuggestion: activeSuggestion + 1 });
}
};
render() {
const {
onChange,
onClick,
onKeyDown,
state: {
activeSuggestion,
filteredSuggestions,
showSuggestions,
userInput
}
} = this;
let suggestionsListComponent;
if (showSuggestions && userInput) {
if (filteredSuggestions.length) {
suggestionsListComponent = (
<ul class="suggestions">
{filteredSuggestions.map((suggestion, index) => {
let className;
// Flag the active suggestion with a class
if (index === activeSuggestion) {
className = "suggestion-active";
}
return (
<li className={className} key={suggestion} onClick={onClick}>
{suggestion}
</li>
);
})}
</ul>
);
} else {
suggestionsListComponent = (
<div class="no-suggestions">
<em>No suggestions, you're on your own!</em>
</div>
);
}
}
return (
<Fragment>
<input
type="text"
onChange={onChange}
onKeyDown={onKeyDown}
value={userInput}
/>
{suggestionsListComponent}
</Fragment>
);
}
}
export default AutocompleteClass;
I am sending an array named tags as props and i want to assign it in initial states so that those array elements should be displayed. using this code they are displayed properly but i am unable to edit them. when i click cross it gets deleted but after sometime it displays again. Seems like setState in handelClose method is not working.
import React from 'react';
import ReactDOM from 'react-dom';
import { Tag, Input, Tooltip, Button } from 'antd';
class EditableTagGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
tags: [],
inputVisible: false,
inputValue: '',
};
}
handleClose = (removedTag) => {
// debugger;
const tags = this.state.tags.filter(tag => tag !== removedTag);
console.log(tags);
// debugger;
this.setState({ tags: tags });
}
showInput = () => {
this.setState({ inputVisible: true }, () => this.input.focus());
}
handleInputChange = (e) => {
this.setState({ inputValue: e.target.value });
}
handleInputConfirm = () => {
const state = this.state;
const inputValue = state.inputValue;
let tags = state.tags;
if (inputValue && tags.indexOf(inputValue) === -1) {
tags = [...tags, inputValue];
}
console.log(tags);
this.setState({
tags,
inputVisible: false,
inputValue: '',
});
this.props.onSelect(tags);
}
// componentDidUpdate(prevProps, prevState) {
// this.setState({tags: this.props.tags})
// }
componentDidUpdate(prevProps, prevState) {
// debugger;
if (this.state.tags !== this.props.tags) {
this.setState({tags: this.props.tags})
}
}
saveInputRef = input => this.input = input
render() {
const {tags , inputVisible, inputValue } = this.state;
return (
<div>
{tags.map((tag, index) => {
const isLongTag = tag.length > 20;
const tagElem = (
<Tag key={tag} closable={index !== -1} afterClose={() => this.handleClose(tag)}>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</Tag>
);
return isLongTag ? <Tooltip title={tag} key={tag}>{tagElem}</Tooltip> : tagElem;
})}
{inputVisible && (
<Input
ref={this.saveInputRef}
type="text"
size="small"
style={{ width: 78 }}
value={inputValue}
onChange={this.handleInputChange}
onBlur={this.handleInputConfirm}
onPressEnter={this.handleInputConfirm}
/>
)}
{!inputVisible && <Button size="small" type="dashed" onClick={this.showInput}>+ New Tag</Button>}
</div>
);
}
}
export default EditableTagGroup
The problem is that, you are saving and using the props in the local state and then modifying those, however everytime the component updates, you are setting the state back to the props.
// this is where you are setting the state back to
// props and hence your edit disappears
componentDidUpdate(prevProps, prevState) {
// debugger;
if (this.state.tags !== this.props.tags) {
this.setState({tags: this.props.tags})
}
}
What you need to do is to not maintain, the props in state but rather directly modifying it, by passing a handler from the parent to update the props.
See this answer on how to pass and update data in parent
How to pass data from child component to its parent in ReactJS?
Help,please. I use React js and Material-IU. I did "incorrect" red color textfield validation with errorText. How can i validate "correct" with green color on buttonsubmit click?
validate = () => {
const cardNumber = '1234567890123456'; //credit card form with 2 fields
const expiryNumber = '1234';
let isError = false;
const errors = {};
if (this.state.number != cardNumber) { //cardNumber validation
isError = true;
errors.numberError = 'failed';
}
if (this.state.expiry != expiryNumber) { //expiry validation
isError = true;
errors.expiryError = "failed";
}
if(isError) {
this.setState({
...this.state,
...errors
});
}
return isError;
}
onSubmit = e => { // on Button submit
e.preventDefault();
const err = this.validate();
if (!err) {
this.setState({
number: "",
expiry: ""
});
console.log(this.state);
};
} // next will be render <TextField>
I tried using an extra field in the component state, successValidation, that I change in onSubmit depending on the validation outcome. I then used that state property to determine what style to pass on to underlineStyle (property of the TextField). Maybe not the cleanest solution (don't know) but as far as I can tell it works. You can see it in action in this JSFiddle demo.
By the way: It was a while since I used Material-UI, but they have pretty good documentation to refresh your memory. You can, for example, see all the props for TextField at the bottom of this page.
const { RaisedButton, MuiThemeProvider, getMuiTheme, TextField } = MaterialUI;
class Test extends React.Component {
state = {
value: 'good',
error: false,
successValidation: false
}
handleInputChange = (e, newValue) => {
// reset errors and validation as well
this.setState({
value: newValue,
error: false,
successValidation: false
});
}
isValid = () => {
const {value} = this.state;
return value != 'bad';
}
onSubmit = () => {
if(this.isValid()) {
this.setState({successValidation: true, error: false});
} else {
this.setState({error: true});
}
}
render = () => {
const {value, error, successValidation} = this.state;
const underlineStyle = successValidation ? { borderColor: 'green' } : null;
return (
<div style={{width: '50%', margin: '0 auto'}}>
<h3>The input "bad" will cause error</h3>
<h3>All other input will cause green underline on submit</h3>
<TextField
errorText={error ? 'Validation error!' : ''}
name="test"
value={value}
onChange={ this.handleInputChange }
underlineStyle={underlineStyle}
/>
<RaisedButton onClick={ this.onSubmit }>Submit</RaisedButton>
</div>
);
}
}
const App = () => (
<MuiThemeProvider muiTheme={getMuiTheme()}>
<Test />
</MuiThemeProvider>
);
ReactDOM.render(
<App />,
document.getElementById('container')
);
I am trying to use redux-form with react-widget Multiselect this example:
var Multiselect = ReactWidgets.Multiselect
, people = listOfPeople();
var Example = React.createClass({
getInitialState() {
return { value: people.slice(0,2) };
},
_create(name){
var tag = { name, id: people.length + 1 }
var value = this.state.value.concat(tag)
// add new tag to the data list
people.push(tag)
//add new tag to the list of values
this.setState({ value })
},
render(){
// create a tag object
return (
<Multiselect data={people}
value={this.state.value}
textField="name"
onCreate={this._create}
onChange={value => this.setState({ value })}/>
)
}
});
ReactDOM.render(<Example/>, mountNode);
Below is a code snippet for a parent component which makes usage of redux-form (EditVideo component) component (please look at the comments in onSubmit method):
class VideoEdit extends React.Component {
constructor(props) {
super(props);
}
onSubmit = (values) => {
console.log(values.categories) // always returns initialValues for categories, new values not adding
}
render() {
const { loading, videoEdit, categories } = this.props;
if (loading) {
return (
<div>{ /* loading... */}</div>
);
} else {
return (
<div>
<h2>Edit: {videoEdit.title}</h2>
<EditVideo
onSubmit={this.onSubmit}
initialValues={videoEdit}
categories={categories}
/>
</div>
);
}
}
}
And here is a code snippet of redux-form component with react-widget Multiselect component:
class CategoryWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.defValue,
extData: this.props.data
}
this._create = this._create.bind(this);
}
_create(name) {
var tag = { name, id: this.state.extData.length + 100 + 1 }
var value = this.state.value.concat(tag)
var extData = this.state.extData.concat(tag)
this.setState({
extData,
value
})
}
render() {
return (
<Multiselect
{...this.props.input}
data={this.state.extData}
onBlur={() => this.props.input.onBlur()}
value={this.state.value || []}
valueField="id"
textField="name"
onCreate={this._create}
onChange={value => this.setState({ value })}
/>
)
}
}
const EditVideoForm = (props) => {
const { handleSubmit, submitting, onSubmit, categories, initialValues, defBook } = props;
return (
<Form name="ytvideo" onSubmit={handleSubmit(onSubmit)}>
<div>
<Field
name="categories"
component={CategoryWidget}
data={categories}
defValue={initialValues.categories}
/>
</div>
<br />
<Button color="primary" type="submit" disabled={submitting}>
Submit
</Button>
</Form>
);
};
export default reduxForm({
form: 'videoEdit',
enableReinitialize: true
})(EditVideoForm);
The Multiselect widget works as expected, yet the form on submit always returns the same initial values for categories.
I believe the problem lays in the fact that CategoryWidget is a class base component? If so, what is a way to make it work?
Here is what I have done for my Multiselect at the end:
class CategoryWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.defValue,
extData: this.props.data
}
this._create = this._create.bind(this);
}
_create(name) {
var tag = { name, id: this.state.extData.length + 100 + 1 }
var value = this.state.value.concat(tag)
var extData = this.state.extData.concat(tag)
this.setState({
extData,
value
})
}
componentDidUpdate() {
let { onChange } = this.props.input
onChange(this.state.value)
}
handleOnChange(value) {
this.setState({ value })
}
render() {
const input = this.props.input
return (
<Multiselect
{...input}
data={this.state.extData}
onBlur={() => input.onBlur()}
value={this.state.value || []}
valueField="id"
textField="name"
onCreate={this._create}
onChange={value => this.handleOnChange(value)}
/>
)
}
}