admin管理员组

文章数量:1389887

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 ponent 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.

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 ponent 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.

Share Improve this question edited Mar 8, 2020 at 21:30 Timppa asked Mar 8, 2020 at 16:28 TimppaTimppa 3632 gold badges7 silver badges25 bronze badges 2
  • So in a list of Skills when you click on a certain button in a Skill you want that Skill to display extra info and when you click it again you want to hide it. In the list only one skill will ever show it's extra info or can there be more than one? – HMR Commented Mar 8, 2020 at 17:33
  • @MHR yes, exactly. there can be also more displayed at the same time, but for each ponent I want to be able to hide or show the information. – Timppa Commented Mar 8, 2020 at 17:56
Add a ment  | 

3 Answers 3

Reset to default 3

You can use State in functional ponents 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.pleted} {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 ponent 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./todos')
        .then(result => {
          if (isMounted.current) {
            //do not set data if ponent 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./ajax/libs/axios/0.19.2/axios.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./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/docs/react-ponent.html#ponentdidupdate
  ponentDidUpdate(prevProps) {
    if (this.props.length !== prevProps.length) {
      this.fetchData(this.props.length)
    }
  }
  //fetch data on mount
  ponentDidMount() {
    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./ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./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 remend using props/state like the other answers said in order to achieve your task.

本文标签: javascriptGet closest parent element by class name in ReactStack Overflow