简体   繁体   中英

Get closest parent element by class name in React

Using React, how do I refer to parent element's closest element with certain classname?

Code below:

const Skill = props => (
<div className="skill-card">
    <div className="skill-header">
        <div className="skill-header-text">
            <div className="skill-header-img-container">
                <img src={require('../../img/projects/skills/' + props.skill.image)} alt={props.skill.name} className="skill-image" />
            </div>
            <div className="skill-heading"><h2 className="skill-name">{props.skill.name}</h2></div>
        </div>
        <div className="skill-header-icon">
            <FontAwesomeIcon icon={"angle-" + props.angle} onClick={props.click} />
        </div>
    </div>
    <div className="skill-body">
        ...
    </div>
</div>
);

class Skills extends Component {
    ...

    handleClick = () => {
        // Add style to the closest skill-body in here!
    }
}

In this case, once I click the FontAwesomeIcon , I want the div element with className skill-body to open up.

First I thought that using states would be good, but by far all the skill-body elements will open up upon clicking the FontAwesomeIcon . My approach would be just add stylings to skill-body ( display: block and display: none ).

How can I refer to the skill-body inside the Component 's function with the handleClick ?

EDIT: Used HMR's functional component example, and I am still stuck.

const Skills = ({ skills }) => (
<div>
    {skills.map(skill => (
        <SkillContainer key={skill._id} skill={skill} />
    ))}
</div>
);

const Skill = ({ skill, open, toggle, data }) => (
    <div>
        <h4 onClick={toggle}>skill: {skill} {data.name}</h4>
        {open && <div>{data.description}</div>}
    </div>
);

const SkillContainer = ({ skill }) => {
    const [open, setOpen] = React.useState(false);
    const [data, setData] = React.useState({ skills: [] });

    const toggle = React.useCallback(() => setOpen(open => !open), []);

    useEffect(() => {
        const fetchData = async () => {
            const result = await         
                axios("http://localhost:5000/api/skills/");

        setData(result.data);
    }

    fetchData();
}, []);

return React.useMemo(() => Skill({ skill, open, toggle, data }), [open, 
skill, toggle, data]);
}

export default Skills;

Got to this point so far by reading the useHook documentations. What I want to do, is gather data with axios and then set the content with it.

You can use State in functional components like so:

 const Skills = ({ skills, loading }) => loading ? ( 'loading' ) : ( <div> {skills.map(skill => ( <SkillContainer key={skill._id} skill={skill} /> ))} </div> ) const Skill = ({ skill, open, toggle }) => ( <div> <h4 onClick={toggle}> skill: {skill.completed} {skill.id} </h4> {open && <div>{skill.title}</div>} </div> ) const SkillContainer = ({ skill }) => { const [open, setOpen] = React.useState(false) const toggle = React.useCallback( () => setOpen(open => !open), [] ) return React.useMemo( () => Skill({ skill, open, toggle }), [open, skill, toggle] ) } //savety to not set state when component is no longer mounted const useIsMounted = () => { const isMounted = React.useRef(false) React.useEffect(() => { isMounted.current = true return () => (isMounted.current = false) }, []) return isMounted } const SkillsContainer = () => { const [result, setResult] = React.useState({ loading: true, data: [] }) const isMounted = useIsMounted() React.useEffect(() => { const fetchData = () => { //cannot use async await here because Stack Overflow // uses old babel axios .get('https://jsonplaceholder.typicode.com/todos') .then(result => { if (isMounted.current) { //do not set data if component is no longer mounted setResult({ loading: false, data: result.data }) } }) } fetchData() }, [isMounted]) return React.useMemo( () => Skills({ skills: result.data, loading: result.loading }), [result] ) } //render app ReactDOM.render( <SkillsContainer />, document.getElementById('root') )
 <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <div id="root"></div>

The class version is a little more verbose:

 class SkillsContainer extends React.PureComponent { state = { loading: true, //should do error here as well result: [] } fetchData = ( length //arrow function auto bind to this ) => new Promise(r => setTimeout(() => r(length), 2000) ).then(l => this.setState({ loading: false, result: [...new Array(l)].map((_, i) => i+1) }) ) //if your skills array changes based on props: // example here: https://reactjs.org/docs/react-component.html#componentdidupdate componentDidUpdate(prevProps) { if (this.props.length !== prevProps.length) { this.fetchData(this.props.length) } } //fetch data on mount componentDidMount() { this.fetchData(this.props.length) } render() { return this.state.loading ? 'loading' : Skills({ skills: this.state.result }) } } const Skills = ({ skills }) => ( <div> {skills.map((skill, id) => ( <SkillContainer key={id} skill={skill} /> ))} </div> ) const Skill = ({ skill, open, toggle }) => ( <div> <h4 onClick={toggle}>skill: {skill}</h4> {open && <div>extra</div>} </div> ) class SkillContainer extends React.PureComponent { state = { open: false } toggle() { this.setState({ open: !this.state.open }) } render() { return Skill({ skill: this.props.skill, open: this.state.open, toggle: this.toggle.bind(this) }) } } //render app ReactDOM.render( <SkillsContainer length={2} />, document.getElementById('root') )
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <div id="root"></div>

The code uses conditional rendering but you can use props to set className as well

I would like to pass the skill body element ref to the Parent so that you can handle the style for the element. Hope the below code would done the thing which you need.

class Skill extends React.Component {

    render() {
        return (
            <div className="skill-card">
            <div className="skill-header">
                <div className="skill-header-icon">
                   <div onClick={() => this.props.click(this.skillBodyEle)}>Header Icon</div>
                </div>
            </div>
            <div
            ref={e => this.skillBodyEle = e}
            className="skill-body">
                Skill Body
            </div>
        </div>
            );
    }
}

class Skills extends React.Component {
    handleClick = (skillBodyEle) => {
        if (skillBodyEle.hidden) {
            skillBodyEle.hidden = false;
        } else {
            skillBodyEle.hidden = true;
        }
      };
      render() {
          return (
          <Skill 
            click={this.handleClick} 
          />
          );
      }
}

Inside of handleClick(), you can manipulate the DOM to make it work:

const ele = document.getElementsByClassName('skill-body')[0];

if (ele.visibility === 'hidden') {
  ele.visibility = 'visible';
}
else {
  ele.visibility = 'hidden';
}

However, I recommend using props/state like the other answers said in order to achieve your task.

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