I just got started in React and am confused about something. Let's say I have a Login controller view (smart component), and then a LoginForm view (dumb component), and within the LoginForm, I created custom components for to handle all my inputs on this page the same.
class TextInput extends React.Component {
render() {
return (
<input type="text"
name={this.props.name}
className="form-control"
placeholder={this.props.placeholder}
ref={this.props.name}
onChange={this.props.onChange}
value={this.props.value} />
);
}
}
class LoginForm extends React.Component {
render() {
return (
<TextInput
name="username"
placeholder="Username"
onChange={this.props.onChange}
value={this.props.username} />
<TextInput
name="address"
placeholder="Address"
onChange={this.props.onChange}
value={this.props.address} />
);
}
}
I'm then not sure how in my top level Login controller view's render function, when I create the
How do you get a different to be called for the different inputs? Because my top level component sets the properties down right? Since the bottom most component expects an onChange property, from the top level when I pass down onChange, aren't all TextInputs going to call the same onChange method instead of two separate methods?
Yes, the same function is called. What changes is the parameter. This is just old school JS. The input will pass the change event to to the onChange function as the first param.
You will need to handle that in one component or another.
onChange(e){
var formKeyVal = {};
formKeyVal[e.target.name] = e.target.value;
this.setState(formKeyVal);
}
Thanks to refs We can set up different referring to each low-level element. No matter, dumb or smart.
Your high-level component contains:
`handleText(){
this.setState({
text: ReactDOM.findDOMNode(this.refs.yourElement).value
})
}`
Your low-level component contains:
<input
type="text"
ref="yourElement"
onChange={this.props.textInput}
/>
This example gives you some clue. Pay attention to details: Selector: refs
but element: ref.
From time to time you need two level refs. In this case it would be
text: ReactDOM.findDOMNode(this.refs.yourComponent.refs.yourElement).value
Related
When I type something in my custom field it loses focus and needs to be clicked again in order to add data, Below is the sandbox link to code. It only happens when I pass error and touched props to check for validation, If I remove that it works perfectly.
https://codesandbox.io/s/formik-example-forked-w0bub?file=/index.js
Browser I'm using is Chrom for windows
The input inside the functional component is recreated every time when there is state changes.
Read this blog post to know more the behaviour of nested functional components
To avoid recreation problem, you can follow the Formik example to create other functional component and use it inside your component.
const MyInput = ({ field, form, ...props }) => {
return <input {...field} {...form} {...props} />;
};
...
<Field
id="email"
name="email"
placeholder="Email"
component={MyInput}
/>
Here is the codesandbox for demo.
I have an input field in React, where I set the global name for my application.
I have the handlers set in the parent component called App.js
....
state = {
name: 'app_x',
isProcessedData: false,
isUploadedFiles: false,
isProcessedDBScan: false
}
setNameHandler = (e) => {
this.setState({name: e.target.value});
}
...
in my child component, I refer to the state and the handler via props
export default function Home(props) {
console.log(props)
return (
<div>
<p>Welcome to our application</p>
<TextField
id="outlined-name"
label="Name"
className={"textfiled"}
value={props.name}
onChange={props.handleChange} //the textfield loses focus after each time the input is changed
margin="normal"
variant="outlined"
/>
</div>
)
}
after each keystroke, the text field, is not focused anymore, because the entire UI is rendered.
I tried using React.useState("app_x")
for testing purposes, which worked fine, as long it's inside the component, but I need to pass down the state of the application to other components.
Is there a way to solve this?
I thought about using refs, and then passing a ref down as a callback instead, but as far as the most use cases I have looked at it's mostly relevant inside the same component?
I have created a React View, say MyView, which has 2 text inputs whose initial values will be passed by parent read from a DB.
I also want the changed values to be saved back to DB. So, the view is also passed a callback function for the same.
Consider that DB save operation is heavy and you should not do it very frequently. So, I decided to listen to onBlur events instead of onChange events on the input boxes as onChange is invoked on every key stroke.
First Approach:
class MyView extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<input type="url" value={this.props.values.A}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
<input type="url" value={this.props.values.B}
onBlur={(evt)=>{this.props.saveValue('B', evt.target.value)}} />
<button type="button" onClick={this.props.resetValues}>Reset</button>
</div>
);
}
}
However, this does not work as React enforces a controlled input (with value attribute) always to be accompanied by an onChange listener.
Second Approach:
So, I tried to make these inputs as uncontrolled. That is, instead of value attribute, used defaultValue.
<input type="url" defaultValue={this.props.values.A}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
But this also did not work as on reset/clear button click, although the view was made to re-render but defaultValue does not update once view is created.
Third Approach:
So, I finally added an onChange listener but as no-op.
<input type="url" value={this.props.values.A}
onChange={()=>{console.log('do nothing')}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
Again, this did not work as the view re-renders after calling onChange and since value is not reflected in props yet, value seems to reset back to initial on every key stroke.
Fourth Approach:
Last I tried was to maintain a state in component and read value from state and on every onChange save the value back to state. This worked to most extent but whenever there were external changes to props and the view was re-rendered, state did not update. So, I added a getDerivedStateFromProps function to view:
static getDerivedStateFromProps(props, state) {
return props.values;
}
Now, this again did not work. Reason being that this function is invoked even if I temporarily save values to state and the state was reset to initial values in props.
Can some ReactJS expert help me with my use-case?
You will still need onChange to help you set the states of both url input. onBlur is only used to trigger saving, it's 2 different events for different purposes.
Since your A & B values are passed down from parent component. MyView's parent component should pass down this.state.values and the functions to set the state.
Refer to this snippet if everything is in single component. You should be able move handleChange function up to its parent component.
class App extends React.Component {
state = {
values: {
A: '',
B: ''
}
}
handleChange = e => {
this.setState({
values: {
...this.state.values,
[e.target.name]: e.target.value
})
}
handleBlur = e => {
if (e.target.name === 'A') {
alert(`Saving A: ${this.state.values.A}`)
}
if (e.target.name === 'B') {
alert(`Saving B: ${this.state.values.B}`)
}
}
render() {
return (
<div>
<label>Value A</label>
<input
type="url"
name="A"
value={this.state.values.B}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
<label>Value B</label>
<input
type="url"
name="B"
value={this.state.values.A}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
</div>
EDIT: Your fourth approach should work with the following:
static getDerivedStateFromProps(props, state) {
return { values: props.values }
}
constructor(props) {
super(props)
this.state = {
values: props.values
}
}
so basically the this.state.values is the final source of truth. When user types something, you setState in this component and change it. But if props.values changes (from external source), getDerivedStateFromProps will update the values state.
Going by the comments on Liren Yeo's solution, I would handle the props-state reconciliation on componentDidUpdate, where you get both the old state and props. This way you can determine how this.props was updated and act accordingly. When the value in props does not match state nor oldProps, the update is external and you should override the unsaved changes in the state.
The code should look something like this
componentDidUpdate(prevProps) {
if (this.props.values !== prevProps.values && this.props.values !== this.state.values) {
this.setState({values:this.props.values});
}
}
If you go this route, you can also leave the input uncontrolled and update its value through a reference. This solves some unreliability with controlled inputs, like for example, a type='number' returning undefined as its value when you type a decimal comma. You still need to store the value onChange but only save it onBlur and handling the state-prop-dom reconciliation in componentDidUpdate
So with the idea that onChange works, I would recommend you to take a look at this:
https://schier.co/blog/2014/12/08/wait-for-user-to-stop-typing-using-javascript.html
Navigate to the heading: Wait for Typing to Stop
Hope it can somehow lead you to what you want to achieve.
how to retain component props after emitting back event.
Desc: i have a container it has multiple components (in a same view) in one of component has two fields and one button "continue". After entering some values into those fields , clicking on continue button its going to another component in this component we have two fields and two buttons one is back and continue when i click on back button its going to previous component but not retaining the props which entered on those . can you help me how to retain data.
The simplest solution for you is to have a parent component which holds all the data in its state. Parent container component passes the data to the children components you have as props, also it passes the callbacks to so a child can update the data. Given that you parent is always rendered and thus is never unmounted the data will be always there.
Something like this:
class Parent extends React.Component {
state = { name: 'John' }
render() {
return <div>
<Child name={this.state.name} onNameChange={e => this.setState({name: e.target.value})} />
</div>
}
}
class Child extends React.Component {
render() {
return <div>
<p>The name "{this.props.name}" is saved in the Parent</p>
<input type="text" value={this.props.name} onChange={this.props.onNameChange} />
</div>
}
}
A more complicated solution is to use something like Flux or Redux, but I believe it will be a bit too much for you now.
I have some Controlled Component text inputs for a form:
<input type="text" onChange={(e) => this.props.changeBusiness(e)}/>
I also want to render the (above) text input in a separate component - however! It would be nice if I could render it with the COMPLETED text input to give (in my opinion), what would be a better feel.
How might this be implemented? I assume I could have an onBlur
handler that is triggered.
Is there any way to delay the render() till only after the complete text input?
Danke
Yes if you want to delay a render you just need to return null in the render based on a flag. Lets say you have a state variable inputBlurred that you pass around as a prop to something else.
render() {
if (!this.props.inputBlurred) return null;
... more here
}
You would use it like so.
class ParentComponent extends Component {
constructor() {
super();
this.state = {inputBlurred: false}
}
handleBlur = (e) => {
if(e.target.value){
this.setState({inputBlurred: true});
}
}
render() {
return(
<div>
<input onBlur={this.handleBlur} />
<ChildComponent inputBlurred={this.state.inputBlurred} />
</div>
)
}
}
You have several posibilities.
Create custom Input wrapper around input component, which does trigger "change" event on "complete" change - e.g. on blur. While you typing, parent component won't receive incremental onChange events.
From parent, pass onBlur handler to child input component and as soon, as onBlur handler is triggered, render input's value wherever you wish.