问题
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.
回答1:
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
回答2:
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}
/>
);
}
}
回答3:
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.
来源:https://stackoverflow.com/questions/60589579/get-closest-parent-element-by-class-name-in-react