Having some trouble trying to focus in on an element. I have a mapped array function that spits out html with inputs. It is possible to have multiple id's, so I want to set the ref to be 'type' + Id. The two possible types are task and subtask. When I try access via this.refs.{refValue}.focus() I get a Cannot read property 'focus' of undefined
Here's my jsx:
<input className="ui-input-text" type="text" ref="subTask + {subTask.Id}" onChange={this.handleSubTaskChange.bind(this, indx, idx)} value={subTask.Name} />
Here's where I get my error
var subTaskRef = 'subTask' + subTaskId;
this.refs.subTaskRef.focus();
The variable subTaskId is correct, I have verified that. Perhaps I am setting the ref incorrectly?
EDIT
After following #Ori Drori's answer, here's some more code:
class Tasks extends React.Component {
focusTasks: [],
focusSubTasks: [],
constructor(props) {
super(props);
this.state = {
editableTasks: [],
editableSubTasks: [],
tasks: [],
subTasks: [],
plannerId: this.props.plannerId,
};
var state = this.state;
}
and (part) of my render method
render() {
const tasks = this.state.tasks.map((task, idx) => {
var editable = this.state.editableTasks.filter(id => id === task.Id).length > 0;
var editableSubTasks = this.state.editableSubTasks;
const subTaskComponents = task.SubTasks.map((subTask, indx) =>
<li key={subTask.Id} className="list-group-item" style={{minHeight: '50px', border: 0, backgroundColor: 'rgba(127,191,63,.42)'}}>
<div className="pull-left" style={{width: '50%'}}>
<!-- Pay attention to this line -->{editableSubTasks.filter(id => id === subTask.Id).length > 0 ? <input className="ui-input-text" type="text" ref={ (ref) => this.focusSubTasks[subTask.Id] = ref } onChange={this.handleSubTaskChange.bind(this, indx, idx)} value={subTask.Name} /> : <span>{subTask.Name}</span>}
</div>
<div className="pull-right" style={{marginTop: '-5px', width: '50%'}}>
<div className="pull-right">
<button className="btn btn-default" onClick={() => { this.EditSubTask(task.Id, subTask.Id)}}>{editableSubTasks.filter(id => id === subTask.Id).length > 0 ? <i className="fa fa-check"></i> : <i className="fa fa-pencil-square-o"></i>}</button>
</div>
</div>
</li>
);
Here's where the issue seems to be (won't build)
Ended up just using jQuery, it's much easier when it's one line of code. Not sure if what I'm doing is too complicated for this, but I ended up setting an id on the inputs, and just calling $(el).focus() to solve this problem. Unless someone has a working example, I will update this SO.
Using the ref callback just to set a property on the class is a common pattern for accessing DOM elements and React creators recommend to use this pattern instead of this.refs.myRef pattern.
class MyComponent extends React.Component {
// ..
render() {
return (
<input ref={(thisEl) => { this['name' /* + subTask.Id */] = thisEl }} />
)
}
}
Now you can just use it as this['name' /* + subTask.Id */].focus().
However, Im not 100% sure if that could be the cause of your issue, especially because you didn't let us know if console.log(this.refs) actually has correct elements and if you didn't make mistakes.
Let me know how it works out for you.
I don't recommend to use jQuery, in other words: don't mix avoidable imperative code with declarative code. It seems like an easy solution for your issues but if you'll get the whole point of React, you'll understand that jQuery is not the easy solution, especially in long run.
Related
I'm learning React and I have problem with refreshing a child component when parent's state is changed by another child. I found that I should use componentWillReceiveProps() and it works but with one click delay. What should I change to get immediate update?
I've posted my code on CodePen to make it easier to explain. If it's better to post it here directly please let me know, and I will update.
FCC: PomodoroClock - CodePen
Problems:
When I increase or decrease length in ClockSetter (+ and - buttons) then parent's state changes immediately but session length in Timer shows previous value.
When I click reset button parent's state changes immediately, but nothing happens with ClockSetter's length and Timer's lenght changes to ClockSetter's. After another click on reset both children lenght's changes to parent's state.
If I try to increase or decrease after reset (instead of second reset click) it goes crazy (i can't find the rule how it changes)
Is it possible to make it only with React or should I start learning Redux?
Edit: My code
import React from 'react';
import './PomodoroClock.scss';
class PomodoroClock extends React.Component {
constructor(props) {
super(props);
this.state = {
sessionLength: 25,
breakLength: 5,
};
this.handleTime = this.handleTime.bind(this);
this.handleReset = this.handleReset.bind(this);
}
handleTime(type, time) {
this.setState({
[type]: time
});
}
handleReset() {
this.setState({
sessionLength: 25,
breakLength: 5,
});
}
render() {
return (
<div className="container">
<ClockSetter clockType="break" initialLength={this.state.breakLength} handleTime={this.handleTime} />
<ClockSetter clockType="session" initialLength={this.state.sessionLength} handleTime={this.handleTime} />
<Timer sessionLength={this.state.sessionLength} reset={this.handleReset}/>
<span>TEST: State session - {this.state.sessionLength} State break - {this.state.breakLength}</span>
</div>
);
}
}
class ClockSetter extends React.Component {
constructor(props) {
super(props);
this.state = {
length: this.props.initialLength
}
this.handleDecrease = this.handleDecrease.bind(this);
this.handleIncrease = this.handleIncrease.bind(this);
this.refresh = this.refresh.bind(this);
}
handleDecrease() {
if(this.state.length > 1) {
this.setState ({
length: this.state.length - 1
});
}
this.props.handleTime(this.props.clockType+'Length', this.state.length - 1);
}
handleIncrease() {
if(this.state.length < 60) {
this.setState ({
length: this.state.length + 1
});
}
this.props.handleTime(this.props.clockType+'Length', this.state.length + 1);
}
refresh() {
this.setState({
length: this.props.initialLength
});
}
componentWillReceiveProps(props) {
if(this.state.length !== this.props.initialLength) {
this.refresh();
}
}
render() {
let type = this.props.clockType;
return(
<div className="clock-setter">
<div id={type + '-label'} className="first-letter-capitalize">{type + ' Length'}</div>
<span id={type + '-decrement'} className="button" onClick={this.handleDecrease}>-</span>
<span id={type + '-length'}>{this.state.length}</span>
<span id={type + '-increment'} className="button" onClick={this.handleIncrease}>+</span>
</div>
);
}
}
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
activeCountdown: 'Session',
length: this.props.sessionLength
}
this.refresh = this.refresh.bind(this);
}
refresh() {
this.setState({
length: this.props.sessionLength
});
}
componentWillReceiveProps(props) {
if(this.state.length !== this.props.sessionLength) {
this.refresh();
}
}
render() {
return(
<div className="timer">
<span id="timer-label">{this.state.activeCountdown}</span>
<div id="time-left">{this.state.length}</div>
<span id="start_stop" className="button">Start/Stop</span>
<span id="reset" className="button" onClick={this.props.reset}>Reset</span>
</div>
);
}
}
export default PomodoroClock;
Let's refactor your code in such a way that fixes the immediate issue while at the same time addresses some bad practices and antipatterns that will cause you headaches moving forward.
And because you're just starting, this is the perfect time to learn about hooks because they will make your code much more simple and easier to follow.
The main pitfall in your code is duplication of state. Let's start with the Timer component.
You are setting its initial state length to be the value of its parents state sessionLength. Even though you can perceive this to be some type of "initial" state and then afterwards the Timer's length will be independent of sessionLength once the countdown starts, this is not necessary. In fact.. duplication of state is not necessary in 99% of situations.
So what state should the Timer have? I would reckon that the timer might have its own internal counter state such that you display the current time like this.props.sessionLength - this.state.elapsedTime, but in your case the Timer isn't actually doing any timing. You're keeping track of the current time at the parent level anyways.
Knowing this.. what should the Timer state be? Nothing! The answer is no state. Timer can be a function, not a class, and receive props and display them.
function Timer(props) {
return (
<div className="timer">
<span id="timer-label">Session</span>
<div id="time-left">{props.sessionLength}</div>
<span id="start_stop" className="button">
Start/Stop
</span>
<span id="reset" className="button" onClick={props.reset}>
Reset
</span>
</div>
)
}
If that's all you change, this already solves your question.
Next let's look at the ClockSetter component. You are duplicating the state here in the exact same way, and not only that, you have extra handlers which simply call the parents handler handleTime, introducing extra steps and complexity which add unnecessary noise to your application. Let's get rid of the state and the extra handlers altogether, and as such we can use a function again, instead of a class:
function ClockSetter(props) {
let type = props.clockType
return (
<div className="clock-setter">
<div id={type + '-label'} className="first-letter-capitalize">
{type + ' Length'}
</div>
<span
id={type + '-decrement'}
className="button"
onClick={() => props.handleTime(type + 'Length', props.length - 1)}
>
-
</span>
<span id={type + '-length'}>{props.length}</span>
<span
id={type + '-increment'}
className="button"
onClick={() => props.handleTime(type + 'Length', props.length + 1)}
>
+
</span>
</div>
)
}
I've inlined the onClick handlers for brevity. You could write the named handleDecrease and handleIncrease functions above the return statement and passed them the onClick if you want. That's just a matter of preference though.
*Note: the prop is now length not initialLength. Make sure to update that when rendering your ClockSetter components
For this last refactor I have updated your React cdn to point to the latest stable release 16.8.3, since it includes hooks.
Instead of using a class, let's write a normal function and use the React.useState hook. The code looks like this now:
function PomodoroClock() {
let [sessionLength, setSessionLength] = React.useState(25)
let [breakLength, setBreakLength] = React.useState(5)
function handleReset() {
setSessionLength(25)
setBreakLength(5)
}
return (
<div className="container">
<ClockSetter
clockType="break"
length={breakLength}
handleTime={setBreakLength}
/>
<ClockSetter
clockType="session"
length={sessionLength}
handleTime={setSessionLength}
/>
<Timer
sessionLength={sessionLength}
reset={handleReset}
/>
<span>
Parent's state TEST: session - {sessionLength} break -
{breakLength}
</span>
</div>
)
}
and instead of having a single state object with keys that reference each timer, since that was the only way with stateful components before, we call useState twice each with their respective state and handlers. Now we can remove the type + Length argument in our ClockSetter component:
onClick={() => props.handleTime(props.length + 1)}
This is the entire program now:
function PomodoroClock() {
let [sessionLength, setSessionLength] = React.useState(25)
let [breakLength, setBreakLength] = React.useState(5)
function handleReset() {
setSessionLength(25)
setBreakLength(5)
}
return (
<div className="container">
<ClockSetter
clockType="break"
length={breakLength}
handleTime={setBreakLength}
/>
<ClockSetter
clockType="session"
length={sessionLength}
handleTime={setSessionLength}
/>
<Timer
sessionLength={sessionLength}
reset={handleReset}
/>
<span>
Parent's state TEST: session - {sessionLength} break -
{breakLength}
</span>
</div>
)
}
function ClockSetter(props) {
let type = props.clockType
return (
<div className="clock-setter">
<div id={type + '-label'} className="first-letter-capitalize">
{type + ' Length'}
</div>
<span
id={type + '-decrement'}
className="button"
onClick={() => props.handleTime(props.length - 1)}
>
-
</span>
<span id={type + '-length'}>{props.length}</span>
<span
id={type + '-increment'}
className="button"
onClick={() => props.handleTime(props.length + 1)}
>
+
</span>
</div>
)
}
function Timer(props) {
return (
<div className="timer">
<span id="timer-label">Session</span>
<div id="time-left">{props.sessionLength}</div>
<span id="start_stop" className="button">
Start/Stop
</span>
<span id="reset" className="button" onClick={props.reset}>
Reset
</span>
</div>
)
}
ReactDOM.render(
<PomodoroClock />,
document.getElementById('root')
);
Link to codepen
We've shaved of over 50 lines of code, it's much easier to read, and there's no potential issue of state being duplicated.
Hope that helps and happy coding! Please ask any questions if you need.
Dear genius StackOverflowians,
I am trying to write an app where users can configure questions and answers, along with defining help text for each question. I'm writing this in typescript React - which is handy when you want to define types of answers for questions.
I want to have a button next to the question that shows/hides a styled document. The button looks and works great, but the document that is hidden/shown doesn't get the generated style class that ought to be associated with it.
Here is the functional component to display the help document:
let HelpTextBody = function(props: { helpDocument: DocumentationStore }) {
return (
<div>
{props.helpDocument.toReallySimple().map(tok => {
return React.createElement(tok.tag, null, tok.content);
})}
</div>
);
};
tok comes from a custom class DocumentationStore that is pretty much a wrapper around markdown-it, a handy js library for working with md files, which I would like my users to write their helptext in (and store it that way).
So I do this (in a different module for DocumentationStore class):
toReallySimple(): MdJson[] {
let bigParsed = this.md_.parse(this.Text, null).filter(
t => return t.type == "inline" || t.type.indexOf("open") > 0
});
Later on, I style HelpTextBody with:
const StyledHelpDocument = styled(HelpTextBody)`
background-color: lightslategray;
`;
Keeping it simple now so I can just see if it's working...
I then include it in a component with the button that I export:
class HelpText extends React.Component<helpProps, helpState> {
constructor(props: helpProps) {
super(props);
this.state = {
hidden: true
};
}
swapHidden() {
this.setState({
hidden: !this.state.hidden
});
}
render() {
if (this.state.hidden) {
return (
<span>
<StyledButton
itemScope={this.state.hidden}
onClick={() => this.swapHidden()}
>
Need Help?
</StyledButton>
</span>
);
} else {
return (
<span>
<StyledButton onClick={() => this.swapHidden()}>
Hide Help
</StyledButton>
<StyledHelpDocument helpDocument={this.props.helpDocument} />
</span>
);
}
}
So I webpack it all and get stuff into the browser, and what I get back is this style tag (after clicking the button), which looks right:
<style data-styled-components="">
/* sc-component-id: sc-bdVaJa */
.sc-bdVaJa {} .gscXTZ{background:red;color:white;font-size:1em;margin:1em;padding:0.25em 1em;border:2px solid red;border-radius:3px;}.iwtdKP{background:white;color:red;font-size:1em;margin:1em;padding:0.25em 1em;border:2px solid red;border-radius:3px;}
/* sc-component-id: sc-bwzfXH */
.sc-bwzfXH {} .hAvMqj{background-color:lightslategray;}</style>
But my html for the document is missing the reference to the class (.hAvMqj I guess?)
<span>
<button class="sc-bdVaJa iwtdKP">Hide Help</button>
<div><p>Here the text is grey</p></div>
<!-- ^This^ is the StyledHelpDocument... no class!-->
</span>
So where am I going wrong? I don't understand why it generates the style, and the component's HTML renders... but the class isn't applied to the component! What do you think?
Your styled-components class isn't being applied because you're styling a custom component, but you haven't included className as a prop. Add className as an optional prop in the component you're styling, and also be sure to apply className somewhere in the render method for that component. For your case, it should be added like so:
let HelpTextBody = function(props: { helpDocument: DocumentationStore, className: string }) {
return (
<div className={props.className}>
{props.helpDocument.toReallySimple().map(tok => {
return React.createElement(tok.tag, null, tok.content);
})}
</div>
);
};
Currently, this will render a component below each of the list items when the img is clicked by keeping an array of shown components per index in local state. Eg. (state.showItems ==[true,false,false,true]).
I would like to restrict the values in this array to only one 'true' at a time so that the <SuggestStep /> component is rendered only once in the div under the button that was clicked. I'm not using CSS because the list can grow very large and don't want to render and hide a component for each one. Also considered using a radio button displayed as an image, but don't know if that would involve mixing forms with LI's and if that is bad. Feedback on the question of restricting the showItems array items to only one true at a time, and general patterns to approaching the component rendering problem I'm describing are welcome.
class CurrentSteps extends Component {
constructor(props) {
super(props)
this.state = {
toggleOnSuggestInput: false,
showItems: []
}
this.clickHandler = this.clickHandler.bind(this)
}
clickHandler(index){
let showItems = this.state.showItems.slice();
showItems[index] = !showItems[index]
this.setState({showItems})
this.setState(prevState => ({
toggleOnSuggestInput: !prevState.toggleOnSuggestInput
}))
}
render() {
let steps = this.props.currentGoalSteps.map((step, index) => {
return (
<div key={`divKey${index}`}>
<li key={index}>{step}</li>
<img key={`imageKey${index}`} onClick={this.clickHandler.bind(this,index)} alt="" src={plus}/>
{this.state.showItems[index] ? <SuggestStep /> : null}
</div>
)
});
return (
<div>
<ul> {steps} </ul>
</div>
)
}
Try making the following modifications to your code...
Change your this.state like so.
this.state = {
toggleOnSuggestInput: false,
activeIndex: null
};
Change your clickHandler to this.
clickHandler(event, index) {
this.setState({ activeIndex: index })
}
Change your map to like the one below. Notice the onClick prop change.
let steps = this.props.currentGoalSteps.map((step, index) => {
return (
<div key={`divKey${index}`}>
<li key={index}>
{step}
</li>
<img
key={`imageKey${index}`}
onClick={e => this.clickHandler(e, index)}
alt=""
src={plus}
/>
{this.state.activeIndex === index ? <SuggestStep /> : null}
</div>
);
});
As a way to learn new frameworks, I usually rewrite a small web app I made to handle a list of mp3s I use in a web radio. So, now, it's react's turn !
So basically, I render a table that has fields, and one of those td has a list of tags, as I use bootstrap for that purpose, I have bootstrap labels (that are called badges on bootstrap 4), so here's the minimum example of code I could have :
<td>
<span class="badge badge-success">tag1</span>
<span class="badge badge-success">tag2</span>
<span class="badge badge-success">tag3</span>
</td>
So, basically, to teach myself how to use a component inside a component by the example, I made a component to handle the tags (showing them, and later, have a popup to select them) but the way I have to render the data is so inelegant, I suppose there's some way to make it better.
here is the render() of the component :
render() {
return (
<span>
{ this.props.tags.map((tag) => {
return (
<span key={tag._id}>
<span className="badge badge-success">{tag.intitule}</span>
{' '}
</span>
);
})}
</span>
);
}
I suppose I could render the whole td to avoid a span, but then it would not be as reusable as withouth it. So, how can I make this prettier to read, and with a more elegant rendered code.
Rendering children components without a parent tag is now impossible, but there is an open GitHub issue that hopefully will let us avoid this limitation.
However, your code can be more elegant if you will convert it to:
render(){
const { tags } = this.props;
return (
<div>
{ tags.map(tag => <span key={tag._id} className="badge badge-success">{tag.intitule}</span>) }
</div>
)
}
Update #1
You can also separate "tag" to a stateless functional component, like so:
const Tag = ({tag}) => (
<span className="badge badge-success">{tag.intitule}</span>
);
then, in other, more complex component, you can do
class ComplexComponent extends React.Component{
render(){
const { tags } = this.props;
return (
<div>
{ tags.map(tag => <Tag key={tag._id} tag={tag} />) }
</div>
)
}
}
Check this fiddle.
I think I would do the mapping out of the return. Like this :
render() {
let labels = this.props.tags.map((tag) => {
return (
<span key={tag._id}>
<span className="badge badge-success">{tag.intitule}</span>
{' '}
</span>
);
})
return (
<span>
{ labels }
</span>
);
}
You could also create a small component for your labels (a little bit overkill), like this:
export class MyLabel extends React.Component {
render() {
const {tag} = this.props;
return <span key={tag._id}>
<span className="badge badge-success">{tag.intitule}</span>
{' '}
</span>
}
}
... // In your main Component
render() {
let labels = this.props.tags.map((tag) => {
return <MyLabel tag={tag}/>;
})
return (
<span>
{ labels }
</span>
);
}
I’ve created InitializePhoneNumbersPanel:
class InitializePhoneNumbersPanel extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(phoneNumbers) {
const { dispatch, operatorId } = this.props;
dispatch(updateOperatorData(operatorId, phoneNumbers, {include: 'phone_numbers'}));
}
render() {
const {
handleSubmit,
submitting,
fields: { phone_numbers }
} = this.props;
console.log('\n... Render ...');
console.log('phone_numbers <<<<< ', phone_numbers);
if (_.isEmpty(phone_numbers)) {
return (
<div className={"fade in"}>
Hello
</div>
)
}
return (
<form onSubmit={handleSubmit(this.onSubmit)}>
<div className="row">
<div className="col-md-12">
<ul className="list-unstyled m-b-0 clearfix">
{phone_numbers && phone_numbers.map((phone, index) =>
<PhoneNumbersPanelItem key={index} phone={phone} phone_numbers={phone_numbers}
index={index}/>
)}
</ul>
</div>
</div>
<div className="row">
<div className="col-md-12">
<button type="button" className="btn btn-sm btn-success" onClick={event => {
event.preventDefault();
phone_numbers.addField();
}}><i className="fa fa-plus"></i>
</button>
</div>
</div>
<hr/>
<div className="row">
<div className="col-md-12">
<button type="submit" disabled={ submitting } className="btn btn-sm btn-success pull-right">
Save
</button>
</div>
</div>
</form>
)
}
}
Then this component is wrapped by Redux-form:
InitializePhoneNumbersPanel = reduxForm({
form: 'phone-numbers-panel',
fields
})(InitializePhoneNumbersPanel);
Then everything is wrapped by connect method to make data from Store accessible in Redux-form as fields:
function select(state) {
return {
initialValues: {
phone_numbers: _.map(state.operators.items[state.operators.selectedOperator] && state.operators.items[state.operators.selectedOperator].phone_numbers, phoneId => {
return state.phoneNumbers.items[phoneId];
})
},
operatorId: state.operators.selectedOperator
};
}
InitializePhoneNumbersPanel = connect(select)(InitializePhoneNumbersPanel);
The error is…
The code above works normally however in PhoneNumbersPanelItem component phone numbers which come from “phone_numbers” variable are repeated.
When the operators page(whose phone numbers are shown using PhoneNumbersPanelItem) is loaded the first time no errors occur, however if I choose other operator, Route will change which means operatorId param in store will change which means operators object will change and the phone numbers will be different… changed data are sent to component here:
function select(state) {
return {
initialValues: {
phone_numbers: _.map(state.operators.items[state.operators.selectedOperator] && state.operators.items[state.operators.selectedOperator].phone_numbers, phoneId => {
return state.phoneNumbers.items[phoneId];
})
},
operatorId: state.operators.selectedOperator
};
}Operator
};
}
InitializePhoneNumbersPanel = connect(select)(InitializePhoneNumbersPanel);
So if the number of phone numbers of chosen operator is less than the previous one had , the error is thrown
Uncaught Invariant Violation: findComponentRoot(...,
.0.0.0.1.2.0.0.2.1.0.1.1.0.0.0.$1.0.0.0.0.1.1.0): Unable to find
element. This probably means the DOM was unexpectedly mutated (e.g.,
by the browser), usually due to forgetting a when using
tables, nesting tags like , , or , or using non-SVG
elements in an parent. Try inspecting the child nodes of the
element with React ID ``.
As I understood, the error is thrown because at the beginning there were 3 phone numbers for example, and when I choose a new operator the number of phones is 2 and React seemingly fails to find html code for the third number as in the new rendering this element was not created
Even though there is an error, everything works ok. Probably with another rendering react understands that the state has updated and rerenders virtual DOM
If all operators have the same number of phone numbers, NO error occur AT ALL
How can I fix this error? Has anybody encountered anything like that? So strange that React doesn’t understand that the virtual DOM has changed when we switch to a new Route.
I’ll appreciate any help/solution to this problem
I've tried multiple things to make it work on mine. I had a similar problem.
Apparently the problem was with the type of the button. ReactDOM gets lost if you use a type="button"
I removed the type="button" and added a event.preventDefault() on my onClick handler and it worked for me.
I had a very similar scenario. I tried a bunch of things and the only thing that worked for me was updating react and react-dom to version 15.3.2 (from 0.14.2).