In ReactJS, how can I assign multiple ref to a single DOM? - reactjs

I have a dialogue component in which I have created a ref, but I also want to pass the ref from the parent to it. How can I do this?
import { forwardRef } from "react";
export const PopOver = ({
show = false,
...
}, ref) => {
const thisRef = useRef(null);
// dealing with UI changes with 'thisRef'
return (
<div
ref={thisRef}, // How can I add a `ref` here?
....
>
Hello world
</div>
);
};
export default forwardRef(PopOver);

You can wrap it with another element and pass parent ref to it
sth like this :
<div ref={ref}>
<div
ref={thisRef}
....
>
Hello world
</div>
</div>
or if you want to replace ref you can do it in useEffect
like this :
useEffect(()=>{
if(ref.current){
thisRef.current = ref.current
}
},[ref])

Related

How do you reference a div from a separate JSX file and send it to another JSX file

So I have app.jsx which calls up all my other JSX files.
This way I have each component organized in its own file.
I would like to navigate to each section of the website (all on one page just different areas). The only issue is I can't seem to reference where
I want to go to. I thought if I referenced the imported jsx file it would get its DOM elements. But that's not working. What seems to work is if I have a div directly being referenced in the same App.jsx file. But I need to get the child div element.
function App() {
const aboutmeOnClickRef = useRef(null);
const portfolioOnClickRef = useRef(null);
const employmentOnClickRef = useRef(null);
const educationOnClickRef = useRef(null);
const letschatOnClickRef = useRef(null);
return (
<div className="app">
<Topbar
aboutmeOnClickRef={aboutmeOnClickRef}
portfolioOnClickRef={portfolioOnClickRef}
employmentOnClickRef={employmentOnClickRef}
educationOnClickRef={educationOnClickRef}
letschatOnClickRef={letschatOnClickRef}
/>
<div className="sections">
<Intro />
<DisplayAboutMe ref={aboutmeOnClickRef} />
<Portfolio ref={portfolioOnClickRef} />
<Employment ref={employmentOnClickRef} />
<Education ref={educationOnClickRef} />
<LetsChat ref={letschatOnClickRef} />
</div>
</div>
);
}
//This is what I want to do, but doesn't work
//const DisplayAboutMe = React.forwardRef((props, ref) => <AboutMe ref={ref} />);
//This one works but creates a whole new div and doesn't get my jsx file
const DisplayAboutMe = React.forwardRef((props, ref) => <div ref={ref} />);
AboutMe.jsx
import React from "react";
import styles from "./aboutme.module.scss";
export default function AboutMe() {
return (
<div className={styles.aboutme} id={styles.aboutme}></div>
);
}
By default, refs to function components don't do anything. You need to use forwardRef to tell react which dom element you want the ref to point to. Currently, your about me component doesn't do this. Here's what it might look like:
import React from "react";
import styles from "./aboutme.module.scss";
const AboutMe = React.forwardRef((props, ref) => {
return (
<div ref={ref} className={styles.aboutme} id={styles.aboutme}></div>
);
});
export default AboutMe
With the above change, you shouldn't need DisplayAboutMe. App can render an <AboutMe> and pass in a ref, and the ref will be assigned the div that's inside AboutMe.
function App() {
const aboutmeOnClickRef = useRef(null);
// ...
<div className="sections">
<Intro />
<AboutMe ref={aboutmeOnClickRef} />
</div>
// ...
}
// In topBar:
function TopBar(props) {
return (
// ...
<a onClick={() => { props.aboutMeOnClickRef.current.scrollIntoView() }}/>
// ...
)
}

Re-Rendering a component

I'm doing a simple todo list using React. What I fail to do is to remove an item once I click on the button.
However, if I click delete and then add a new item, it's working, but only if I add a new todo.
Edit:I've edited the post and added the parent componenet of AddMission.
import React,{useState}from 'react';
import { Button } from '../UI/Button/Button';
import Card from '../UI/Card/Card';
import classes from '../toDo/AddMission.module.css'
const AddMission = (props) => {
const [done,setDone]=useState(true);
const doneHandler=(m)=>{
m.isDeleted=true;
}
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li className={mission.isDeleted?classes.done:''} key={mission.id}>
{mission.mission1}
<div className={classes.btn2}>
<Button onClick={()=>{
doneHandler(mission)
}} className={classes.btn}>Done</Button>
</div>
</li>
)) }
</ul>
</Card>
);
};
export default AddMission;
import './App.css';
import React,{useState} from 'react';
import { Mission } from './components/toDo/Mission';
import AddMission from './components/toDo/AddMission';
function App() {
const [mission,setMission]=useState([]);
const [isEmpty,setIsEmpty]=useState(true);
const addMissionHandler = (miss) =>{
setIsEmpty(false);
setMission((prevMission)=>{
return[
...prevMission,
{mission1:miss,isDeleted:false,id:Math.random().toString()},
];
});
};
return (
<div className="">
<div className="App">
<Mission onAddMission={addMissionHandler}/>
{isEmpty?<h1 className="header-title">Start Your Day!</h1>:(<AddMission isVisible={mission.isDeleted} missions={mission}/>)}
</div>
</div>
);
}
const doneHandler=(m)=>{
m.isDeleted=true;
}
This is what is causing your issue, you are mutating an object directly instead of moving this edit up into the parent. In react we don't directly mutate objects because it causes side-effects such as the issue you are having, a component should only re-render when its props change and in your case you aren't changing missions, you are only changing a single object you passed in to your handler.
Because you haven't included the code which is passing in the missions props, I can't give you a very specific solution, but you need to pass something like an onChange prop into <AddMission /> so that you can pass your edited mission back.
You will also need to change your function to something like this...
const doneHandler = (m) =>{
props.onChange({
...m,
isDeleted: true,
});
}
And in your parent component you'll then need to edit the missions variable so when it is passed back in a proper re-render is called with the changed data.
Like others have mentioned it is because you are not changing any state, react will only re-render once state has been modified.
Perhaps you could do something like the below and create an array that logs all of the ids of the done missions?
I'm suggesting that way as it looks like you are styling the list items to look done, rather than filtering them out before mapping.
import React, { useState } from "react";
import { Button } from "../UI/Button/Button";
import Card from "../UI/Card/Card";
import classes from "../toDo/AddMission.module.css";
const AddMission = (props) => {
const [doneMissions, setDoneMissions] = useState([]);
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li
className={
doneMissions.includes(mission.id)
? classes.done
: ""
}
key={mission.id}
>
{mission.mission1}
<div className={classes.btn2}>
<Button
onClick={() => {
setDoneMissions((prevState) => {
return [...prevState, mission.id];
});
}}
className={classes.btn}
>
Done
</Button>
</div>
</li>
))}
</ul>
</Card>
);
};
export default AddMission;
Hope that helps a bit!
m.isDeleted = true;
m is mutated, so React has no way of knowing that the state has changed.
Pass a function as a prop from the parent component that allows you to update the missions state.
<Button
onClick={() => {
props.deleteMission(mission.id);
}}
className={classes.btn}
>
Done
</Button>;
In the parent component:
const deleteMission = (missionId) => {
setMissions(prevMissions => prevMissions.map(mission => mission.id === missionId ? {...mission, isDeleted: true} : mission))
}
<AddMission missions={mission} deleteMission={deleteMission} />

can we able to specify on click on the component definition itself?

I want to create a component(comp) with onclick event handler. which should be handled by the component itself (i.e. inside the comp.js file).
if I use it inside the parent component we don't need to specify the event but it is handled by the component element(comp element).
is this possible. Any idea to develop this one.
in ParentComponent.js current behavior.
<NewComponent onClick={clickBehaviour}/>
I want like,
In NewComponent.js
const NewComponent.js = ()=>{
// Some code
const clickBehaviour = () =>{
// click behaviour
}
}
Is it possible in the current standards?
why you want to write your onClick event in parent component?
you can do it inside NewComponent.js easily.
just do this:
import React from 'react'
function NewComponent() {
const clickBehaviour = () =>{
// click behaviour
}
return (
<div onClick={clickBehaviour}>
//some jsx here
</div>
)
}
export default NewComponent
and use in anywhere you want to use without onClick event :
< NewComponent />
i cant understand well you situation but you can use forwardRef if you want (also can use old getElementById but using forwardRef is recommended).
import React, { useRef } from "react";
const NewComponent = React.forwardRef((props, ref) => (
<div onClick={() => alert("div 2 clicked")} ref={ref}>
div 2
</div>
));
export default function App() {
const compRef = useRef(null);
return (
<div>
<NewComponent ref={compRef} onClick={() => {
compRef && compRef.current && compRef.current.click();
}} />
</div>
);
}

React how to add class to parent element when child element is clicked

This is my code.
export default MainContent = () => {
handleClick = (e) => {
// This is where I got confused
};
return (
<>
<div>
<div onClick={handleClick}>1</div>
</div>
<div>
<div onClick={handleClick}>2</div>
</div>
<div>
<div onClick={handleClick}>3</div>
</div>
</>
);
}
What I want is to add a class to parent div when child element is clicked. I couldn't use useState() since I only need one element to update. Couldn't use setAttribute since it changes the same element. Is there any solution for that?
I take it you want to apply the class only to direct parent of clicked child.
create a state to oversee different clicked child div
apply the class only to direct parent of clicked* child div based on the state
make use of clsx npm package (since we don't wanna overwrite parent div styling)
you may see the working examples here: https://codesandbox.io/s/happy-babbage-3eczt
import { useState } from "react";
import "./styles.css";
import styled from "styled-components";
import classnames from "clsx";
export default function App() {
const [styling, setstyling] = useState({
status: false,
from: "",
style: ""
});
function handleClick(childNo) {
setstyling({ status: true, from: childNo, style: "applyBgColor" });
}
return (
<div className="App">
<Styling>
<div
className={
styling?.status && styling?.from == "child-1"
? classnames("indentText", styling?.style)
: "indentText"
}
>
<div
className="whenHoverPointer"
onClick={() => handleClick(`child-1`)}
>1</div>
</div>
<div
className={
styling?.status && styling?.from == "child-2"
? styling?.style
: ""
}
>
<div
className="whenHoverPointer"
onClick={() => handleClick(`child-2`)}
>2</div>
</div>
</Styling>
</div>
);
}
const Styling = styled.div`
.indentText {
font-style: italic;
}
.applyBgColor {
background-color: grey;
}
.whenHoverPointer {
cursor: pointer;
}
`;
function Item({ children }) {
const [checked, isChecked] = useState(false);
const onClick = () => isChecked(true);
return (
<div {...(isChecked && { className: 'foo' })}>
<button type="button" onClick={onClick}>{children}</button>
</div>
);
}
function MainContent() {
return [1, 2, 3].map(n => <Item key={n}>{n}</Item>);
}
I think theirs something wrong, useState and JSX will update related part, react will handling that itself, but base on logic, may you need to prevent re-render to stop issue, for example, here on handleClick, will keep re-render in each state since function will re-addressed in memory each update..
any way, base on your question, you can do that by this:
const handleClick = useCallback((e) => {
e.target.parentElement.classList.add('yourClass')
}, []);
But I believe its a Bad solution.
What I recommend is solve issue by state to keep your react life cycle is fully work and listen to any update, also you can use ref to your wrapper div and add class by ref.

Accessing HTML elements inside a React component

I have a React component InputComponent which I cannot edit, and I would like to get a reference to one of its inner divs. (for example for the purpose of focusing on the input field).
const RefsExamplePage = () => {
return (
<div>
<div>
<InputComponent
title="Test component"
></InputComponent>
</div>
</div>
)
}
export default RefsExamplePage;
How do I achieve this?
which I cannot edit
If you can't edit it, the only thing you can do is pass ref to it and hope the InputComponent have refs implemented.
e.g.
const RefsExamplePage = () => {
// use inputRef.current to access the input reference
const inputRef = React.useRef()
return (
<div>
<div>
<InputComponent
ref={inputRef}
title="Test component"
/>
</div>
</div>
)
}
If this doesn't work or give you some error, you will need to modify the InputComponent
If InputComponent doesn't provide ref you can wrap its parent (the div container) then set ref for it:
import React, { useRef } from "react";
const RefsExamplePage = () => {
const container = useRef();
return (
<div>
<div ref={container}>
<InputComponent
title="Test component"
></InputComponent>
</div>
</div>
)
}
export default RefsExamplePage;
Then you can access the child element through the div's ref.
Use useRef() to create a ref to the component itself. This way you can get the components reference and you can use .current property of it to get the underlying DOM:
const RefsExamplePage = () => {
const inputRef = useRef();
const getInput = e => {
// here get the any dom node available
inputRef.current.querySelector('input').focus();
};
return (....
<InputComponent
ref={inputRef}
onClick={getInput}
title="Test component"/> // <---if no child are passed change to self closing
....)
}

Resources