how to access a component style in react js - javascript

I have a div tag I want to render only if renderCard() style overflow is scroll. I have tried a renderCard().style.overflow which does not seem to target this
Edit: renderCard added
const SearchCard = () => (
<button class="invisible-button" onClick={onSearchCardClick}>
//
</button>
);
const AnswerCard = () => (
<div className="results-set">
//
</div>
);
const renderCard = () => {
if (card && card.answer) {
return AnswerCard();
} else if (card) {
return SearchCard();
}
return null;
};
<React.Fragment>
<div id="search-results">{renderCard()}</div>
{renderFollowup ? null : (
<React.Fragment>
<div id="search-footer">
{
(renderCard().style.overflow = "scroll" ? (
<div className="scroll-button">
<a href="#bottomSection">
<img src="images/arrow_down.svg" alt="scroll to bottom" />
</a>
</div>
) : null)
}
</div>
</React.Fragment>
)}
</React.Fragment>
);
};

To do this you need a ref to the actual DOM element rendered by renderCard().
renderCard() here returns a React element which doesn't have the style property or any other DOM properties on it - it's just a React representation of what the DOM element will eventually be once rendered - hence you need to get the actual DOM element via a ref where you'll have access to this and other properties.
Example code below using useRef to create the ref that will be attached to the element with the style you need to access. Note how useEffect is used to access the ref's value because it's only available after the first render when the DOM element is present.
const Example = () => {
const ref = React.useRef()
React.useEffect(() => {
alert('overflow value is: ' + ref.current.style.overflow)
}, [])
return (
<div ref={ref} style={{ overflow: 'scroll' }}>hello world</div>
)
}
ReactDOM.render(<Example />, document.getElementById('root'))
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Refactor the cards to be actual rendered components, pass the ref and attach to the elements
const SearchCard = ({ overflowRef }) => (
<button ref={overflowRef} class="invisible-button" onClick={onSearchCardClick}>
//
</button>
);
const AnswerCard = ({ overflowRef }) => (
<div ref={overflowRef} className="results-set">
//
</div>
);
const RenderCard = ({ overflowRef }) => {
if (card && card.answer) {
return <AnswerCard overflowRef={overflowRef} />;
} else if (card) {
return <SearchCard overflowRef={overflowRef} />;
}
return null;
};
In the component rendering them, create a ref using either createRef or useRef react hook if it is a functional component
const overflowRef = createRef();
or
const overflowRef = useRef();
Pass the ref to RenderCard, to be passed on
<RenderCard overflowRef={overflowRef} />
And then check the overflow value as such
overflowRef.current.style.overflow === "scroll"

With the approach above you might want to refactor some of the HoC components and pass a function from parent to child that returns the ref of the element to be accessed also I am not sure of the scope of the block of code where you are executing the ternary.
Perhaps a simpler hack is to rely on using a useState hook with vanilla DOM selectors and passing that into the or even better, just add it to the stateless func component that wraps React.Fragment as a hook:
const myComponent = () => {
const [hasScrollOverflow, setHasScrollOverflow] = useState(false);
React.useEffect(() => {
const element = document.querySelector(".results-set");
const elementStyle = element.style;
if (elementStyle.getPropertyValue('overflow') === 'scroll') {
setHasScrollOverflow(true);
}
}, [])
return (
<React.Fragment>
<div id="search-results">{renderCard()}</div>
{renderFollowup ? null : (
<React.Fragment>
<div id="search-footer">
{hasScrollOverflow ? (
<div className="scroll-button">
<a href="#bottomSection">
<img src="images/arrow_down.svg" alt="scroll to bottom" />
</a>
</div>
) : null}
</div>
</React.Fragment>
)}
</React.Fragment>
);
};

Related

How can I dynamically update className with React js?

I have a Collection component and when I click on a div in this component, I want to change both the className of the Collection component and the className of the first sibling component after the Collection component.
With UseState, I could only change the className of the component I was in.
My Collection.js looks like this:
const Collection = () => {
const [toggleClass, setToggleClass] = useState(false);
function handleClick() {
setToggleClass((toggleClass) => !toggleClass);
}
let toggleClassCheck = toggleClass ? "passive" : "active";
return (
<React.Fragment>
<div className={`step ${toggleClassCheck}`}>
<div className="atrium">
<span>Collection</span>
</div>
<div className="content">
<div
className="moreThenOne"
area="collectionTitle"
onClick={handleClick}
>
Can anyone help me on how to do the process I mentioned above?
I didn't quite understand what you want, but if you want to impact sibling of component "Collection" executing function inside of "Collection" you definitely should try "ref". Via useRef hook.
export const Collection = () => {
const [toggleClass, setToggleClass] = useState(false);
const divRef = useRef(null);
function handleClick() {
divRef.current.nextSibling.classList.toggle('active');
divRef.current.nextSibling.classList.toggle('passive');
setToggleClass((toggleClass) => !toggleClass);
}
let toggleClassCheck = toggleClass ? 'passive' : 'active';
return (
<>
<div className={`step ${toggleClassCheck}`} ref={divRef}>
<div className="atrium">
<span>Collection</span>
</div>
<div className="content">
<div className="moreThenOne" onClick={handleClick}>
CLICK ZONE
</div>
</div>
</div>
</>
);
};
The React components rerender, based on the state. So you sould use your state to define the className instead of a boolean.
When your state changes the component will re-render.
Try this:
const Collection = () => {
const [toggleClass, setToggleClass] = useState("active");
function handleClick() {
setToggleClass((prevState) => prevState == "active" ? "passive" : "active" );
}
return (
<React.Fragment>
<div className={`step ${toggleClass}`}>
<div className="atrium">
<span>Collection</span>
</div>
<div className="content">
<div
className="moreThenOne"
area="collectionTitle"
onClick={handleClick}
>

getBoundingClientRect() on two React components and check if they overlap onScroll

I want to get a ref, more specifically a getBoundingClientRect() on the <Header/> and <Testimonials/> component. I then want to watch for a scroll event and check if the two components ever overlap. Currently, my overlap variable never flips to true even if what appears on the page is that the two components are overlaping.
const [isIntersecting, setIsIntersecting] = useState(false)
const header = useRef(null)
const testimonials = useRef(null)
const scrollHandler = _ => {
let headerRect = header.current.getBoundingClientRect();
let testiRect = testimonials.current.getBoundingClientRect();
let overlap = !(headerRect.right < testiRect.left ||
headerRect.left > testiRect.right ||
headerRect.bottom < testiRect.top ||
headerRect.top > testiRect.bottom)
console.log(overlap) // never flips to true
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler, true);
return () => {
window.removeEventListener("scroll", scrollHandler, true);
};
}, []);
const App = () => {
return (
<div className="App">
<Header />
<LandingPage />
<div style={{ height: '100vh', backgroundColor: 'black', color: 'white' }}>
</div>
<AboutPage />
<TestimonialsPage />
<Footer />
</div>
);
}
First: Components can't receive directly a ref prop, unless you are wrapping the Component itself in a React.forwardRef wrapper:
const Component = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// Inside your Parent Component:
const ref = useRef();
<Component ref={ref}>Click me!</Component>;
Second: you can also pass a ref down to a child as a standard prop, but you can't call that prop ref since that's a special reserved word just like the key prop:
const Component= (props) => (
<button ref={props.myRef}>
{props.children}
</button>
);
// Inside your Parent Component
const ref = useRef();
<Component myRef={ref}>Click me!</Component>;
This works perfectly fine, and if it's a your personal project you
might work like this with no issues, the only downside is that you
have to use custom prop name for those refs, so the code gets harder to
read and to mantain, especially if it's a shared repo.
Third: Now that you learnt how to gain access to the DOM node of a child Component from its parent, you must know that even if usually it's safe to perform manipulations on those nodes inside a useEffect ( or a componentDidMount ) since they are executed once the DOM has rendered, to be 100% sure you will have access to the right DOM node it's always better using a callback as a ref like this:
const handleRef = (node) => {
if (node) //do something with node
};
<Component ref={handleRef}/>
Basically your function hanldeRef will be called by React during
DOM node render by passing the node itself as its first parameter,
this way you can perform a safe check on the node, and be sure it's
100% valorized when you are going to perform your DOM manipulation.
Concerning your specific question about how to access the getBoundingClientRect of a child Component DOM node, I made a working example with both the approaches:
https://stackblitz.com/edit/react-pqujuz
You'll need to define each of your components as Forwarding Refs, eg
const Header = forwardRef<HTMLElement>((_, ref) => (
<header ref={ref}>
<h1>I am the header</h1>
</header>
));
You can then pass a HTMLElement ref to your components to refer to later
const headerRef = useRef<HTMLElement>(null);
const scrollHandler = () => {
console.log("header position", headerRef.current?.getBoundingClientRect());
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler);
return () => {
window.removeEventListener("scroll", scrollHandler);
};
}, []);
return (
<Header ref={headerRef} />
);
I'm using TypeScript examples since it's easier to translate back down to JS than it is to go up to TS

How to focus an input in React via refs

I'm using React JS and have faced a problem.
I have a component on the page which has some inputs. When user clicks on any input a new block should be created below and the same input has to be focused at the same time.
Everything worked until I've created a show logic:
const readyBlock = isTouched ? <ViewModule textInput={textInput}/> : null;
After that I get ×
TypeError: Cannot read property 'focus' of null
Below is my Main component where everything on the page happens.
const Sales = () => {
const [isTouched, setIsTouched] = useState(false);
const textInput = useRef(null);
function handleInput() {
setIsTouched(true);
textInput.current.focus();
}
const readyBlock = isTouched ? <ViewModule textInput={textInput}/> : null;
return (
<main className="sales-page">
<div className="main__title">
<h2 className="main__heading">Bonuses</h2>
</div>
<div className="content-container">
<UploadForm>
<FileUploadInput
handleChange={handleInput}
placeholder="Header"/>
<FileUploadTextArea placeholder="Descr"/>
</UploadForm>
</div>
<div className="ready-container ">
{readyBlock}
</div>
</main>
)
}
const ViewModule = ({textInput}) => {
return (
<UploadForm classNames="textarea-written">
<FileUploadInput
ref={textInput}
placeholder="Заголовок"/>
<FileUploadTextArea placeholder="Descr"/>
<div className="btn-container">
<Btn classNames="cancel-btn">Cancel</Btn>
<Btn>Save</Btn>
</div>
</UploadForm>
)
}
Below is an input component:
const FileUploadInput = React.forwardRef((props, ref) => {
return (
<div className="text-input-wrapper">
<input
ref={ref}
type="text"
id="file-text-input"
name="file__upload-title"
placeholder={props.placeholder}
onClick={props.handleChange} />
</div>
)
});
I assume you want to focus the ViewModule component when its added.
The problem is that the Ref textInput is not assigned to any component before the ViewModule is added to the DOM tree. You would have to first add the ViewModule to the DOM tree on state change and then later in a useEffect hook you will find the textInput Ref properly assigned.
function handleInput() {
setIsTouched(true);
}
useEffect(() => {
if (textInput.current === null) return
if (isTouched) textInput.current.focus();
}, [isTouched])
Also you should pass the textInput Ref to ViewModule using React.forwardRef as you did for FileUploadInput.
const ViewModule = React.forwardRef((ref) => {...});
And use it like this.
const readyBlock = isTouched ? <ViewModule ref={textInput}/> : null;

How to mount child component outside parent using React?

I know that there is React portals that may solves the problem, but portals mount child components outside of DOM tree if I understand it correctly. But I need to render child component inside DOM tree but just outside the parent. Here the example.
I have a page:
const Page = () => {
return (
<>
// -> the place to mount <Child_2/> <--
<Parent/>
</>)
I have the Parent:
const Parent = () => {
return (
<>
<Child_1/>
<Child_2/> //<- I need it NOT to mount here but outside the parent
// in the <Page> component and not outside the DOM.
</>
)
How can I do it? And yet can I make it by portal?
In React doc I found an example just for case:
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
But it is not my case...
const Page = ()=> {
const [el, setEl] = React.useState(null);
return (
<>
<div ref={setEl}></div>
{el && <Parent el={el} />}
</>
)
}
const Parent = ({el})=> {
React.useEffect();
return (
<>
<Child_1/>
{ReactDOM.createPortal(<Child2 />,el)}
</>
)
}

Using querySelectorAll on a React component's children

I am trying to render a list of elements lazily by initially rendering only what's in view and then replacing placeholder elements with the real thing as you scroll down using an IntersectionObserver. This keeps the list's length from changing as I add new elements and is much cheaper to render as I'm only listing empty divs when the page loads. Like a poor man's virtualization.
The issue:
The parent element adds its children to the IO as so:
useEffect(() => {
if (!observer.current) return
const els = [...document.querySelectorAll(`.list > :nth-child(n + 10})`)]
els.forEach(el => observer.current.observe(el))
}, [list])
els does not always find elements as React renders its elements as it sees fit. The problem is that I don't know how I could do this using ref. Using context I may be able to do this but I'd imagine there would be constant rerendering of the entire list.
I hope this example can help you understand how to use Refs in your case. I recommend you to read React Docs about Refs.
Please let me know if you have any doubt.
const MyItem = React.forwardRef((props, ref) => {
return (<div ref={ref}>{'ITEM ' + props.index}</div>);
});
const MyList = () => {
const [list, setList] = React.useState([]);
const refsMap = React.useRef(new Map());
const onClickHandler = React.useCallback(() => {
setList((previousList) => {
const key = previousList.length;
return ([
...previousList,
<MyItem key={key} index={key} ref={(ref) => {
if (ref) {
refsMap.current.set(key, ref);
} else {
refsMap.current.delete(key);
}
console.log(refsMap.current.get(key));
}} />
]);
});
}, []);
return (
<div>
<div>
<button type={'button'} onClick={onClickHandler}>{'ADD'}</button>
</div>
<div>
{list}
</div>
</div>
);
};
function App() {
return (
<MyList />
);
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Categories

Resources