React state doesnt update, drilling seems ok though - reactjs

I've got a modal that I want to be able to auto-shut itself using a drilled function. The console.log does work, but the state isn't actually updating. What am I doing wrong? Triggering the state via the dev tools works fine, so it's not the state itself. Is drilling within a component the problem?
index.js:
export default function Home() {
const [modalOpen, setModalOpen] = useState(false)
const handleModalOpen = () => {
console.log ("Setting modal to true")
setModalOpen (true)
}
const handleModalClose = () => {
console.log ("Setting modal to false")
setModalOpen (false)
}
// all the normal app body
{modalOpen ?
(<Modal handleModalClose={handleModalClose} height='30vh'>
<h4>Thank you for your contact request.</h4>
<h4>Keep in mind that this is only a demo website, not an actual business.</h4>
</Modal>): null}
</div>
)
}
Modal.js:
import { createPortal } from "react-dom";
import { useEffect, useState } from "react";
import styles from '../styles/Modal.module.css'
const Backdrop = (props) => {
return <div onClick={() => props.handleModalClose()} className={styles.backdrop} />
}
const Message = (props) => {
let width = '70vw'
let height = '80vh'
if (props.width) width = props.width
if (props.height) height = props.height
return (
<div style={{ width: width, height: height }} className={styles.message}>
{props.children}
</div>
)
}
const Modal = (props) => {
const [backdropDiv, setBackdropDiv] = useState(null)
const [modalDiv, setModalDiv] = useState(null)
useEffect(() => {
if (typeof (window) !== undefined) {
let backdropDiv = document.getElementById('backdrop')
setBackdropDiv(backdropDiv)
let modalDiv = document.getElementById('modal')
setModalDiv(modalDiv)
}
}, [])
return (
<>
{backdropDiv !== null && modalDiv !== null ? (
<>
{createPortal(<Backdrop handleModalClose = {props.handleModalClose} />, backdropDiv)}
{createPortal(<Message children={props.children} width={props.width} height={props.height} />, modalDiv)}
</>
) : null
}
</>
)
}
export default Modal

Related

useState set to string not working in Reactjs

I have this code that controls the behavior of what to map from an array onClick. The useState is set to a string const [activeFilter, setActiveFilter] = useState('All'); that is supposed to automatically filter all products containing the string as tag but it doesn't do this automatically and I can't figure out why. Please help with code below.
index.js
import React, { useEffect, useState } from 'react'
import {client} from '../lib/client'
import { Product, FooterBanner, HeroBanner } from '../components'
const Home = ({products, bannerData}) => {
const [productItems, setProductItems] = useState([])
const [filterWork, setFilterWork] = useState([]);
const [activeFilter, setActiveFilter] = useState('All');
useEffect(() => {
setProductItems(products)
}, [])
const handleProductFilter = (item) => {
setActiveFilter(item)
setTimeout(() => {
if (item == 'All'){
setFilterWork(productItems)
}else{
setFilterWork(productItems.filter((productItem)=> productItem.tags.includes(item)))
}
}, 500)
}
return (
<>
<HeroBanner heroBanner={bannerData.length && bannerData[0]} />
<div className='products-heading'>
<h2>Best Selling Products</h2>
<p>Smoke accessories of many variations</p>
</div>
<div className='product_filter'>
{['Lighter', 'Pipe', 'Roller', 'Hookah', 'All'].map((item, index) => (
<div
key={index}
className={`product_filter-item app__flex p-text ${activeFilter === item ? 'item-active' : ''}`}
onClick={() => handleProductFilter(item)}
>
{item}
</div>
))}
</div>
<div className='products-container'>
{
filterWork.map((product) => <Product key={product._id} product={product} />)
}
</div>
<FooterBanner footerBanner={bannerData && bannerData[0]} />
</>
)
};
export const getServerSideProps = async () => {
const query = '*[_type == "product"]'
const products = await client.fetch(query)
const bannerQuery = '*[_type == "banner"]'
const bannerData = await client.fetch(bannerQuery)
return {
props: {products, bannerData}
}
}
export default Home
The image below is what it looks like on load and the only time All products containing 'All' tags are visible is when the All button is clicked on again, regardless of it being active initially
No products are being displayed initially when the component renders because the displayed products are loaded from the filterWork state that is only set once an onClick event is triggered. To fix this you can simply set the initial products in the useEffect because you are starting with all the products being displayed.
useEffect(() => {
setProductItems(products);
setFilterWork(products);
}, [])

why useRef current value , isn't sharing trough custom hook?

I wanted to calculate the user scroll height , so I created a custom hook. and I wanted to share this value to another component. but it doesnt work.
code:
const useScroll = () => {
let scrollHeight = useRef(0);
const scroll = () => {
scrollHeight.current =
window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body)
.scrollTop;
};
useEffect(() => {
window.addEventListener("scroll", scroll);
return () => {
window.removeEventListener("scroll", () => {});
};
}, []);
return scrollHeight.current;
};
export default useScroll;
the value is not updating here.
but if I use useState here , it works. but that causes tremendous amount of component re-rendering. can you have any idea , how its happening?
Since the hook won't rerender you will only get the return value once. What you can do, is to create a useRef-const in the useScroll hook. The useScroll hook returns the reference of the useRef-const when the hook gets mounted. Because it's a reference you can write the changes in the useScroll hook to the useRef-const and read it's newest value in a component which implemented the hook. To reduce multiple event listeners you should implement the hook once in the parent component and pass the useRef-const reference to the child components. I made an example for you.
The hook:
import { useCallback, useEffect, useRef } from "react";
export const useScroll = () => {
const userScrollHeight = useRef(0);
const scroll = useCallback(() => {
userScrollHeight.current =
window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body)
.scrollTop;
}, []);
useEffect(() => {
window.addEventListener("scroll", scroll);
return () => {
window.removeEventListener("scroll", scroll);
};
}, []);
return userScrollHeight;
};
The parent component:
import { SomeChild, SomeOtherChild } from "./SomeChildren";
import { useScroll } from "./ScrollHook";
const App = () => {
const userScrollHeight = useScroll();
return (
<div>
<SomeChild userScrollHeight={userScrollHeight} />
<SomeOtherChild userScrollHeight={userScrollHeight} />
</div>
);
};
export default App;
The child components:
export const SomeChild = ({ userScrollHeight }) => {
const someButtonClickHandlerWhichPrintsUserScrollHeight = () => {
console.log("userScrollHeight from SomeChild", userScrollHeight.current);
};
return (
<div style={{
width: "100vw",
height: "100vh",
backgroundColor: "aqua"
}}>
<h1>SomeChild 1</h1>
<button onClick={() => someButtonClickHandlerWhichPrintsUserScrollHeight()}>Console.log userScrollHeight</button>
</div>
);
};
export const SomeOtherChild = ({ userScrollHeight }) => {
const someButtonClickHandlerWhichPrintsUserScrollHeight = () => {
console.log("userScrollHeight from SomeOtherChild", userScrollHeight.current);
};
return (
<div style={{
width: "100vw",
height: "100vh",
backgroundColor: "orange"
}}>
<h1>SomeOtherChild 1</h1>
<button onClick={() => someButtonClickHandlerWhichPrintsUserScrollHeight()}>Console.log userScrollHeight</button>
</div>
);
};
import { useRef } from 'react';
import throttle from 'lodash.throttle';
/**
* Hook to return the throttled function
* #param fn function to throttl
* #param delay throttl delay
*/
const useThrottle = (fn, delay = 500) => {
// https://stackoverflow.com/a/64856090/11667949
const throttledFn = useRef(throttle(fn, delay)).current;
return throttledFn;
};
export default useThrottle;
then, in your custom hook:
const scroll = () => {
scrollHeight.current =
window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body)
.scrollTop;
};
const throttledScroll = useThrottle(scroll)
Also, I like to point out that you are not clearing your effect. You should be:
useEffect(() => {
window.addEventListener("scroll", throttledScroll);
return () => {
window.removeEventListener("scroll", throttledScroll); // remove Listener
};
}, [throttledScroll]); // this will never change, but it is good to add it here. (We've also cleaned up effect)

Unit test a component that behaves according to its height using jest

I have below component that shows a 'Show more' button if the content inside increases a certain number of lines and on click of that button the div will expand and show rest of the content. The functionality works perfectly fine, But unable to cover the 'Show more' button scenarios as the button is not rendered on running test. I guess it is because of the height evaluation in the logic. I Could see the content renders in console but without show-more button. Any help is appreciated.
import styles from './ShowMore.module.scss';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import React, { useEffect, useState, useRef } from 'react';
import { faAngleDown, faAngleUp } from '#fortawesome/pro-regular-svg-icons';
import { IShowMore } from '../../interfaces/ShowMore.model'
const ShowMore: React.FunctionComponent<IShowMore> = (props) => {
const [showMore, setShowMore] = useState<boolean>(false);
const [boxHeight, setBoxHeight] = useState<string>('auto');
const [showButton, setshowButton] = useState<boolean>(false);
const [numberOfLines, setNumberOfLines] = useState<number>(props.numberOfLines);
const ref = useRef<HTMLDivElement>(null);
const getLineheight: Function = (el:HTMLDivElement) => {
return parseInt(window?.getComputedStyle(el, null)
.getPropertyValue('line-height')
.replace('px', ''));
}
const isMoreLines: Function = () => {
if (!ref?.current) return true;
const el = ref?.current;
const lines: number = Math.floor(el?.offsetHeight / getLineheight(el));
return lines > numberOfLines ? true : false;
}
useEffect(() => {
setShowMore(false);
const newHeight = isMoreLines() ? `${ getLineheight(ref?.current) * numberOfLines }px` : 'auto';
setBoxHeight(newHeight);
setshowButton(isMoreLines());
}, [props.selectedTab]);
return (
<>
<div
ref={ref}
className={styles['show-more-pane']}
data-expanded={showMore ? 'true' : 'false'}
data-showmore={showButton ? 'enabled' : 'disabled'}
style={{ height: showMore ? '' : boxHeight }}
data-tesid="show-more-pane">
{ props.children || <div dangerouslySetInnerHTML={{ __html: props.tabContent! }}></div>}
</div>
{showButton && <button
className={styles['show-more-btn']}
data-expanded={showMore ? 'true' : 'false'}
data-showmore={showButton ? 'enabled' : 'disabled'}
data-testid='show-more-btn'
onClick={() => setShowMore(!showMore)}>
{showMore ? 'Show Less' : 'Show More'}
<FontAwesomeIcon className='show-more-icon' icon={showMore ? faAngleUp : faAngleDown} />
</button>}
</>
);
}
export default ShowMore;
I tried several solutions from various sources, none of them worked. Pasting here a basic and simple one i tried initially.
import { screen, render, waitFor } from '#testing-library/react';
import { ProductDetailsResponseMock } from '../../mocks/CatalogBrowse.mock';
import ShowMore from './ShowMore';
const { longDescription } = ProductDetailsResponseMock.details;
describe('Show More Component', () => {
it('Should render show more comp', () => {
const showMoreComp = render(<ShowMore selectedTab='featureBenefits' numberOfLines={2} tabContent={longDescription.value} />);
const showMoreEl = async () => { await showMoreComp.getByTestId('show-more-pane')};
waitFor(()=>{
expect(showMoreEl).toBeInTheDocument();
})
});
it('Should render show more comp', async () => {
const showMoreComp = render(<ShowMore selectedTab='featureBenefits' numberOfLines={2} tabContent={longDescription.value} />);
await waitFor(()=>{
expect(screen.queryByTestId('show-more-pane')).toBeInTheDocument();
})
});
});

Opening a new window without an address bar in react js

I want to have a pop up window without an address bar.. I saw similar posts but it didn't solve my problem.. However I saw a video on youtube, he entered this on windows command run chrome.exe --app=https://google.com. The window output has no address bar. Can someone please teach me how can I put this on my code in react js..
I already build my component here it is:
import React, { useEffect, useRef, useState } from "react";
import { Button } from '#zendeskgarden/react-buttons';
import { createPortal } from "react-dom";
import UpdateRepair from './UpdateRepair';
import './App.css';
const RenderInWindow = (props) => {
const [container, setContainer] = useState(null);
const newWindow = useRef(window);
useEffect(() => {
const div = document.createElement("div");
setContainer(div);
}, []);
useEffect(() => {
if (container) {
newWindow.current = window.open(
"",
"Repair Progress",
"directories=0,titlebar=0,toolbar=no,menubar=no,location=no,width=600,height=400,left=200,top=200"
);
newWindow.current.document.body.appendChild(container);
const curWindow = newWindow.current;
return () => curWindow.close();
}
}, [container]);
return container && createPortal(props.children, container);
};
export default function RepairWindowButton(props) {
const [open, setOpen] = useState(false);
console.log(props)
return (
<>
{(open === false) ?
<Button className = "button" onClick={() => setOpen(true)}>Open in new window</Button>
:
<Button className="button" onClick={() => setOpen(false)}>Open in new window</Button>
}
{open && <RenderInWindow>
{props === [] ? null :
Object.keys(props.buttonData).map((key,i) => (
<UpdateRepair tryData={props.buttonData[i]} buttonName = "Details"
textName = {(parseInt(key, 10) + 1).toString() + '. ' + props.buttonData[i].attributes.RepairOrder} className = "ongoing" />
))}
</RenderInWindow>}
</>
);
}
As you can see, I already tried the location=no feature. It is not working.
let x = new StringBuilder();
x.Append("<script language=\"javascript\">");
x.AppendFormat("eval(\"popUpWindow('{0}',0,0,{1},{2},directories=no)\");", url,
width, height);
x.Append("</script>");
lblScript.Text = x.ToString();

In react, how can I deselect an element after it has been set as active with onClick?

I have an image grid in react, with an onclick function that highlights the selected image. Clicking another image will change the active element but I'd like to be able to re-click the selected icon to deselect it and return the grid to default.
Here is my codesandbox and the script is below
import { useState, useRef, useEffect } from "react";
import "./App.css";
import { Data } from "./Data.js";
function App() {
const scrollRef = useRef();
useEffect(() => {
const el = scrollRef.current;
if (el) {
const wheelListener = (e) => {
e.preventDefault();
el.scrollTo({
left: el.scrollLeft + e.deltaY * 5,
behavior: "smooth"
});
};
el.addEventListener("wheel", wheelListener);
return () => el.removeEventListener("wheel", wheelListener);
}
}, []);
const [active, setActive] = useState(-1);
const [active2, setActive2] = useState(false);
return (
<div ref={scrollRef} className="grid_container">
{Data.map((prev, i) => {
return (
<div
onClick={() => {
setActive(i);
setActive2(true);
console.log(prev.Team);
}}
className={`${
(active === i && "scale") || (active2 && "notScale")
} card`}
key={i}
>
<img src={prev.TeamBadge} alt="" />
</div>
);
})}
</div>
);
}
export default App;
If I understand the problem correctly, I think this solves the problem, based on the codesandbox:
onClick={() => {
setActive(i);
if (active === i) {
setActive2(null);
setActive(null);
} else {
setActive2(true);
}
console.log(i);
console.log(prev.Team);
}}
since every team has its unique number, the logic here is to check if the same number is in the state (if that makes sense). Let me know if this was you are looking for!
You can set your state like that :
setActive2(!active2);

Resources