Получить ближайший родительский элемент по имени класса в React - PullRequest
1 голос
/ 08 марта 2020

Используя React, как мне обратиться к ближайшему элементу родительского элемента с определенным именем класса?

Код ниже:

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!
    }
}

В этом случае, когда я нажимаю FontAwesomeIcon, я хочу, чтобы открылся элемент div с className skill-body.

Сначала я подумал, что использование состояний было бы хорошо, но, безусловно, все элементы skill-body откроются после нажатия FontAwesomeIcon. Мой подход заключается в добавлении стилей к skill-body (display: block и display: none).

Как я могу ссылаться на skill-body внутри функции Component с помощью handleClick?

РЕДАКТИРОВАТЬ: Использовал пример функционального компонента HMR, и я все еще застрял.

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;

До сих пор, прочитав документацию useHook. То, что я хочу сделать, это собрать данные с помощью axios, а затем установить содержимое с ним.

Ответы [ 3 ]

2 голосов
/ 08 марта 2020

Вы можете использовать State в таких функциональных компонентах, как:

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>

Версия класса немного более многословна:

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>

Код использует условный рендеринг, но вы можете также использовать реквизиты для установки className

0 голосов
/ 08 марта 2020

Внутри handleClick (), вы можете манипулировать DOM, чтобы он работал:

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

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

Однако я рекомендую использовать props / state, как и другие ответы, сказанные для достижения вашей задачи.

0 голосов
/ 08 марта 2020

Я хотел бы передать ссылку на элемент тела навыка Родителю, чтобы вы могли обработать стиль для элемента. Надеюсь, что приведенный ниже код сделает то, что вам нужно.

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