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.
- 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
3 Answers
Reset to default 3You 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.