简体   繁体   中英

react recreating a component when I don't want to

I'm super new to react, this is probably a terrible question but I'm unable to google the answer correctly.

I have a component (CogSelector) that renders the following

import React from "react"
import PropTypes from "prop-types"
import Collapsible from 'react-collapsible'
import Cog from './cog.js'
const autoBind = require("auto-bind")

import isResultOk from "./is-result-ok.js"

class CogSelector extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            docs: null,
            loaded: false,
            error: null
        }

        autoBind(this)
    }

    static get propTypes() {
        return {
            selectCog: PropTypes.func
        }
    }

    shouldComponentUpdate(nextProps, nextState){
        if (nextState.loaded === this.state.loaded){
            return false;
        } else {
            return true;
        }
    }

    componentDidMount() {
        fetch("/api/docs")
            .then(isResultOk)
            .then(res => res.json())
            .then(res => {
                this.setState({docs: res.docs, loaded: true})
            }, error => {
                this.setState({loaded: true, error: JSON.parse(error.message)})
            })
    }

    render() {
        const { docs, loaded, error } = this.state
        const { selectCog } = this.props
        if(!loaded) {
            return (
                <div>Loading.  Please wait...</div>
            )
        }
        if(error) {
            console.log(error)
            return (
                <div>Something broke</div>
            )
        }
        return (
            <>
                Cogs:
                <ul>
                    {docs.map((cog,index) => {
                        return (
                            <li key={index}>
                                <Cog name={cog.name} documentation={cog.documentation} commands={cog.commands} selectDoc={selectCog} onTriggerOpening={() => selectCog(cog)}></Cog>
                            </li>
                            // <li><Collapsible onTriggerOpening={() => selectCog(cog)} onTriggerClosing={() => selectCog(null)} trigger={cog.name}>
                                // {cog.documentation}
                            // </Collapsible>
                            // </li>
                        )
                    })}
                    
                    {/* {docs.map((cog, index) => { */}
                        {/* return ( */}
                            {/* <li key={index}><a onClick={() => selectCog(cog)}>{cog.name}</a></li>
                        )
                    // })} */}
                </ul>
            </>
        )
    }
}

export default CogSelector

the collapsible begins to open on clicking, then it calls the selectCog function which tells it's parent that a cog has been selected, which causes the parent to rerender which causes the following code to run

class DocumentDisplayer extends React.Component{
    constructor(props) {
        super(props)
        this.state = {
            cog: null
        }

        autoBind(this)
    }

    selectCog(cog) {
        this.setState({cog})
    }

    render(){
        const { cog } = this.state
        const cogSelector = (
            <CogSelector selectCog={this.selectCog}/>
        )
        if(!cog) {
            return cogSelector
        }
        return (
            <>
                <div>
                    {cogSelector}
                </div>
                <div>
                    {cog.name} Documentation
                </div>
                <div
                dangerouslySetInnerHTML={{__html: cog.documentation}}>
                </div>
            </>
        )
    }
}

export default DocumentDisplayer

hence the cogSelector is rerendered, and it is no longer collapsed. I can then click it again, and it properly opens because selectCog doesn't cause a rerender.

I'm pretty sure this is just some horrible design flaw, but I would like my parent component to rerender without having to rerender the cogSelector. especially because they don't take any state from the parent. Can someone point me to a tutorial or documentation that explains this type of thing?

I'm super new to react, this is probably a terrible question but I'm unable to google the answer correctly.

I have a component (CogSelector) that renders the following

import React from "react"
import PropTypes from "prop-types"
import Collapsible from 'react-collapsible'
import Cog from './cog.js'
const autoBind = require("auto-bind")

import isResultOk from "./is-result-ok.js"

class CogSelector extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            docs: null,
            loaded: false,
            error: null
        }

        autoBind(this)
    }

    static get propTypes() {
        return {
            selectCog: PropTypes.func
        }
    }

    shouldComponentUpdate(nextProps, nextState){
        if (nextState.loaded === this.state.loaded){
            return false;
        } else {
            return true;
        }
    }

    componentDidMount() {
        fetch("/api/docs")
            .then(isResultOk)
            .then(res => res.json())
            .then(res => {
                this.setState({docs: res.docs, loaded: true})
            }, error => {
                this.setState({loaded: true, error: JSON.parse(error.message)})
            })
    }

    render() {
        const { docs, loaded, error } = this.state
        const { selectCog } = this.props
        if(!loaded) {
            return (
                <div>Loading.  Please wait...</div>
            )
        }
        if(error) {
            console.log(error)
            return (
                <div>Something broke</div>
            )
        }
        return (
            <>
                Cogs:
                <ul>
                    {docs.map((cog,index) => {
                        return (
                            <li key={index}>
                                <Cog name={cog.name} documentation={cog.documentation} commands={cog.commands} selectDoc={selectCog} onTriggerOpening={() => selectCog(cog)}></Cog>
                            </li>
                            // <li><Collapsible onTriggerOpening={() => selectCog(cog)} onTriggerClosing={() => selectCog(null)} trigger={cog.name}>
                                // {cog.documentation}
                            // </Collapsible>
                            // </li>
                        )
                    })}
                    
                    {/* {docs.map((cog, index) => { */}
                        {/* return ( */}
                            {/* <li key={index}><a onClick={() => selectCog(cog)}>{cog.name}</a></li>
                        )
                    // })} */}
                </ul>
            </>
        )
    }
}

export default CogSelector

the collapsible begins to open on clicking, then it calls the selectCog function which tells it's parent that a cog has been selected, which causes the parent to rerender which causes the following code to run

class DocumentDisplayer extends React.Component{
    constructor(props) {
        super(props)
        this.state = {
            cog: null
        }

        autoBind(this)
    }

    selectCog(cog) {
        this.setState({cog})
    }

    render(){
        const { cog } = this.state
        const cogSelector = (
            <CogSelector selectCog={this.selectCog}/>
        )
        if(!cog) {
            return cogSelector
        }
        return (
            <>
                <div>
                    {cogSelector}
                </div>
                <div>
                    {cog.name} Documentation
                </div>
                <div
                dangerouslySetInnerHTML={{__html: cog.documentation}}>
                </div>
            </>
        )
    }
}

export default DocumentDisplayer

hence the cogSelector is rerendered, and it is no longer collapsed. I can then click it again, and it properly opens because selectCog doesn't cause a rerender.

I'm pretty sure this is just some horrible design flaw, but I would like my parent component to rerender without having to rerender the cogSelector. especially because they don't take any state from the parent. Can someone point me to a tutorial or documentation that explains this type of thing?

I'm super new to react, this is probably a terrible question but I'm unable to google the answer correctly.

I have a component (CogSelector) that renders the following

import React from "react"
import PropTypes from "prop-types"
import Collapsible from 'react-collapsible'
import Cog from './cog.js'
const autoBind = require("auto-bind")

import isResultOk from "./is-result-ok.js"

class CogSelector extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            docs: null,
            loaded: false,
            error: null
        }

        autoBind(this)
    }

    static get propTypes() {
        return {
            selectCog: PropTypes.func
        }
    }

    shouldComponentUpdate(nextProps, nextState){
        if (nextState.loaded === this.state.loaded){
            return false;
        } else {
            return true;
        }
    }

    componentDidMount() {
        fetch("/api/docs")
            .then(isResultOk)
            .then(res => res.json())
            .then(res => {
                this.setState({docs: res.docs, loaded: true})
            }, error => {
                this.setState({loaded: true, error: JSON.parse(error.message)})
            })
    }

    render() {
        const { docs, loaded, error } = this.state
        const { selectCog } = this.props
        if(!loaded) {
            return (
                <div>Loading.  Please wait...</div>
            )
        }
        if(error) {
            console.log(error)
            return (
                <div>Something broke</div>
            )
        }
        return (
            <>
                Cogs:
                <ul>
                    {docs.map((cog,index) => {
                        return (
                            <li key={index}>
                                <Cog name={cog.name} documentation={cog.documentation} commands={cog.commands} selectDoc={selectCog} onTriggerOpening={() => selectCog(cog)}></Cog>
                            </li>
                            // <li><Collapsible onTriggerOpening={() => selectCog(cog)} onTriggerClosing={() => selectCog(null)} trigger={cog.name}>
                                // {cog.documentation}
                            // </Collapsible>
                            // </li>
                        )
                    })}
                    
                    {/* {docs.map((cog, index) => { */}
                        {/* return ( */}
                            {/* <li key={index}><a onClick={() => selectCog(cog)}>{cog.name}</a></li>
                        )
                    // })} */}
                </ul>
            </>
        )
    }
}

export default CogSelector

the collapsible begins to open on clicking, then it calls the selectCog function which tells it's parent that a cog has been selected, which causes the parent to rerender which causes the following code to run

class DocumentDisplayer extends React.Component{
    constructor(props) {
        super(props)
        this.state = {
            cog: null
        }

        autoBind(this)
    }

    selectCog(cog) {
        this.setState({cog})
    }

    render(){
        const { cog } = this.state
        const cogSelector = (
            <CogSelector selectCog={this.selectCog}/>
        )
        if(!cog) {
            return cogSelector
        }
        return (
            <>
                <div>
                    {cogSelector}
                </div>
                <div>
                    {cog.name} Documentation
                </div>
                <div
                dangerouslySetInnerHTML={{__html: cog.documentation}}>
                </div>
            </>
        )
    }
}

export default DocumentDisplayer

hence the cogSelector is rerendered, and it is no longer collapsed. I can then click it again, and it properly opens because selectCog doesn't cause a rerender.

I'm pretty sure this is just some horrible design flaw, but I would like my parent component to rerender without having to rerender the cogSelector. especially because they don't take any state from the parent. Can someone point me to a tutorial or documentation that explains this type of thing?

As per the reconciliation algorithm described here https://reactjs.org/docs/reconciliation.html . 在此处输入图片说明

In your parent you have first rendered <CogSelector .../> but later when the state is changed it wants to render <div> <CogSelector .../></div>... which is a completely new tree so react will create a new CogSelector the second time

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