React - change input defaultValue by passing props - javascript

Consider this example:
var Field = React.createClass({
render: function () {
// never renders new value...
return (
<div>
<input type="text" defaultValue={this.props.value || ''} />
</div>
);
}
});
var App = React.createClass({
getInitialState: function () {
return {value: 'Hello!'};
},
changeTo: function (str) {
this.setState({value: str});
},
render: function () {
return (
<div>
<Field value={this.state.value} />
<button onClick={this.changeTo.bind(null, 'Whyyyy?')}>Change to "Whyyyy?"</button>
<button onClick={this.changeTo.bind(null, void 0)}>Change to undefined</button>
</div>
);
}
});
React.render(
<App />,
document.getElementById('app')
);
I want to pass value into defaultValue as prop of dumb input component. However it never re-renders it.

As a previous answer mentioned, defaultValue only gets set on initial load for a form. After that, it won't get "naturally" updated because the intent was only to set an initial default value.
You can get around this if you need to by passing a key to the wrapper component, like on your Field or App component, though in more practical circumstances, it would probably be a form component. A good key would be a unique value for the resource being passed to the form - like the id stored in the database, for example.
In your simplified case, you could do this in your Field render:
<div key={this.props.value}>
<input type="text" defaultValue={this.props.value || ''} />
</div>
In a more complex form case, something like this might get what you want if for example, your onSubmit action submitted to an API but stayed on the same page:
const Form = ({item, onSubmit}) => {
return (
<form onSubmit={onSubmit} key={item.id}>
<label>
First Name
<input type="text" name="firstName" defaultValue={item.firstName} />
</label>
<label>
Last Name
<input type="text" name="lastName" defaultValue={item.lastName} />
</label>
<button>Submit!</button>
</form>
)
}
Form.defaultProps = {
item: {}
}
Form.propTypes = {
item: PropTypes.object,
onSubmit: PropTypes.func.isRequired
}
When using uncontrolled form inputs, we generally don't care about the values until after they are submitted, so that's why it's more ideal to only force a re-render when you really want to update the defaultValues (after submit, not on every change of the individual input).
If you're also editing the same form and fear the API response could come back with different values, you could provide a combined key of something like id plus timestamp.

defaultValue only works for the initial load. After that, it won't get updated. You need to maintain the state for you Field component:
var Field = React.createClass({
//transfer props to state on load
getInitialState: function () {
return {value: this.props.value};
},
//if the parent component updates the prop, force re-render
componentWillReceiveProps: function(nextProps) {
this.setState({value: nextProps.value});
},
//re-render when input changes
_handleChange: function (e){
this.setState({value: e.target.value});
},
render: function () {
// render based on state
return (
<div>
<input type="text" onChange={this._handleChange}
value={this.state.value || ''} />
</div>
);
}
});

I'm fairly certain this has to do with Controlled vs. Uncontrolled inputs.
If I understand correctly, since your <input> is Uncontrolled (doesn't define a value attribute), then the value will always resolve to the value that it is initialized with. In this case Hello!.
In order to overcome this issue, you can add a value attribute and set it during the onChange:
var Field = React.createClass({
render: function () {
// never renders new value...
return (
<div>
<input type="text" defaultValue={this.props.default || ''} value={this.props.value} />
</div>
);
}
});
Here is a plunker showing the change.

You can make the input conditionally and then every time you want to force an update of the defaultValue you just need to unmount the input and then immediately render it again.

The issue is here:
onClick={this.changeTo.bind(null, 'Whyyyy?')}
I'm curious why you bind to null.
You want to bind to 'this', so that changeTo will setState in THIS object.
Try this
<button onClick={this.changeTo.bind(this, 'Whyyyy?')}>Change to "Whyyyy?"</button>
<button onClick={this.changeTo.bind(this, void 0)}>Change to undefined</button>
In Javascript, when a function is called, its called in the scope where it was called from, not where it was written (I know, seems counter intuitive). To ensure it is called in the context you write it, you need to '.bind(this)'.
To learn more about binding and function scope, there are lots of online tutes, (some much better than others) - you might like this one: http://ryanmorr.com/understanding-scope-and-context-in-javascript/
I also recommend using the React Dev tools if you are using firefox or chrome, this way you would have been able to see that state.message was not changing:
https://facebook.github.io/react/blog/2015/09/02/new-react-developer-tools.html

Use conditional rendering, then the component will load correct initial value. Something like in this module:
class MenuHeaderInput extends React.Component{
constructor(props){
super(props);
this.handleBlur = this.handleBlur.bind (this);
}
handleBlur (e) {
this.props.menuHeaderUpdate(e.target.value);
}
render(){
if (this.props.menuHeader) {
return (
<div className="w3-row w3-margin" onClick = {() => this.props.handleTitleClick (10)}>
<div className="w3-third" ><pre></pre></div>
<input
className = {"w3-third w3-input w3-jumbo " + EDIT_COLOR}
type = "text"
defaultValue = {this.props.menuHeader}
onBlur = {this.handleBlur}
/>
<div className="w3-third" ><pre></pre></div>
</div>
)
}
else {
return null;
}
}
}

Related to Sia's excellent answer above: https://stackoverflow.com/a/41962233/4142459.
For my case I had a few ways in which a form could be updated:
users could input values into form fields
An API request allowed users to restore from previous versions
Users could navigate to a filled out form (using queryParams of the URL)
clearing the form fields.
Etc more ways of allowing all the fields or just a single change to happen from user action or websockets.
I found that the easiest way to make sure the state of the form is reflected in its inputs is indeed:
To provide a manually-controlled key prop on the top level of the form or parent element to the form (as long as it is above the inputs in the DOM tree.
When users are typing a key update does not need to happen.
I made the key be a simple formHistoricalVersion and as certain updates external to a user typing/selecting/etc interacting with the form field's values happened I incremented the formHistoricalVersion.
This made sure that the state of the form whether by user action or by API request was in-sync--I had complete control over it.
Other solutions I tried:
While making the API request make the whole form disappear (when loading change to a loading spinner instead of the form). Disadvantage to performance and for clearForm it was a bit crazy to do, but possible with setImmediate to convert the form to a loading spinner when they first clear it, then setting isLoading back to false in the setImmediate.
Adding a key on each input: this worked amazingly, but it had a weird blip whenever users would type so I had to get rid of it.
Putting a static key for the form (field.id) (as suggested by above answer) didn't cover all the use cases I had.
In conclusion, it worked pretty easily to set the key of the form with react/redux, I just would add the equivalent of:
return {
...state,
formFieldState: payload.formFields,
historicalFormVersion: state.historicalFormVersion + 1
}
This was necessary because I was using some 3rd party libraries and my own Numeric Input that took in value as a prop but used value as a defaultValue:
const NumberDisplay: FunctionComponent = ({ value, setValue }) => (
<input
defaultValue={convertToSpecialNumberDisplay(value)}
onBlur={(e) => convertToSpecialNumberDisplay(e.target.value)}
onFocus={(e) => convertToNumberFromDisplay(e.target.value)}
onChange={(e) => setValue(e.target.value)}
/>
)
Approximate Redux of overall Form:
const FullForm: FunctionComponent = () => {
const dispatch = useDispatch();
const formState = useState((state) => state.formState);
const formHistoricalVersion = useState((state) => state.formHistoricalVersion);
return (
<form key={formHistoricalVersion}>
{renderFormFields(formState, dispatch)}
</form>
)
}

I also face this problem, what I did was to manually update the input value when the props has change. Add this to your Field react class:
componentWillReceiveProps(nextProps){
if(nextProps.value != this.props.value) {
document.getElementById(<element_id>).value = nextProps.value
}
}
You just need to add an id attribute to your element so that it can be located.

Related

react-jsonschema-form input box out of focus when ObjectFieldTemplate is used

I have rjsf version ^5.0.0-beta.10 installed in package.json and am able to render a proper Form using react-jsonschema-form. The problem is that I'm using ObjectFieldTemplate and every time I enter a character in one of the string input boxes, the box goes out of focus and I have to click on the box again to be able to type anything.
I have read https://github.com/rjsf-team/react-jsonschema-form/issues/2106, which suggested me to move the ObjectFieldTemplate outside of the custom Form definition. I did that and it does not work. I have also read Custom widget with input loses focus in react-jsonschema-form when formData is passed as a prop to the form, which is an advice about setting state, but I'm using functional components rather than class components, so I'm not sure if it's applicable.
The code looks like:
import validator from "#rjsf/validator-ajv6";
import Form from "#rjsf/mui";
const ObjectFieldTemplate = (props) => {
// some logic to be computed
return (
<div>
<h3>{props.title}</h3>
<p>{props.description}</p>
{props.properties.map(function (field) {
// logic to determine the style
return (<fieldset style={style} key={uuidv4()}>{field.content}</fieldset>);
})}
</div>
);
}
const JsonSchemaForm = (props) => {
// define schema and uiSchema
const onSubmit = ({formData}, e) => {
// some logic
}
const onError = (errors) => {console.log(errors);}
return (<Form
schema={schema}
validator={validator}
formData={data}
uiSchema={uiSchema}
onSubmit={onSubmit}
onError={onError}
templates={{ ObjectFieldTemplate }}
/>);
}
Solved. I'm not sure why, but it appears that setting key={uuidv4()} is an expensive computation step that forces the input box to be out of focus.

I want the input data to go into the title and the textarea data to go into the content

import React from 'react';
export default class CreateNote extend React.component {
constructor(props) {
super(props);
this.state = {note:{title:" ",content:" "} };
console.log(this.state);
}
const inputEvent = (event) => {
const value = event.target.value;
const name = event.target.name;
this.setState({
note:{title: ,content: }
})
}
render(){
return(
<input
type="text"
placeholder="Title"
name="title"
id=""
value={note.title}
onChange={inputEvent}
/>
<textarea
name="contant"
id=""
value={note.contant}
cols=""
rows=""
placeholder="Write a Notes"
onChange={inputEvent}
onClick={expanded}>
</textarea>
)
}
When I write text in the input field and textarea then do not go data to note state. I want the input data to go into the title and the textarea data to go into the content.
Now I will write what on setState? I want to see result in the console.
Tip: Your code is riddled with errors and typos (e.g., CreateNote extend React.component instead of CreateNote extends React.Component , writing const for a function inside a Class Component, contant instead of content). For better chances of getting a help, kindly post the working code that you have, so that duplicating the issue becomes easier for the people looking to help you.
Now on to the solution. If you are a beginner, the best way to get what you want is to make separate functions - one that is triggered when the text inside the input is changed, and the other which is triggered when the text inside textarea is changed.
Note that you have kept title and content inside a state object note. This means in order to change title or content, you have to update the entire note object. Be careful if you update just one key of note without persisting the other one (e.g., if you update title only and want to leave content unaffected), you should use the spread operator ... which helps to clone the object's values, so then after that you can update the value of the key you want.
titleChange = (e) => {
this.setState({
note: {
...this.state.note,
title: e.target.value
}
});
};
contentChange = (e) => {
this.setState({
note: {
...this.state.note,
content: e.target.value
}
});
};
Update, a cleaner approach: We can also use the "name" attribute of the input and textarea to our benefit and combine the two functions into one (I've named it inputChange )
inputChange = (e) => {
this.setState({
note: {
...this.state.note,
[e.target.name]: e.target.value
}
});
};
You can find a working CodeSandBox here. Note that using spread operator will not be necessary if you move out the required fields title and content out from notes and make them state variables directly. Check out the AppWithoutNote.js file to see how it can be implemented.

State not updating inside of render

I'm trying to bind elements using setAttribute. It works, except it will not allow me to change the value.
Basically I want to pass a value from state as the value in the input.
Currently, the state does NOT update inside the render. It only takes the initial state. In the render, my 'console.log' only fires once.
The correct this.state.answer does appear in componentDidUpdate (and did Mount).
I have put this on JSFiddle https://jsfiddle.net/69z2wepo/91132/
class Hello extends React.Component {
cf = null
state = {answer:''}
componentDidMount(){
this.refs.q1.setAttribute('cf-questions', "How are you?")
this.cf = window.cf.ConversationalForm.startTheConversation({
formEl: this.refs.form,
context: document.getElementById("cf-context"), // <-- bind this to an element instead of html body
flowStepCallback: (dto, success, error) => {
// dto.text contains the value being passed to the form
// State appears in console.log
// dto.text = 'blah' + this.state.answer
// above ONLY passes 'blah'
success()
},
});
}
componentDidUpdate(props) {
this.refs.q1.setAttribute("value", this.state.answer);
}
onChange = e => {
this.setState({ answer: 'X' });
}
render() {
console.log('a change', this.state.answer)
// Only fires once
return (
<div>
<button onClick={this.onChange} className='but'> onChng </button>
<div id="cf-context" >
<form id="form" className="form" ref="form">
<select ref="q1" type="radio" id="links">
<option value="X">X</option>
<option value="Y">Y</option>
</select>
</form>
</div>
</div>
);
}
}
Most probably the issue is within the componentDidMount() method.
I am not sure about what you have been doing. But i guess you are setting the scope of the form Element within those particular div "cf-context".
So even after the state is updated, it takes value 'this' from particular scope which has been set. Try moving the scope even further up towards the top of DOM
I am not sure this is right. Please try

Dispatching and listeing for events in different components - reactjs

I am trying to create a custom event in one component and add an event listener in another component. The component that is listening for the event contains a function that I want to execute on the event. Below are what I have in the two components, I just feel like I'm going about this in the wrong way...
Component #1
toggleWidget() {
const event = new CustomEvent('sliderClicked', {
bubbles: true,
});
const sliderToggle = document.getElementById('input');
sliderToggle.dispatchEvent(event);
this.setState({
checked: !this.state.checked,
});
}
/* and then in my render... */
render() {
const displaySlider = this.state.isSliderDisplayed ? (
<div className="slider-container" >
<label className="switch" htmlFor="input">
<input type="checkbox" checked={this.state.checked} onChange={this.toggleWidget} id="input" />
<span className="slider round" />
</label>
<p className="batch-slider-title"> Batch Widget </p>
</div>) : null;`
Component Two
window.addEventListener('sliderClicked', this.refreshLayout);`
Any ideas as to what I may be doing wrong?
Basically it should work, but in react - if you rendered an element in a component you can use the ref to access it:
<input
type="checkbox"
checked={this.state.checked}
onChange={this.toggleWidget}
id="input"
ref={(c) => this.input = c}
/>
And your toggleWidget function should be something like this:
toggleWidget() {
...
this.input.dispatchEvent(event);
...
}
In React it's pretty common to pass down callbacks from parent to child.
const Child = ({handleClick}) => (
<div onClick={ handleClick } >Click me!</div>
);
const Parent = () => {
const childClickHandler = event => {
// do stuff
alert('My child is calling?');
}
return (
<Child handleClick={ childClickHandler }/>
);
};
Maybe that could work for you? You can try the code here. (JSFiddle)
Refs are generally considered something to avoid in React as they couple components together. see the documentation here:
https://facebook.github.io/react/docs/refs-and-the-dom.html
Your first inclination may be to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to "own" that state is at a higher level in the hierarchy. See the Lifting State Up guide for examples of this.
Try using a global state container like redux and when you "toggleWidget" in one component, set a property in your redux store. Listen to that property by setting it as a prop in your second component(the one that you want to respond to a change/toggle). On change of that property your component will have the "componentWillReceiveProps" lifecycle method called and you can then have your "responding" component take whatever action you like.

Why isn't my input value updating with React?

I have the following code in my component. It will get called when I update certain things, thereby replacing a bunch of things in the UI. Everything is updating EXCEPT the value of the input as seen by the user.
let input = {
id: 'discount-' + geo + '-' + segment,
value: percentage,
disabled: applyToAll,
placeholder: '0.00'
};
cells.push(
<td key={'cell-' + geo + '-' + segment} className="discount-segment cfix">
<Text {...input} />
</td>
);
This is what <Text> returns, with things removed for clarity
return (
<div className={containerClasses.join(' ')} id={'field-container-' + id}>
{label}
<input
autoComplete="off"
type="text"
id={id}
ref="input"
{...extraProps}
name={id}
className={classes.join(' ')}
defaultValue={this.props.value}
onChange={this.props.onChange}
/>
</div>
);
Everything renders fine. Let's say the percentage value is 5 on start, it will show 5 in the field. I then do something else that updates percentage to 50. (A console log will show the right number on re-render). However the value is only showing 5 in the UI still. I am using defaultValue on the input field, but I figure that should be changing as the whole thing re-renders from parent.
Edit
Updated <Text> to set value instead of defaultValue. However then I need to use state to update the value when user types. Then when I re-render, I'm sending in new props with proper value, but of course props isn't updated. Catch-22 for me.
You need to perform a couple of steps:
Your input needs to only use the value and onChange props, do not use defaultValue
Initialize your state using your props to set your default value
Update your state when your props change
So, for example:
const MyComponent = React.createClass({
propTypes: {
defaultInputValue: React.PropTypes.string
},
getInitialState() {
return {
inputValue: this.props.defaultInputValue
};
},
componentWillReceiveProps(nextProps) {
if (nextProps.defaultInputValue !== this.props.inputValue) {
//Forcibly overwrite input value to new default if the default ever changes
this.setState({inputValue: nextProps.defaultInputValue});
}
},
render() {
return <input type="text"
value={this.state.inputValue}
onChange={e => this.setState({inputValue: e.target.value})} />;
}
});
In general initializing state off of props is a no-no. I would probably cringe a little bit if I saw this come across in a code review as there is probably some behavior that can be simplified.
You can also do:
<input value={this.state.inputValue || this.props.defaultInputValue} />
Such that the value of the input reverts to the prop value if you ever clear out the input. In this case you wouldn't have to forcibly overwrite the state with the new props.
componentWillReceiveProps is deprecated now, and even its successor getDerivedStateFromProps is recommended against. To square the circle of "I need to manage the state of a text input (state), but I also need it to update its initial value when I re-render (props)", the React blog recommended using a key in the props. When the key changes, the component remounts and props can initialize state anew.
To aggressively force the component to refresh whenever anything about the input changes, I would use
<Text {...input} key={JSON.stringify(input)}/>
Then you can use value={this.state.value} like a normal controlled form, but you can set this.state.value = props.value and it will change every time.
We can do it as this.We Must have to use onChnage() Event.
<input placeholder="as_you_want"
value={this.state.as_your_state}
onChange={e => {
this.setState({ as_your_state: e.target.value });
this.value = this.state.as_your_state;
}}
/>
Hope This Works.

Categories

Resources