ReactJS: How to refresh component without 1 click delay - reactjs

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.

Related

Unexpected Behavior After State Change in React Component

RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
console.log(selected)
return(
<div className="results_wrapper">
{selected.map((r,i)=>{
let openState = (this.state.selectedImage==i)?true:false;
return(
<RenderPanel panelType={PanelType.large} openState={openState} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
<div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
<img src={r.url} />
</div>
)} content={(closeIt)=>(
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain,r.parent)}
<div onClick={()=>{
closeIt();
this.setState({selectedImage:2})
console.log('wtfff'+this.state.selectedImage)
}
}>Next</div>
<img src={r.url} />
</div>
)}/>
)
})}
</div>
)
}
When I change the state of 'selectedImage', I expect the variable 'openState' to render differently within my map() function. But it does not do anything.
Console.log shows that the state did successfully change.
And what is even stranger, is if I run "this.setState({selectedImage:2})" within componentsDidMount(), then everything renders exactly as expected.
Why is this not responding to my state change?
Update
I have tried setting openState in my component state variable, but this does not help either:
RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
console.log(selected)
let html = selected.map((r,i)=>{
return(
<RenderPanel key={i} panelType={PanelType.large} openState={this.state.openState[i]} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
<div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
<img src={r.url} />
</div>
)} content={(closeIt)=>(
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain,r.parent)}
<div onClick={()=>{
closeIt();
let openState = this.state.openState.map(()=>false)
let index = i+1
openState[index] = true;
this.setState({openState:openState},()=>console.log(this.state.openState[i+1]))
}
}>Next</div>
<img src={r.url} />
</div>
)}/>
)
})
return(
<div className="results_wrapper">
{html}
</div>
)
}
https://codesandbox.io/s/ecstatic-bas-1v3p9?file=/src/Search.tsx
To test, just hit enter at the search box. Then click on 1 of 3 of the results. When you click 'Next', it should close the pane, and open the next one. That is what I'm trying to accomplish here.
#Spitz was on the right path with his answer, though didn't follow through to the full solution.
The issue you are having is that the panel's useBoolean doesn't update it's state based on the openState value passed down.
If you add the following code to panel.tsx, then everything will work as you described:
React.useEffect(()=>{
if(openState){
openPanel()
}else{
dismissPanel();
}
},[openState, openPanel,dismissPanel])
What this is doing is setting up an effect to synchronize the isOpen state in the RenderPanel with the openState that's passed as a prop to the RenderPanel. That way while the panel controls itself for the most part, if the parent changes the openState, it'll update.
Working sandbox
I believe it's because you set openState in your map function, after it has already run. I understand you think the function should rerender and then the loop will run once more, but I think you'll need to set openState in a function outside of render.
The problem is that even though you can access this.state from the component, which is a member of a class component, there's nothing that would make the component re-render. Making components inside other components is an anti-pattern and produces unexpected effects - as you've seen.
The solution here is to either move RenderImages into a separate component altogether and pass required data via props or context, or turn it into a normal function and call it as a function in the parent component's render().
The latter would mean instead of <RenderImages/>, you'd do this.RenderImages(). And also since it's not a component anymore but just a function that returns JSX, I'd probably rename it to renderImages.
I tire to look at it again and again, but couldn't wrap my head around why it wasn't working with any clean approach.
That being said, I was able to make it work with a "hack", that is to explicitly call openIt method for selectedImage after rendering is completed.
RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter((x) =>
this.state.selectedGroups.includes(x.domain)
);
return (
<div className="results_wrapper">
{selected.map((r, i) => {
let openState = this.state.selectedImage === i ? true : false;
return (
<RenderPanel
key={i}
panelType={PanelType.medium}
openState={openState}
title={r.domain + ".TheCommonVein.net"}
preview={(openIt) => {
/* This is where I am making explicit call */
if (openState) {
setTimeout(() => openIt());
}
/* changes end */
return (
<div
className="result"
onClick={openIt}
style={{ boxShadow: theme.effects.elevation8 }}
>
<img src={r.url} />
</div>
);
}}
content={(closeIt) => (
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain, r.parent)}
<div
onClick={() => {
closeIt();
this.setState({
selectedImage: i + 1
});
}}
>
[Next>>]
</div>
<img src={r.url} />
</div>
)}
/>
);
})}
</div>
);
};
take a look at this codesandbox.

Switch to different component after button click with React

I am absolutely new to React and need to create a button to take the user from a current component (MovieView) to the main one (MainView). I managed to create it properly and I used onClick to trigger the display of MainView. I know I cannot call a class from a function (so as console says), so I created a second function to be called and trigger MainView. But as you may wonder, it does not work. This is the code I am using:
import React from 'react';
import { MainView } from '../main-view/main-view';
function NewButton() {
return <MainView />;
}
export class MovieView extends React.Component {
constructor() {
super();
this.state = {};
}
render() {
const { movie } = this.props;
if (!movie) return null;
return (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={NewButton}>Back</button>
</div>
</div>
);
}
}
Could you any seeing this to point the path to take or how I can call this MainView class properly. I understand it's a simple task, but I am afraid I still haven't absorbed React principles yet. Thanks in advance to you all.
Without introducing additional dependencies, probably the easiest way for this example is to use state to track if the button has been clicked. If it has been clicked, render MovieView, if not render MainView.
For this you need to following:
Set state variable that tracks that MainView should be rendered in onClick event. (boolean will probably suffice)
in render():
if the state var is false, render the content of MovieView.
if the state var is true, render the MainView component.
Implementation details are left as an exercise :)
If you're using a router already (like react-router) you could probably just update the url to an url that is linked to the MainView component..
I make the Class into a Function Component instead, and implement it like this :-)
import React from "react";
import { MainView } from '../main-view/main-view';
export const MovieView = ({ movie }) => {
const { showMainView, setShowMainView } = React.useState(false);
if (!!movie) {
return null;
}
function toggleMainView() {
setShowMainView(true);
}
return (
<React.Fragment>
{ showMainView ? (
<MainView />
) : (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={() => toggleMainView()}>Back</button>
</div>
</div>
)}
</React.Fragment>
);
};
export default MovieView;
If you need to add or remove components or show a new view based on an user action in react we need to do this via state .
import React from 'react';
import { MainView } from '../main-view/main-view';
export class MovieView extends React.Component {
constructor() {
super();
this.state = {
showMainView: false;
};
}
toggleMainView = () => this.setState(prevState => ({ showMainView:
!prevState.showMainView}))
render() {
const { movie } = this.props;
const { showMainView } = this.state;
if (!movie) return null;
return (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={this.toggleMainView}>Back</button>
</div>
// render the mainView based on the state
{ showMainView && <MainView />}
</div>
);
}
}
I reached a solution that I'd not do with the help I had here. I have first pasted here some pieces of code but, I believe it can be helpful to give the whole answer.
So, the question I placed here was regarding a component called <MovieView, that was a part of an app that interacted with another component (in another file) called MainView.
The solution was the insertion of the method onClick on the live 14 of <MovieView> and the button on line 39 that you can see at this file on Github. If the file was updated, check the version for November 22nd.
The solution had, however, been found in the main component, <MainView>, adding code on lines 48 and 49. Here is the link to the file: https://github.com/cgobbet/react-client/blob/master/src/components/main-view/main-view.jsx
The repository with the whole app is on this page: https://github.com/cgobbet/react-client.
And sorry for the reviewers (I had accidentally deleted part of the fantastic answer submitted by #nick-miller.

Setting focus via a ref only works in setTimeout in React?

My code below works but the this.buttonRef.current.firstChild.focus() stops working if it's not in a setTimeout function.
From looking at the official docs for refs I cant see why this is happening. Is there anything obviously wrong with my component? If not Im wondering if another component on my site is 'stealing' focus as when the url prop changes a modal is closed.
UPDATE: One weird thing is if I console.log outside of the setTimeout then I can see the element is present in the DOM.
UPDATE2: Turns out it was React Trap Focus in my modal that was causing the issue. Removing the focus trap means I don't need the timeout. As I need the focus trap I think the setTimeout will need to stay.
https://github.com/davidtheclark/focus-trap-react
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.buttonRef = React.createRef();
}
componentDidUpdate(prevProps) {
if (this.props.url === '' && prevProps.url = "old-url") {
console.log('target element: ', this.buttonRef.current.firstChild)
// This doenst work if not in a setTimeout
// this.buttonRef.current.firstChild.focus();
setTimeout(() => {
this.buttonRef.current.firstChild.focus();
}, 1);
}
}
render() {
const {
limitIsReached,
add
} = this.props;
return (
<Fragment>
<Title>My title</Title>
<Section>
<Button>
Add a promo code
</Button>
<span ref={this.buttonRef}>
{limitIsReached ? (
<Alert
message="Sorry limit reached"
/>
) : (
<Button
onClick={add}
>
Add new
</Button>
)}
</span>
<List compact />
</Section>
</Fragment>
);
}
}
export default MyComponent;
Considering that seemingly componentDidUpdate runs before your buttonRef is resolved, a short setTimeout isn't the worst solution.
You could try other ways involving setting state:
componentDidUpdate(prevProps) {
if (.... oldurl) {
this.setState({focusBtn: true})
}
Then when the buttonref resolves:
<span ref={ref=>{
if (this.state.focusBtn) {
this.buttonRef = ref;
this.buttonRef.current.firstChild.focus();
} } >...
EDIT
ok so if you remove the conditional in your render method, React will ensure that your ref has resolved on componentDidMount and also componentDidUpdate (as you wish it to be)
Try this:
<span ref={this.buttonRef}>
<Alert
message="Sorry limit reached"
style={{display: limitIsReached ? 'block' : 'none'}}
/>
<Button
onClick={add} style={{display: limitIsReached ? 'none' : 'inline-block'}}
>
Add new
</Button>
)}
</span>

I'd like some advices on best practises on using react

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>
);
}

Can't focus on React input

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.

Resources