简体   繁体   中英

What is the correct way to select one of many child elements in React?

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

In your case, there is no really simpler way. State of current selected Line is "above" line collection (parent), which is correct (for case where siblings need to know).

However, you could simplify your code a lot :

<Container>
{this.state.lines.map((subtitle, index) => (
    <div id={"text-line-" + index}>
        <Line
            handleClick={this.handleClick}
            lineContent={subtitle.text}
            selected={index === this.state.selectedLine}
        />
    </div>
))}
</Container>

and for Line component, it is good practice to use functional component, since it is stateless and even doesn't use any lifecycle method.

Edit: Added missing close bracket

'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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM