I have a small part of my new React app which contains a block of text, AllLines
, split into line-by-line components called Line
. I want to make it work so that when one line is clicked, it will be selected and editable and all other lines will appear as <p>
elements. How can I best manage the state here such that only one of the lines is selected at any given time? The part I am struggling with is determining which Line
element has been clicked in a way that the parent can change its state.
I know ways that I can make this work, but I'm relatively new to React and trying to get my head into 'thinking in React' by doing things properly so I'm keen to find out what is the best practice in this situation.
class AllLines extends Component {
state = {
selectedLine: 0,
lines: []
};
handleClick = (e) => {
console.log("click");
};
render() {
return (
<Container>
{
this.state.lines.map((subtitle, index) => {
if (index === this.state.selectedLine) {
return (
<div id={"text-line-" + index}>
<TranscriptionLine
lineContent={subtitle.text}
selected={true}
/>
</div>
)
}
return (
<div id={"text-line-" + index}>
<Line
lineContent={subtitle.text}
handleClick={this.handleClick}
/>
</div>
)
})
}
</Container>
);
}
}
class Line extends Component {
render() {
if (this.props.selected === true) {
return (
<input type="text" value={this.props.lineContent} />
)
}
return (
<p id={} onClick={this.props.handleClick}>{this.props.lineContent}</p>
);
}
}
'Thinking in React' you would want to give up your habit to grab DOM elements by their unique id
;)
From what I see, there're few parts missing from your codebase:
- smart click handler that will keep only one line selected at a time
- edit line handler that will stick to the callback that will modify line contents within parent state
- preferably two separate components for the line capable of editing and line being actually edited as those behave in a different way and appear as different DOM elements
To wrap up the above, I'd slightly rephrase your code into the following:
const { Component } = React,
{ render } = ReactDOM
const linesData = Array.from(
{length:10},
(_,i) => `There goes the line number ${i}`
)
class Line extends Component {
render(){
return (
<p onClick={this.props.onSelect}>{this.props.lineContent}</p>
)
}
}
class TranscriptionLine extends Component {
constructor(props){
super(props)
this.state = {
content: this.props.lineContent
}
this.onEdit = this.onEdit.bind(this)
}
onEdit(value){
this.setState({content:value})
this.props.pushEditUp(value, this.props.lineIndex)
}
render(){
return (
<input
style={{width:200}}
value={this.state.content}
onChange={({target:{value}}) => this.onEdit(value)}
/>
)
}
}
class AllLines extends Component {
constructor (props) {
super(props)
this.state = {
selectedLine: null,
lines: this.props.lines
}
this.handleSelect = this.handleSelect.bind(this)
this.handleEdit = this.handleEdit.bind(this)
}
handleSelect(idx){
this.setState({selectedLine:idx})
}
handleEdit(newLineValue, lineIdx){
const linesShallowCopy = [...this.state.lines]
linesShallowCopy.splice(lineIdx,1,newLineValue)
this.setState({
lines: linesShallowCopy
})
}
render() {
return (
<div>
{
this.state.lines.map((text, index) => {
if(index === this.state.selectedLine) {
return (
<TranscriptionLine
lineContent={text}
lineIndex={index}
pushEditUp={this.handleEdit}
/>
)
}
else
return (
<Line
lineContent={text}
lineIndex={index}
onSelect={() => this.handleSelect(index)}
/>
)
})
}
</div>
)
}
}
render (
<AllLines lines={linesData} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>