I would like to show and focus an input field at the same time. I am looking for advice on the most elegant way to do this.
The simplistic approach, which doesn't work, would be:
* The element that contains the input field makes some state change which unhides the input box by setting display = ''. I got this part to work.
* The element that contains the input field gets the child element, via a ref, and calls .focus() on its DOM element. This is the part that doesn't work.
I think this is because the style change (display = '') has not propagated to the real DOM, so the focus() doesn't work.
I can think of ways to fix this (the input field could have a state var isFocusing, which calls focus() only after rendering has taken place), but I would to hear if there's a more elegant way of achieving this.
Thanks!
componentDidUpdate(prevProps, prevState) is called immediately after updates are flushed to the DOM and it can be used to focus the input box at the right time.
componentDidUpdate(prevProps) {
if(!prevProps.show && this.props.show){
// We transitioned from hidden to shown. Focus the text box.
this.refs.myInput.getDOMNode().focus();
}
},
render(){
return (
<form>
<input className={this.props.show ? 'input-shown' : 'input-hidden} />
</form>
);
}
There is more info in the docs here: https://facebook.github.io/react/docs/more-about-refs.html
Use a parent child setup with your parent sending a hide prop. In the child render if (this.props.hide) return null; or return the input. Also in the child use componentWillReceiveProps(nextProps) and set focus there.
Related
My code is as follows, and you can see how it works in https://codepen.io/rongeegee/pen/BaVJjGO:
const { useState } = React;
const Counter = () => {
const [data, setData] = useState({
displayData: "data_one",
data_one: {
text: ""
},
data_two:{
text:""
}
})
function handleOnChange(event){
event.preventDefault();
const new_data = {...data};
if (event.target.name == "displayData"){
new_data.displayData = event.target.value;
setData(new_data);
}
else{
new_data[event.target.name]["text"] = event.target.value;]
setData(new_data);
}
}
return (
<div>
<form onChange={handleOnChange}>
<select name="displayData" value={data.displayData}>
<option value="data_one">data_one</option>
<option value="data_two">data_two</option>
</select>
<br/>
{
data.displayData == "data_one"
?
<>data One: <input name="data_one" defaultValue={data.data_one.text} /></>
:
<>data two: <input name="data_two" defaultValue={data.data_two.text} /></>
}
</form>
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById('app'))
If I type something in the input of data_one, toggle between the values between "data_one" and "data_two", the data_two input field will have the same value inside. If I change the value in data_one toggle the dropdown to "data_one", data_one will have the same value again.
This shouldn't happen since data_one input uses the value of the text field in data_one field in the data state while data_two input uses the one in data_two field. One should not take the value from another field in the state.
React has a way to determine if/which elements/components have changed and which haven't. This is neccesary, because DOM manipulation is expensive, so we should try to limit it as much as possible. That's why React has a way to determine what changed between rerenders and what didn't; and only changes what changed in the DOM.
So if we in your case swith from dataOne to dataTwo, React goes something like: "Oh nice input element stays input element. Nice I don't have to completely destroy that DOM node and render it rom scratch, I can just check what changed and change that. Let's see: The name has changed ... so let's change that, but apart from that everything stayed the same." (This means your input element won't get destroyed and the other one initialy rendered, but the one input element get's a name change and React calls it a day - and since default Value only works on initial creation of an element/DOM node, it won't be shown)
The way React rerenders stuff and compares/modifies the DOM is quite complicated. For futher information I can recomend the following video: https://youtu.be/i793Qm6kv3U (It really helped me understand the whole React Render process way better).
A possible fix to your problem, would be to give each input element a key. So your input elements could look something like:
<input key="1" name="data_one" defaultValue={data.data_one.text} />
<input key="2" name="data_two" defaultValue={data.data_two.text} />
So yeah, the fix is fairly easy; understanding the reason to why we need this fix however is everything but easy.
regarding your comment (my answer would be to long for a comment, and formatting is nicer in an answer)
nope you aren't changing state on input-field change ... you are changing/manipulating the state variable data ... but you are not updating your state. Hence no rerender gets triggered, and in case of a rerender triggered by something else the data will be lost. So you aren't actually changing state.
Changes to your state can only be made by calling the callback Funcnction provided by the useState-Hook. (In your case the callback provided by the useState-Hook is setData. In your else statement you are not calling the provided callback, just manipulating the data object (since you shallow clone and therefor manipulate the original data object)
When changing state ALWAYS use the provided callback const [stateVariable, thisIsTheCallback] = useState(initVal). If you don't use this callback and just manipulate the stateVariable you will sooner or later run into problems (and debugging that issue is particularly tedious, since it seems like you are changing state (because the stateVariable changes) but you aren't changing state since only the callback can do this)
In place of defaultValue attribute to your <input/> replace that with value attribute and everything will work.
And to know why defaultValue won't get updated after state change, read the first answer by #Lord-JulianXLII
I want to change the existing value in a hidden text input box (automatically when the box is closed) to an empty string ('') when the user chooses one of the drop down menu selections. The problem is that I can close the component with the right menu selection, but the value does not change to an empty string until I click the button a second time. Then the value becomes an empty string and I get the correct information.
I originally based the approach to that of form data being passed to/from the parent and that does not seem to work for this component. I tried using a setState() function, however, this either didn't take or I did not implement correctly. All state has been set and all other components move data around as they're supposed to.
This is the parent component that sends/receives the information from the . The "cost={dailyTransportationCost}" is supposed to send the new value to the child.
<DailyTransportationCost
cost={dailyTransportationCost}
handleTransportationCost={e => setDailyTransportationCost(e.target.value)}
/>
This is the component that needs to change to an empty string when it's closed based on the menu option (separate component)
const DailyTransportationCost = ({ cost, handleTransportationCost }) => {
return (
<div className={`${styles.containerTransportationCost}`}>
<label className={`${styles.containerLabel}`} htmlFor='dailyTransportationCost'>Daily Cost</label>
<input className={`${styles.containerInput}`}
placeholder='0.00'
id='dailyTransportationCost'
type='number'
value={cost.dailyTransportationCost}
onChange={handleTransportationCost}
/>
</div>
);
};
Thank you for your help. I've been banging around on this for a couple of days. Any suggestions will be appreciated.
me again...
I figured it out. It was a simple useEffect() and everything worked remarkably well. Just want to thank anyone who might have stopped by.
Cheers!
I'm new ot react native and am having a hard time with the idea of not using inheritance, but rather composition.
My scenario: I'm creating a component (focusedTextInput) which shows one InputText. My component simply adds functionality to change the style of the TextInput on focus.
I'm using FocusedTextInput in another component which contains five focusedTextInput and I configure those to only have one character at a time and to auto-skip to the next FocusedTextInput when the character is entered (using the focus() method).
Where I'm running into issues is the my FocusedTextInput does not have a focus method (and I don't want to expose the TextInput).
So do I need to surface all the method that might be used from TextInput on FocusedTextInput or is there a better way?
See this answer React set focus on input after render on stack overflow.
I think this applies to react-native but it does work in the web.
That shows you how to set a ref to a component. And in the CDM set the focus to that component.
To extend how that works so you can set the focus to a specific input (if there are many) is to add a prop called setFocused
Change the CDM to
// Set the focus on first render.
componentDidMount(){
if (this.props.setFocus) {
this.nameInput.focus();
}
}
// focus if the prop changes
componentWillRecieveProps(nextProps) {
if (this.nextProps.setFocus) {
this.nameInput.focus();
}
}
I have an input field with a submit button and when someone enters something that text appears on the screen. how can I test that in react?
I know how to simulate the button click but what would the expectation be? or how would I actually be able to tell if the text is there?
You would need to use refs to check the values of input fields using React. The refs provide the hooking interface between virtual DOM element of input fields to access it values.
You scry for that input element by id or type or class. And you can access the text of that element by looking at .value
I'm not sure I understand the question... the first part of your question makes me think you want to detect when a user is typing some text in an input field, but the second part makes me think you want to automate a test, placing some text in the input and simulating click on a button, kinda confusing ...
In my last project I used that package instead of a classic input: react-contenteditable
You can hook your code to events that this component is firing:
render() {
const { selectedDbItem } = this.props
return (
<div>
<ContentEditable
onBlur={(e)=>this.onBlurField(e, 'supplier')}
className="field-value"
html={selectedDbItem ? selectedDbItem.supplier : ''}
disabled={false}
onChange={(e)=>this.onChangeField(e, 'supplier')}
onKeyDown={(e) => this.onKeyDown(e)}
/>
</div>
You can then implement the callbacks onBlur, onChange, onKeyDown (for input filtering for example ...) in the containing component.
Hope that helps
So I made a component for including content-editable components in my app. I copied it from some gist I believe, then edited to what i needed.
The code is below. When I edit it, it triggers updates on the parent just fine, but when I attempt to set props.html in the parent, it doesn't reflect in the UI.
FURTHER, the console.log shows that this.props.html is equal to '' a blank string, yet the UI doesn't update, and maintains the text that was originally in there.
I don't understand how this is possible... dangerouslySetInnerHtml = {__html: ''} should make it so the UI reflects an empty string... it feels like it should be impossible for it to show the old text.
var React = require('react');
var ContentEditable = React.createClass({
render: function(){
//TODO: find where html=undefined and fix it! So I can remove this? Maybe I should keep this safety.
var html = this.props.html || '';
console.log('content editable render, html: ', this.props.html);
return <div id="contenteditable"
onInput={this.emitChange}
onBlur={this.emitChange}
contentEditable
dangerouslySetInnerHTML={{__html: html}}></div>;
},
shouldComponentUpdate: function(nextProps){
return nextProps.html !== this.getDOMNode().innerHTML;
},
emitChange: function(){
var html = this.getDOMNode().innerHTML;
if (this.props.onChange && html !== this.lastHtml) {
this.props.onChange({
target: {
value: html
}
});
}
this.lastHtml = html;
}
});
module.exports = ContentEditable;
(A little background, I'm trying to clear my input after submitting it to be saved. The clearing isn't working, hence this question.)
I got a very similar problem using contentEditable and shouldComponentUpdate, it looks like there is a bug when resetting the innerHTML to the same previous value using dangerouslySetInnerHTML function (or prop) (I think it does not work even if you insert the code without using it) ... I suspect (this is just an idea) that React compares the last value set through dangerouslySetInnerHTML with the new one you are trying to send and decides not to update the component because it is the same (even if the real innerHtml has changed due to user interactions, because those interactions does not trigger any state or props update on React).
Solution: The easiest solution I found was to use a different key each time I needed it to re-render. for example using key={Date()}.
Example: Here you can find your code (I changed some of it to make it work), when you type '?' into the div, the text inside the ContentEditable component should become an empty string (i.e. ''), it works only once, the second time you type '?' won't work because the innerHTML for react will be the same to the one you're setting (i.e. an empty string so it won't update the component).
And here, I added the key={Date()} (this is the easiest way to show you that this work, but it is not the best way to set a unique key each time it re-render) to the editable component, now you can type any number of '?' and it will work.
I found another solution that is probably better than generating random keys. Putting a key specifically on the div that calls #dangerouslySetInnerHtml, and not just on the component itself
<div class='wrapper'>
<div key={this.props.thing.id} dangerouslySetInnerHtml={this.props.thing.content} />
</div>
This is not the case here but make sure you always dangerouslyset html on div tag and never on span, p ... because if span element child is div, there will be a problem.
Solved rerender bug to me
My (very simple) React (version 16) app: It has a contentEditable <div>.
It successfully re-renders this <div> upon a progression of submit button clicks. Instead of dangerouslySetInnerHtml, I used ref={el => this.myRefElem = el} with componentWillUpdate(nextProps) { this.myRefElem.innerHTML = nextProps.myInputText; } For me, nextProps was important for the proper value to re-render. See my app's project files, to see the rest of the required code.
CLICK-HERE to see my React app. It has a button to download its (development mode) project files. It (basically) only has an index.js file. - - - This app was initiated by mlbrgl, who asked me for an alternative technique.
I ran into the same issue (React 16) and used an approach suggested by MLR which consists in dropping dangerouslySetInnerHTML and using componentDidMount() instead for the initial render and componentDidUpdate() for any subsequent renders.
Solution here, adapted to React 16: https://codepen.io/mlbrgl/pen/PQdLgb
These hooks would perform the same update, directly updating innerHTML from props:
componentDidMount() {
this.updateInnerHTMLFromProps();
}
componentDidUpdate() {
this.updateInnerHTMLFromProps();
}
updateInnerHTMLFromProps() {
this.refEl.innerHTML = this.props.html;
}
This makes it clearer (for me at least) to see what is really going on, without having the false expectation that dangerouslySetInnerHTML would keep the DOM in sync in all circumstances, as suggested by Mike Woodcock here https://stackoverflow.com/a/38548616/9408759.
For a complete view of the problem and both solutions outlined here, please check https://codepen.io/mlbrgl/pen/QQVMRP.
Adding key property for an element with dangerouslySetInnerHTML did resolve my issue.
As a key I used
key={new Date().getTime()} // timestamp