I'm trying to focus() a conditionally rendered textarea in React. The code below is almost exactly identical to the example in the React docs or to this similar question.
The code below immediately shows and focuses the textarea. If the three commented lines are uncommented, the textarea gets shown after the condition prop is set to true (its value depends on the state of the parent and is initially false), but the element doesn't get focused anymore.
If the condition is initially true, the input element gets focused as expected when the component renders for the first time. The problem occurs when the condition is changed from false to true.
import React, { Component } from 'react'
class TestClass extends Component {
constructor(props) {
super(props)
this.focus = this.focus.bind(this)
}
componentDidMount() {
// this.props.condition &&
this.focus()
}
componentDidUpdate() {
// this.props.condition &&
this.focus()
}
focus() {
console.log(`this.textInput: ${this.textInput}`)
this.textInput.focus()
}
render() {
return (
<div>
{
// this.props.condition &&
<textarea
ref={(input) => {this.textInput = input}}
defaultValue="Thanks in advance for your invaluable advice">
{console.log('textarea rendered')}
</textarea>
}
</div>
)
}
}
The console output
textarea rendered
this.textInput: [object HTMLTextAreaElement]
rules out that the element is unavailable at the time focus() gets executed.
Furthermore:
Setting autoFocus attribute doesn't seem to work, in contrast to this question
Same problem for both <input /> and <textarea />
Edit: in response to the question below, the parent component looks as follows.
class ParentComponent extends Component {
constructor(props) {
super(props)
this.state = {
condition: false
}
this.toggleCondition = this.toggleCondition.bind(this)
}
toggleCondition() {
this.setState(prevState => ({
condition: !prevState.condition
}))
}
render() {
return (
<div>
<TestClass condition={this.state.condition} />
<button onMouseDown={this.toggleCondition} />
</div>
)
}
}
import React, { Component } from 'react';
class TestClass extends Component {
constructor(props) {
super(props);
this.focus = this.focus.bind(this);
}
componentDidMount() {
this.focus();
}
componentWillReceiveProps(nextProps) {
if (nextProps.condition !== this.props.condition) {
this.focus();
}
}
focus() {
console.log(`this.textInput: ${this.textInput}`);
if (this.props.condition === true) {
this.textInput.focus();
}
}
render() {
return (
<div>
{
this.props.condition &&
<textarea
ref={(input) => { this.textInput = input; }}
defaultValue="Thanks in advance for your invaluable advice"
>
{console.log('textarea rendered')}
</textarea>
}
</div>
);
}
}
export default TestClass;
Turns out the problem was really really stupid. The way I implemented the toggle button (which I simplified to <button onClick={this.toggleCondition} /> in the original question "for clarity"), was with a custom component which takes the onClick prop and attaches its value to the onMouseDown attribute of a hyperlink.
Because the hyperlink gets focused after its onMouseDown action, the focus was immediately taken away from the textarea.
I've edited the question to reflect my usage of onMouseDown.
Related
I have a parent component:
import React, { Component, Fragment } from "react";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
SignUpClicked: null
};
this.openSignUp = this.openSignUp.bind(this)
}
openSignUp() {
this.setState({
SignUpClicked: true
})
console.log('signup clicked')
}
render() {
return (
<div>
<SignUp
authState={this.props.authState}
onStateChange={this.props.onStateChange}
openSignUp = {this.openSignUp} />
<SignIn
authState={this.props.authState}
onStateChange={this.props.onStateChange} />
</div>)
}
}
export default Parent;
and then two child components SignUp & SignIn in different files.
In each of them there's a link like <p>Sign Up Instead?<a href="#" onClick = {this.props.openSignUp}> Sign Up </a></p> and the other way for Sign In.
However, I can't get them to switch between the two components - what am I doing wrong here?
You can easily control which component should be rendered by putting condition with them. If this.props.authState is true, show SignUp component else show SignIn component
<div>
{!this.props.authState && (<SignUp
authState={this.props.authState}
onStateChange={this.props.onStateChange}
openSignUp = {this.openSignUp} />) }
{this.props.authState && (<SignIn
authState={this.props.authState}
onStateChange={this.props.onStateChange} />)}
</div>
There is a concept called conditional rendering you can use that can solve your problem. Simply put in conditional rendering you display a component only when a condition is met. In your case, you can try the following.
import React, { Component, Fragment } from "react";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
SignUpClicked: false
};
}
//changed this to arrow function that binds "this" automatically
toggleSignUp = () => {
this.setState({
SignUpClicked: !this.state.SignUpClicked
})
console.log('signup clicked')
}
render() {
return (
<div>
{ this.state.SignUpClicked &&
<SignUp
authState={this.props.authState}
onStateChange={this.props.onStateChange}
openSignIn = {this.toggleSignUp} />
}
{!this.state.SignUpClicked &&
<SignIn
authState={this.props.authState}
onStateChange={this.props.onStateChange}
openSignUp = {this.toggleSignUp} />
/>
}
</div>
)}
}
export default Parent;
NOTE: Pay attention to the changes I have done
Changed the function to arrow function by using them you don't have to bind this in constructor. It automatically does that for you.
I have changed the name of function openSignUp to toggleSignUp because we will use a single function to display signup component and than hide it if we want. (because I assume you will implement "sign in instead" in <SignUp/> component to get back to sign in
I have passed the same toggleSignUp function reference to both the components so that you can show or hide either of them.
Do it this way
import React, { Component, Fragment } from "react";
class Parent extends Component {
constructor(props) {
super(props);
// Set state of the component
this.state = {
// Show sign in page by default
show: "signin"
};
}
showSignup = () => {
// Show sign up page by changing the show variable
this.setState({
show: "signup"
});
console.log('Showing Signup Page');
}
showSignin = () => {
// Show sign in page by changing the show variable
this.setState({
show: "signin"
});
console.log('Showing Signin Page');
}
render() {
// Render the component as per show state variable
if(this.state.show === "signin") {
return <SignIn
authState={this.props.authState}
onStateChange={this.props.onStateChange}
onSignup={this.showSignup}
/>
}
else {
return <SignUp
authState={this.props.authState}
onSignup={this.showSignin}
/>
}
}
export default Parent;
So basically, export onClick event from both the child components and set show variable of state in parent component. Then depending upon the state, return only the component you want.
Please let me know if there is any question or confusion. Would love to answer.
Question: I want to press the "Manipulator" class button to change the ChangeValue's value variable to Manipulator's manipulatorName value, which isn't occurring. What have I done wrong?
I have a class (called ChangeValue) that initialises with an empty string name. I'm doing this since it will be displayed on the website as "Empty" for now. However, when I click a another class (called Manipulator) it should change ChangeValue, which isn't occurring. The ChangeValue's value is always set to "Empty", despite when I click the button.
My code:
export class ChangeValue extends React.Component {
constructor(props) {
super(props)
this.state = {
value: " "
};
}
render() {
var currentValue = null;
if (this.value == null) {
currentValue = "Empty"
} else {
currentValue = this.value
}
return (
currentValue
)
}
}
export class Manipulator extends React.Component {
constructor(props) {
super(props)
this.state = {
manipulatorName: "New Value"
};
this.handleClick = this.handleClick.bind(this);
}
render() {
return (
<button onClick = {this.handleClick}>
<ChangeValue value = {this.state.manipulatorName} />
</button>
)
}
}
I based the "ChangeValue value" line based off what I was reading from Stack and it may be there is an issue with Parent/Children, too?
There are a few things going on here as to why it's not working.
You can clean up your ChangeValue component, as it doesn't actually use any state. It only needs to use the value of the prop passed to it from the Manipulator, so therefore can be converted into a stateless 'dumb' function based component.
function ChangeValue(props) {
return props.value || "Empty"
}
Or if you still want it as a class so you can add some state in later...
export class ChangeValue extends React.Component {
render() {
return this.props.value || "Empty"
}
}
These will both have the same output. They will return the value of props.value if it is truthy, OR (||) return the word "Empty" if props.value is falsy.
The Manipulator class needs a little bit of work also. It currently sets up a handleClick method but doesn't define it. It should probably look something like this...
export class Manipulator extends React.Component {
constructor(props) {
super(props);
this.state = {
manipulatorName: undefined
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
manipulatorName: "New Value After Click"
});
}
render() {
return (
<button onClick={this.handleClick}>
<ChangeValue value={this.state.manipulatorName} />
</button>
);
}
}
This will initially render a button with the text "Empty" as the manipulatorName is *falsy*. Upon clicking the button, the button text should then change to say "New Value After Click".
I hope this helps, if it doesn't quite fit with what you're trying to achieve, please comment or update the question with some further details.
UPDATE
Here is a working example on CodeSandbox.
Background
I am trying to make an element disappear after the animation ends (I am using animate.css to create the animations).
The above 'copied' text uses animated fadeOut upon clicking the 'Copy to Journal Link'. Additionally, the above demo shows that it takes two clicks on the link to toggle the span containing the text 'copied' from displayed to not displayed.
According to the animate.css docs, one can also detect when an animation ends using:
const element = document.querySelector('.my-element')
element.classList.add('animated', 'bounceOutLeft')
element.addEventListener('animationend', function() { doSomething() })
My Problem
However, within the componentDidMount() tooltip is null when attempting to integrate what animate.css docs suggest.
What am I doing wrong? Is there a better way to handle this behavior?
ClipboardBtn.js
import React, { Component } from 'react'
import CopyToClipboard from 'react-copy-to-clipboard'
class ClipboardBtn extends Component {
constructor(props) {
super(props)
this.state = {
copied: false,
isShown: true,
}
}
componentDidMount() {
const tooltip = document.querySelector('#clipboard-tooltip')
tooltip.addEventListener('animationend', this.handleAnimationEnd)
}
handleAnimationEnd() {
this.setState({
isShown: false,
})
}
render() {
const { isShown, copied } = this.state
const { title, value } = this.props
return (
<span>
<CopyToClipboard onCopy={() => this.setState({ copied: !copied })} text={value}>
<span className="clipboard-btn">{title}</span>
</CopyToClipboard>
{this.state.copied ? (
<span
id="clipboard-tooltip"
className="animated fadeOut"
style={{
display: isShown ? 'inline' : 'none',
marginLeft: 15,
color: '#e0dbda',
}}
>
Copied!
</span>
) : null}
</span>
)
}
}
export default ClipboardBtn
Using query selectors in React is a big NO. You should NEVER do it. (not that that's the problem in this case)
But even though it's not the problem, it will fix your problem:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
https://reactjs.org/docs/refs-and-the-dom.html
componentDidMount gets called only once during the inital mount. I can see that in the inital component state, copied is false, hence #clipboard-tooltip never gets rendered. That is why tooltip is null.
Instead try this :
componentDidUpdate(prevProps, prevState) {
if(this.state.copied === true && prevState.copied === false) {
const tooltip = document.querySelector('#clipboard-tooltip')
tooltip.addEventListener('animationend', this.handleAnimationEnd)
}
if(this.state.copied === false && prevState.copied === true) {
const tooltip = document.querySelector('#clipboard-tooltip')
tooltip.removeEventListener('animationend', this.handleAnimationEnd)
}
}
componentDidUpdate gets called for every prop/state change and hence as soon as copied is set to true, the event handler is set inside componentDidUpdate. I have added a condition based on your requirement, so that it doesn't get executed everytime. Feel free to tweak it as needed.
I am using the react-speech-recognition package to do speech-to-text in my app.
Inside render of app.js:
<ChatContainer
micActive={this.state.micActive}
sendData={this.sendData}
makeInactive={this.makeInactive}
>
<SpeechToText>
sendData={this.sendData}
makeInactive={this.makeInactive}
micActive={this.state.micActive}
</SpeechToText>
<div>
<button
id="micInactive"
type="button"
onClick={this.makeActive}
/>
</div>
</ChatContainer>
As you can see above, my ChatContainer has two Children :
SpeechToText
div that contains a button
SpeechToText.js :
class SpeechToText extends Component {
componentWillReceiveProps(nextProps) {
if (nextProps.finalTranscript && nextProps.micActive) {
this.props.sendData(nextProps.finalTranscript);
this.props.resetTranscript();
this.props.makeInactive();
}
}
render() {
return (
<div>
<button
id="micActive"
type="button"
/>
</div>
);
}
}
export default SpeechRecognition(SpeechToText);
SpeechToText receives the speech recognition props from Speech Recognition
ChatContainer.js
const ChatContainer = props => (
<div>
{
React.Children.map(props.children, (child, i) => {
if (i === 0 && child.props.active) {
return React.cloneElement(child, {
sendData: props.sendData,
makeInactive: props.makeInactive,
micActive: props.micActive,
});
}
if (i === 1 && child.props.inactive) {
return child;
}
return null;
})
}
</div>
);
export default connect()(ChatContainer);
Finally ChatContainer decides which child to render. If the state is inactive render the div with the inactive button.
EDIT
By default the state in inactive -- this.state.micActive: false. If the state is inactive I render the <div> with the button. If that button is clicked the makeActive method gets called and makes the state active -- if the state is active I render <SpeechToText>. Once I complete the voice-to-text I call makeInactive -- that makes the state inactive and the <div> is rendered once again
The first time I click the button SpeechToText gets rendered and voice-to-text works.
However the second time I click the button -- And I try to rerender the SpeechToText component I get an error:
setstate can only update a mounted or mounting component
Sometimes the error does not appear but the voice-to-text does not work.
Why is this happening - Do I need to force remove the component perhaps?
Turns out it was an issue with the SpeechRecognitionContainer.
The package was updated with new props and configuration options and I resolved my issue.
You can read more about react-speech-recognition here.
Now simply I can render the component like so:
render() {
return (
<SpeechToText
sendSpeechToText={this.sendSpeechToText}
/>
);
}
and SpeechToText looks something likes this:
class SpeechToText extends Component {
constructor(props) {
super(props);
this.reactivate = this.reactivate.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.finalTranscript && nextProps.micActive) {
this.props.sendSpeechToText(nextProps.finalTranscript);
this.props.resetTranscript();
this.props.stopListening();
}
}
componentWillUnmount() {
if (this.props.listening) {
this.props.abortListening();
}
}
reactivate() {
if (!this.props.listening) {
this.props.startListening();
}
render() {
return (
<div>
<button
id="micButton"
type="button"
onClick={this.reactivate}
/>
</div>
);
}
}
const options = {
autoStart: false
}
export default SpeechRecognition(options)(SpeechToText)
I'm new to React and I'm trying to pass the HTML element from an onClick event, but I'm not getting the expected result.
Here is the code:
import React, { Component } from 'react';
export class Header extends Component{
isScrolledIntoView (e){
console.log('html element is ',e)
}
render(){
return(
<div>
<button onClick={this.isScrolledIntoView.()}>Click me</button>
</div>
);
}
}
The desired output would be to get the button's HTML element in the console.
You need to capture the target of the e (event) instead of the event itself, like this:
import React, { Component } from 'react';
export class Header extends Component {
isScrolledIntoView (e) {
console.log('html element is ', e.target)
}
render() {
return (
<div>
<button onClick={this.isScrolledIntoView.bind(this)}>Click me</button>
</div>
);
}
}
Here is a demo link: https://codesandbox.io/s/y8xXqopM7
Hope it helps!
The method isScrolledIntoView() is bound to the class, not the component instance, so when you refer to this.isScrolledIntoView() in your render method it will return undefined. Regular React lifecycle methods are bound to the component instance, but for your own custom methods you need to do a little work, you can put it in the constructor:
constructor(props) {
this.isScrolledIntoView = this.isScrolledIntoView.bind(this);
}
Or you can use class properties to auto-bind the method:
isScrolledIntoView = (e) => {
// do stuff
}
2 things you need to change in your code.
1- You have to bind your isScrolledIntoView, and it could be inside your constructor, or doin' this => <button onClick={this.isScrolledIntoView.bind(this)}>Click me</button>
2- You should target your e event instead of only log e you should
=> console.log('html element is ', e.target)
Nice reading for novices in react
Passing current element using ref
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return (
<button className="square"
onClick={function(ref) { console.info(" click : " + ref.target.innerHTML); }}>
{this.props.value}
</button>
);
}
}
Credits : https://stackoverflow.com/ about using "ref"