React refactor simple input component - javascript

I have multiples components who needs input value
so I copy/paste my following code on the render method :
<input type="password"
name="password" value={user.password}
placeholder="password"
onChange={e => this.handleChange(e)}/>
And I copy/paste the handleChange method in all my component :
handleChange(event) {
const {name, value} = event.target
this.setState({
[name]: value,
})
}
I would like to have input in a component and call it from all my components and then get input value to add it to my current state component
Do you have any solution ?
Thanks

If I understand correctly you wan't something like this:
class MyWrapperComponent extends React.Component {
constructor(props) {
this.state = { password: '' }
}
handleChange(event) {
const {name, value} = event.target
this.setState({
[name]: value,
})
}
render() {
return (
<div>
<MyInputComponent value={this.state.password} onChange={this.handeChange.bind(this)} />
<MyDisplayPasswordComponent password={this.state.password} />
</div>
)
}
}
const MyInputComponent = (props) => {
return (
<input type="password"
name="password" value={props.password}
placeholder="password"
onChange={props.onChange}/>
)
}
const MyDisplayPasswordComponent = (props) => {
return <h1>Your secret password is {props.password}</h1>
}

Related

can I update input field value with setState in react

I'm new to React and I implemented a form which uses Material-UI and react-input-mask library. I would like to know how to change user input value in the input field.
For example, I have this field:
class TemplateForm extends React.Component {
constructor(props) {
super(props);
this.state= {
api_access: true,
currency: "",
sku: "",
}
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onChange = (event) => {
const {name, value} = event.target
this.setState({
[name]: value
})
}
onSubmit = (formVals) => {
return false;
}
renderField=({input,label, meta:{touched,error}})=> (
<InputMask mask="99/9999"
value = {this.state.edate}
name="edate"
type="tel"
required
{...input}
>
{(inputProps) =>
<TextField
{...inputProps}
label={label}
fullWidth
/>}
</InputMask>
)
handleBlur = (value) => {
valueM = value.split("/")
if (valueM[0].length< 2) {
valueM[0]= "0"+valueM[0]
}
valueM = valueM.join("/")
this.setState({
ename:valueM
})
}
<Field id="ename" name="ename" onBlur={this.handleBlur} component={this.renderField} />
}
I would like to change value of the month field with a leading zero when user enters only one digit. Like if user enters 1_/2022 should be converted to 01/2022 but this.setState() isn't changing the value in the field. How to achieve this?

React on Rails: Can't enter text in my form input boxes

When I try to type in the boxes on the webpage it doesn't register that I am typing anything. I am guessing it has something to do with the handleChange or onChange, but I could use some help here. I am still pretty new to React and trying to figure it out. What am I missing here?
import React, {Component} from 'react';
import { Form } from 'semantic-ui-react';
class Assessments extends Component {
state = {assessment_name: '', assessment_type: ''}
componentDidMount() {
if (this.props.id) {
const { assessment_name, assessment_type } = this.props
this.setState({ assessment_name, assessment_type })
}
}
handleChange = (a) => {
const { name, value } = a.target
this.setState({ [name]: value })
}
handleSubmit = (a) => {
a.preventDefault()
if (this.props.id) {
const { id, history } = this.props
this.props.updateName(id, this.state, history)
this.props.toggleUpdate()
}
this.props.close()
this.setState({ assessment_name: '', assessment_type: ''})
}
close = () => this.setState({ open: false })
render() {
const { assessment_name, assessment_type } = this.state
return(
<Form onSubmit={this.handleSubmit}>
<Form.Input
name=''
value={assessment_name}
onChange={this.handleChange}
label='Assessment Name'
required
/>
<Form.Input
name='AssessmentType'
value={assessment_type}
onChange={this.handleChange}
label='Assessment Type'
required
/>
<Form.Button>Submit</Form.Button>
</Form>
)
}
}
export default Assessments;
You're not passing the right names to the Form.Input components which the handleChange function uses to update the state. They have to be 'assessment_name' and 'assessment_type' respectively to make sure the state gets updated on input change events and the new values get reflected on the fields.
<>
<Form.Input
name="assessment_name"
value={assessment_name}
onChange={this.handleChange}
label="Assessment Name"
required
/>
<Form.Input
name="assessment_type"
value={assessment_type}
onChange={this.handleChange}
label="Assessment Type"
required
/>
</>

React Hooks: handle multiple inputs

on react docs forms section there is the following example using class components:
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
Considering Hooks can only be called either in a React function component or a custom React Hook function is there a way of doing it using hooks instead?
you can clean up #adam 's final solution a bit by not using the useCallback hook, and instead simply using the useState hook as a controlled component.
const MyComponent = () => {
const [inputs, setInputs] = useState({});
const handleChange = e => setInputs(prevState => ({ ...prevState, [e.target.name]: e.target.value }));
return (
<>
<input name="field1" value={inputs.field1 || ''} onChange={handleChange} />
<input name="field2" value={inputs.field2 || ''} onChange={handleChange} />
</>
)
}
example
const MyComponent = () => {
const [inputs,setInputs] = useState({});
return (
<>
<input key="field1" name="field1" onChange={({target}) => setInputs(state => ({...state,field1:target.value}))} value={inputs.field1}/>
<input key="field2" name="field2" onChange={({target}) => setInputs(state => ({...state,field2:target.value}))} value={inputs.field2}/>
</>
)
}
you can pass in initial values like this:
const MyComponent = (initialValues = {}) => {
const [inputs,setInputs] = useState(initialValues);
...
}
EDIT: A nice short onChange according to #hamidreza's comment
const MyComponent = (initialValues = {}) => {
const [inputs,setInputs] = useState(initialValues);
const onChangeHandler = useCallback(
({target:{name,value}}) => setInputs(state => ({ ...state, [name]:value }), [])
);
return (
<>
<input key="field1" name="field1" onChange={onChangeHandler} value={inputs.field1}/>
<input key="field2" name="field2" onChange={onChangeHandler} value={inputs.field2}/>
</>
)
}
etc, etc, etc
Maybe, on the last example onChangeForField('...') will be triggered on each render, so maybe you have to write onChange={()=>onChangeForField('...')} or if you want the event to get passed onChange={(e)=>onChangeForField('...', e)}
I was looking for the same answer,but i was finding difficulty to understand the previous solutions,so i tried in my own way ,and i found a solution.
const [inputs,setInputs] = useState({
'field1':'',
'field2':'',
});
const handleChange = (e) => {
const name = e.target.name; //it is the name of that input
const value = e.target.value; //value of that input
setInputs((prev) => {
prev[name] = value;//changing the updated value to the previous state
return prev;
});
};
return (
<>
<input key="field1" name="field1" onChange={handleChange} value={inputs.field1}/>
<input key="field2" name="field2" onChange={handleChange} value={inputs.field2}/>
</>
adding to Adam's answer and for those who are looking towards typescript solution,
interface MyIType {
field1: string;
...
}
//Partial from typescript to make properties optional
interface MyFormType extends Partial<MyIType> {}
const [inputs,setInputs] = useState<MyFormType>(initialValues);
const onChangeForField = useCallback(({target}) =>
setInputs(_state => {
return {
..._state,
[target.name]: target.value,
};
}),
[]
);
If you were like me, having multiple inputs on multiple pages using the same input id/name/key, try value={data.xxx || ''} .
Full code:
const [data, setData] = useState<any>({});
const handleValueChanges = e => {
setData({
...data,
[e.target.name]: e.target.value,
});
};
<InputText (using prime react)
id="firstName"
name="firstName"
value={data.firstName || ''}
onChange={handleUpdate}
/>
As of v6 you can use .forEach(), Please refer to the migrate guide
[{name: "firstName", value: "Safwat" }, {name: "lastName", value: "Fathi", }].forEach(({name, value}) => setValue(name, value));

input target value from child component

I've managed to get my input component to render onto the dom, however I'm having a bit of trouble accessing the props.
Functional input component
const InputField = props => {
const { inputValue, updateInputValue } = props
return (
<Input>
<input
type="text"
placeholder="Please specify"
value={inputValue}
onChange={updateInputValue}
/>
<hr />
<label>Other</label>
</Input>
)
}
The component is only rendered to the dom based on an object property inside of an array
const MultiChoiceQuestions = props => {
const { multiChoiceArray, handleClick } = props
return (
<ButtonContainer>
{multiChoiceArray.map(questionChoice => {
if (questionChoice.type === 'input') {
return <InputField />
}
return (
<Button type="button" key={questionChoice.id} onClick={() => handleClick(questionChoice)}>
{questionChoice.text}
</Button>
)
})}
</ButtonContainer>
)
}
The multiChoice component is imported once again to create a top-level component that the app consumes
const Question = props => {
let responses
switch (props.data.type) {
case 'multiChoice':
responses = (
<MultiChoiceQuestions
multiChoiceArray={props.data.choices}
handleClick={props.handleClick}
inputValue={props.inputValue}
updateInputValue={props.updateInputValue}
/>
)
break
default:
responses = <div>Error: no question type: `{props.data.type}`</div>
}
const { data } = props
return (
<AnimatedDiv key={data.id}>
<QuestionText>{data.text}</QuestionText>
{responses}
</AnimatedDiv>
)
}
And the final component looks like this
class Survey extends Component {
constructor(props) {
super(props)
this.state = {
currentQuestionId: 1,
userAnswers: [],
isActive: false,
inputValue: '',
}
this.selectAnswer = this.selectAnswer.bind(this)
this.test = this.test.bind(this)
}
selectAnswer = answer => {
this.setState(state => ({
currentQuestionId: state.currentQuestionId + 1,
userAnswers: state.userAnswers.concat([answer]),
isActive: !state.isActive,
}))
}
checkInput = event => {
this.setState({
inputValue: event.target.value,
})
}
test = event => {
console.log(event.target.value)
}
render() {
const { currentQuestionId, isActive, inputValue } = this.state
const { questions } = this.props
const currentPercentage = (currentQuestionId * 100) / questions.length
return (
<SurveyContainer>
<Question
data={questions.find(q => q.id === currentQuestionId)}
className={isActive ? 'active' : ''}
handleClick={this.selectAnswer}
value={inputValue}
onChange={this.test}
/>
</SurveyContainer>
)
}
}
The InputField component renders out just fine, however, the function for my onChange event is not firing...There's a mistake somewhere in the pipeline, probably inside the question component?
It looks like you haven't passed any props to <InputField /> in your MultiChoiceQuestions component.
I can not see where you pass props from
<MultiChoiceQuestions>
...
<InputFiled props={props} />
...
</MultiChoiceQuestions>
Probably pass only the props which are needed in InputField component, such as inputValue, updateInputValue:
<InputFiled
inputValue={inputValue}
updateInputValue={updateInputValue}
/>
const InputField = (inputValue, updateInputValue) => {
...
<input
type="text"
placeholder="Please specify"
value={inputValue}
onChange={(e) => updateInputValue(e)}
/>
...
}
Hope that will help.

Using multiple filters re-renders component

I use filters in an application which limit the output of a list of users. See the example of the same concept in my codesandbox
The idea is that the list accepts multiple filter values to narrow down the search as much as needed. The first filter works fine when typing in any characters, but switching over and then also typing something inside the second filter input rerenders the list and overwrites the search findings of the first filter.
Main Component:
<div className="App">
<Filters onChange={this.handleFilter} />
<div className="list">
<List users={filteredUsers} />
</div>
</div>
Filter Inputs:
<div className="filters">
<input
name="name"
type="text"
placeholder="Search by name"
onChange={props.onChange}
/>
<input
name="email"
type="text"
placeholder="Search by email"
onChange={props.onChange}
/>
</div>
filter handler:
handleFilter = event => {
const target = event.target;
let updateUsers = this.state.users;
updateUsers = updateUsers.filter(user => {
let type;
if (target.name === "name") {
type = user.name;
} else if (target.name === "email") {
type = user.email;
}
return type.toLowerCase().search(target.value.toLowerCase()) !== -1;
});
this.setState({ filteredUsers: updateUsers });
};
I do plan on using several more filters and the list should not re-render.
What is a way to prevent this or work out a better solution?
How to big websites apply their filters?
You could set filter values of email and name both to state, then trigger a filter function that uses both of those values for the final result. You should also store the values of filter in state, either that of component or a parent. This way you shouldn't have your search values overwritten.
import React, { Component } from "react";
const Filters = ({ onChange, emailFilterValue, nameFilterValue }) => (
<div className="filters">
<input
name="name"
type="text"
placeholder="Search by name"
onChange={onChange}
value={nameFilterValue}
/>
<input
name="email"
type="text"
placeholder="Search by email"
onChange={onChange}
value={emailFilterValue}
/>
</div>
);
class Main extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
nameFilter: "",
emailFilter: ""
};
}
handleFilter = e => {
const { name, value } = e.target;
if (name === "email") {
this.setState({ emailFilter: value });
} else if (name === "name") {
this.setState({ nameFilter: value });
}
this.filterUsers();
};
filterUsers = () => {
const { users, nameFilter, emailFilter } = this.state;
let updateUsers = users.slice();
if (nameFilter.length > "") {
// do your search based on name
}
if (emailFilter.length > "") {
// then apply email filter
}
this.setState({ users: updateUsers });
};
render() {
return (
<div className="App">
<Filters
onChange={this.handleFilter}
emailFilterValue={this.state.emailFilter}
nameFilterValue={this.state.nameFilter}
/>
<div className="list">
<List users={filteredUsers} />
</div>
</div>
);
}
}
Here is a sample of my solution. Generally "big websites" reduce the amount of time filtering as much as possible if it's unnecessary. I would equally add a setTimeout() to not filter on EVERY input immediately, and clear said timeout if the user types within 400ms (for example).
In your filter component I would keep track of the filtered words:
class Filters extends React.Component {
constructor(props) {
super(props);
this.state = {
email: "",
name: ""
};
}
handleChange = event => {
const { onChange } = this.props;
let key = "email";
if (event.target.name === "name") {
key = "name";
}
this.setState({ [key]: event.target.value });
onChange({ email: this.state.email, name: this.state.name });
};
render() {
const { onChange } = this.props;
return (
<div className="filters">
<input
name="name"
type="text"
placeholder="Search by name"
onInput={this.handleChange}
/>
<input
name="email"
type="text"
placeholder="Search by email"
onInput={this.handleChange}
/>
</div>
);
}
}
And in your App component I would create two different filters, this way it puts a priority on your "name" property if it's filled out:
handleFilter = data => {
let updateUsers = this.state.users;
console.log(data);
if (data.name.length > 0) {
updateUsers = updateUsers.filter(user => {
return user.name.toLowerCase().includes(data.name);
});
}
if (data.email.length > 0) {
updateUsers = updateUsers.filter(user => {
return user.email.toLowerCase().includes(data.email);
});
}
this.setState({ filteredUsers: updateUsers });
};

Categories

Resources