Displaying placeholder content when an element is removed by an extension or adblocker - reactjs

I trying to integrate some user feedback tools into out application, and part of this required embedding an iframe into the application. I am using Privacy Badger, and it blocks all the content of the iframe and changes the display property to none all the time. That's all well and good, i'm not trying to force this past peoples privacy extensions, however I do want to be able to detect if it has been removed, and just display some placeholder text like "This was removed by an adblocker. Feel free to email us if you have feedback" so that the app doesn't feel broken.
My Current Attempt: Code Sandbox
This however doesn't display the fallback as I would like it to.
I have tried various renditions of this method, all without luck.
Essentially what I want is this:
Detect if the iframe element has it's display property set to none, and render a fallback in it's place if that is true.

The problem is that you are immediately checking if the style is none, but your test doesn't change it until 5 sec later, so you'd have to wait some time before checking the style.
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
const [fallback, setFallback] = useState(0);
const setNoneDisplay = () => {
const el = document.getElementById("red-rover");
el.style.display = "none";
};
React.useEffect(() => {
window.setTimeout(setNoneDisplay, 5000);
});
const refCallback = React.useCallback((node) => {
if (node) setTimeout(() => setFallback(node.style.display === "none"), 6000);
}, []);
return (
<div className="App">
<div id="red-rover" ref={refCallback}>
Displayed
</div>
{fallback ? <div>Only Displayed As Fallback</div> : null}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Related

How to export a component to a PDF file, that is not visible on UI, but has to be in PDF document (html-to-image, jsPDF, React)

Like the title says, I want to export a component to a PDF file, that I want to be invisible in the app or should I say on UI, but I want it to be inside a PDF document.
To make this PDF exporting functionality I have used the combination of html-to-image library, jsPDF library and everything is made using React.
This is my code:
function App() {
const [exporting, setExporting] = useState(false);
async function createPdf({ doc, element }) {
const imgData = await toPng(element);
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
doc.addImage(imgData, "PNG", 10, 0, pdfWidth, pdfHeight, "", "FAST");
}
async function handleDownloadPdf() {
const element = document.getElementsByClassName("container")[0];
const doc = new jsPDF(
"p",
"px",
[element.clientWidth, element.clientHeight],
true
);
setExporting(true);
await createPdf({ doc, element });
doc.save(`charts.pdf`);
}
return (
<pdfContext.Provider value={{ exporting, setExporting }}>
<div className="App">
<button onClick={handleDownloadPdf}>Test</button>
<div className="container">
<Hidden />
<Foo />
</div>
</div>
</pdfContext.Provider>
);
}
export default App;
The component that I want to be hidden is <Hidden />, this is a simple component but let me show the code anyways:
const Hidden = () => {
const { exporting, setExporting } = useContext(pdfContext);
return (
<div
className="elementOne"
style={{ visibility: exporting ? "visible" : "hidden" }}
>
</div>
);
};
export default Hidden;
As you can see I want to use the context called pdfContext that sets the visibility of a component to hidden when the component is not being exported, and to visible when it's being exported, but this way is not really a good solution, as the component gets visible for a split second before exporting and in my opinion it's not a good design.
So if anyone has any solution or a workaround on how to export a component to a PDF using these libraries, but without showing it on a UI, that would be great.
I know that the way these components are being exported to a PDF is by converting the container to an image, and probably the way I am asking to do this is maybe impossible but then again it does not hurt to ask.

Match background with users current weather conditions

I am new to React, trying to learn and I have this unsolvable problem. I have developed a weather app, I'm still working on it, but at this moment I am stuck for 3 days trying to have a background image that changes depending on the users weather conditions. I have tried something using the icon, from openweather API. I used the same method to get the icon (image from my folder) to match users weather conditions.
import React from "react";
export default function Background(props) {
const codeMapping = {
"01d": "clear-sky-day",
"01n": "clear-sky-night",
"02d": "cloudy-day",
"02n": "cloudy-night",
"03d": "cloudy-day",
"03n": "cloudy-night",
"04d": "cloudy-day",
"04n": "cloudy-night",
"09d": "shower-rain-day",
"09n": "shower-rain-night",
"10d": "rain-day",
"10n": "rain-night",
"11d": "thunderstorm-day",
"11n": "thunderstorm-night",
"13d": "snow-day",
"13n": "snow-night",
"50d": "fog-day",
"50n": "fog-night",
};
let name = codeMapping[props.code];
return (
<img
className="background"
src={`background/${name}.jpg`}
alt={props.alt}
size="cover"
/>
);
}
So... in order to get "icon" of the input city by the user I have to call "<Background cod={weatherData.icon} alt={weatherData.description} />" from the function "Search" which is the function handling the submit form and running api call for input city. But the image is not showing(img1), but to have the img as a background I would call <Background> from my App function(img2), but in this case I will not have access to the real icon value from the input city. I should mention I have a folder in "src" called background and the images names match the codes name from the mapping.
Thank you in advance!
current preview of my app
how I see in other documentation I should set a background
You can pass the code from Search.js as the state.
App.js
const codeMapping = {
"01d": "clear-sky-day",
"01n": "clear-sky-night",
};
export const App = () => {
const [code, setCode] = useState(null) // <-- We'll update this from Search.js
const [backgroundImage, setBackgroundImage] = useState("")
useEffect(() => {
// Set background value based on the code
setBackgroundImage(codeMapping[`${code}`])
}, [code]); // <-- useEffect will run everytime the code changes
return (
<div style={{
height: '100px',
width: '100px',
backgroundImage: `${backgroundImage || "defaultBackgroundImage"}`
}}>
<Search setCode={setCode} />
</div>
)
}
Search.js
import { WeatherContext } from './App';
export const Search = ({ setCode }) => {
const handleClick = (apiResponse) => {
// Some API call returning the actual code value here //
setCode(apiResponse)
}
return (
<input
onClick={() => handleClick("01n")}
type="button"
value="Change city"
/>
)
}

Gatsby application that is SPA on mobile and has different routes on desktop

📖 Summary
Recently my team started a project of a landing page and we chose to use Gatsby in order to have good SEO.
At a point in our project, the designers changed the mobile layouts to be a SPA, and the desktop ones still having different routes and pages.
Refer to that example:
Since Gatsby creates pages in build time, we don't know if the environment is mobile or desktop, it's difficult to think in a way to deal with that behavior.
🐛 Workaround
One quick way that our team thought to temporarily resolve that problem was to map between sections and hide than in desktop screens.
And the biggest problem is: On the first load of the page the content takes almost a second to load because it's not static anymore.
<div>
{
breakpoints.md
? pages.map((page) => renderPage(page))
: renderPage(selectedPageRef.current)
}
</div>
🚀 Goals
I would like to discuss about a solution that will change the behavior of the pages in desktop and mobile without killing the SEO of the application.
If you can't solve by using mediaqueries and you must display two different components rather than the same styled. The workaround to solve this is to check what is the window size at the rendered time and show one layout or another. It would create a minimum delay (insignificant if cached) before the header is shown but this is the only way I am able to guess.
So, using Gatsby's default structure in <Layout> component you should have something like that:
return (
<>
<Header siteTitle={data.site.siteMetadata.title} />
<div>
<main>{children}</main>
<footer>
© {new Date().getFullYear()}, Built with
{` `}
Gatsby
</footer>
</div>
</>
)
So, in your <Header> component you should check your window size and render one component or another:
export const Header = (props) => {
let currentWidth;
if (typeof window !== 'undefined') currentWidth = useWindowWidth();
return typeof window !== 'undefined' ? currentWidth >= 768 ? <DesktopPane /> : <MobilePane /> : null;
};
As you can see, in the return I check if window is defined in a ternary chained condition. If the window is not defined (i.e: is undefined) it returns a null. If it's defined, it checks the current window's width (currentWidth) and there's another ternary condition that displays the mobile or the desktop menu.
As a best practice, chained ternaries are not the cleanest solution, they are difficult to read and maintain but for now, the solution works (and of course it must be refactored).
In this case, useWindowWidth() is a custom hook that calculates in every window the size but you can use whatever you like. It looks like:
import {useEffect, useState} from 'react';
const getWidth = () => window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
export const useWindowWidth = () => {
let [width, setWidth] = useState(getWidth());
useEffect(() => {
let timeoutId = null;
const resizeListener = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => setWidth(getWidth()), 150);
};
window.addEventListener('resize', resizeListener);
return () => {
window.removeEventListener('resize', resizeListener);
};
}, []);
return width;
};
Code provided by: https://usehooks.com/useWindowSize/
Note that it's common in Gatsby's projects to check if the window is !== than undefined due to the point you argued. At the compilation/build point isn't defined yet. You can check for further information about Debugging HTML Builds in their documentation.
First, you can achieve the following with the help of CSS media query (hide the sidebar or transform it to be the top nav):
Then, you can set up a useMediaQuery hook (for example here is an implementation) to conditionally render the About and Events components in one of the two ways shown below:
A) If most users are using desktop, you can defer the import of the other two components (About and Events) in mobile view with loadable-components:
The Index page will look something like this:
import React from "react"
import Loadable from "#loadable/component"
import useMediaQuery from "use-media-query-hook"
import Sidebar from "../components/Sidebar"
import Home from "../components/Home"
const LoadableAbout = Loadable(() => import("../components/About"))
const LoadableEvents = Loadable(() => import("../components/Events"))-
const IndexPage = () => {
const isMobile = useMediaQuery("(max-width: 425px)")
return (
<div>
<Sidebar />
<Home />
{isMobile && <LoadableAbout />}
{isMobile && <LoadableEvents />}
</div>
)
}
export default IndexPage
B) If most users are using mobile, you can include the two components in the main bundle at build time:
The Index page will look something like this:
import React from "react"
import useMediaQuery from "use-media-query-hook"
import Sidebar from "../components/Sidebar"
import Home from "../components/Home"
import About from "../components/About"
import Events from "../components/Events"
const IndexPage = () => {
const isMobile = useMediaQuery("(max-width: 425px)")
return (
<div>
<Sidebar />
<Home />
{isMobile && <About />}
{isMobile && <Events />}
</div>
)
}
export default IndexPage
Regarding SEO, the search engine will only see the main component (Home in route "/", About in "/about", etc) since isMobile defaults to null at build time in the above implementations.
Regarding speed, the main components are statically rendered in the HTML. Only in mobile SPA view, other sections are needed to be loaded.

React execute script if window width

I have a button in React that executes a function onClick. I want to get rid of the button, and instead programmatically execute the function if window width < 1000px.
A restriction is that I can not add a plugin.
Here's what the code looks like...
// Do I need useState, useEffect?
import React, { PureComponent } from "react";
class MainNav extends PureComponent {
state = {
// Does something go here? What goes here and how do I use
// state to execute the function?
navIsCollapsed: false,
};
// this controls rendering of huge images
toggleShowImages() {
this.setState({
navIsCollapsed: !this.state.navIsCollapsed,
});
}
// I want this to be executed by width < 1000
handleSideNavToggle = () => {
this.toggleShowImages(); // controls if React renders components
document.body.classList.toggle("side-nav-closed");
}
Here's render the button that's currently executing the function. I want width < 1000 to programmatically execute its function.
// window width < 1000 should execute this function
<div onClick={this.handleSideNavToggle}>Don't render huge images</div>
// here are the images the function conditionally renders
<should show images &&
<div>Massive huge image</div>
<div>Massive huge image</div>
<div>Massive huge image</div>
>
I could use CSS media query to show or hide the massive images I don't want, but that's horrible use of React.
I've looked and tried to implement similar questions on SO that either invoke plugins, are out of date, or the use case is too different (for example, "re-render everything based on screen size"). I've also tried to React-ify vanilla javascript. This seems like it ought to be simple to do but I can't make it work.
Any React wizards out there who can answer with a clean, efficient solution?
Use the above method that Mathis Delaunay mentioned to get viewport/window width, then to get rid of that button. Just simply add a condition to whether render it or not and then watch on state changes to trigger the function.
Here I use hooks to do it
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
function App() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, [width]);
useEffect(() => {
width < 600 && handleSideNavToggle();
},[width]);
function handleSideNavToggle() {
console.log("toggle it");
}
return (
<div className="App">
{width > 600 && (
<button onClick={() => handleSideNavToggle()}>
Don't render huge images
</button>
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is a working example. I set the width to be handled as 600 to make it easy to see.
https://codesandbox.io/s/react-hooks-counter-demo-w9wgv
Try looking at this answer, i think it is what your are searching for :
Get viewport/window height in ReactJS
You just need to check in the updateWindowDimension if the window.innerWidth is under 1000, if so, change the css button property to display : none; or visibility: hidden;.

Skipping Effects does not work for Array of dynamic URL's

I have a React.SFC / react stateless / functional component which is unfortunately rendering a little too frequent due to some excess data coming in from redux in a parent component. Nothing I can do about that for now, so I'm just accepting the extra rerenders, and using useEffect to make sure data is only fetched whenever a certain property changes. In this case its called "urls" and it is an array of URL's (TypeScript URL Type).
Here's some example code illustrating the issue:
import React from "react";
import { useState, useEffect, useMemo } from "react";
import { render } from "react-dom";
import "./styles.css";
const useCustomHook = urls => {
const [onlyChangeWhenUrlsChange, setOnlyChangeWhenUrlsChange] = useState(
null
);
useEffect(
() => {
setOnlyChangeWhenUrlsChange(Math.random());
},
[urls]
);
return onlyChangeWhenUrlsChange;
};
const dynamicUrls = (pageRouteParamId, someDynamicUrlParam) => {
return [
{
pageRouteParamId: 1337,
urls: [new URL(`https://someurl.com/api?id=${someDynamicUrlParam}`)]
}
];
};
const SomePage: React.SFC<any> = ({
simulateFrequentUpdatingData,
pageRouteParamId
}) => {
const someOtherId = 1;
// As suggested in SO answer, using useMemo seems to work, but will that not create a memory leak?
// Is there any good alternative?
// const urls = useMemo(() => dynamicUrls(pageRouteParamId, someOtherId).find(url => url.pageRouteParamId === pageRouteParamId).urls, [pageRouteParamId, someOtherId]);
const urls = dynamicUrls(pageRouteParamId, 1).find(
url => url.pageRouteParamId === 1337
).urls;
return (
<div>
<p>parent</p>
<p>{simulateFrequentUpdatingData}</p>
<p>
Page route param id (in real app this would come from react-router route
param): {pageRouteParamId}
</p>
{urls && urls.length && <MyStateLessFunctionalComponent {...{ urls }} />}
<p>
Page route param id (in real app this would come from react-router route
param): {pageRouteParamId}
</p>
{urls && urls.length && (
<MyStateLessFunctionalComponentWithHook {...{ urls }} />
)}
</div>
);
};
const MyStateLessFunctionalComponent: React.SFC<any> = ({ urls }) => {
const [onlyChangeWhenUrlsChange, setOnlyChangeWhenUrlsChange] = useState(
null
);
useEffect(
() => {
setOnlyChangeWhenUrlsChange(Math.random());
},
[urls]
);
return (
<div>
<p>MyStateLessFunctionalComponent</p>
<p>{JSON.stringify(urls)}</p>
<p>This should only change when urls change {onlyChangeWhenUrlsChange}</p>
</div>
);
};
const MyStateLessFunctionalComponentWithHook: React.SFC<any> = ({ urls }) => {
const onlyChangeWhenUrlsChange = useCustomHook(urls);
return (
<div>
<p>MyStateLessFunctionalComponentWithHook</p>
<p>{JSON.stringify(urls)}</p>
<p>This should only change when urls change {onlyChangeWhenUrlsChange}</p>
</div>
);
};
function App() {
const [
simulateFrequentUpdatingData,
setSimulateFrequentUpdatingData
] = useState(null);
const [pageRouteParamId, setPageRouteParamId] = useState(1337);
useEffect(() => {
setInterval(() => setSimulateFrequentUpdatingData(Math.random()), 1000);
}, []);
return (
<div className="App">
<SomePage {...{ simulateFrequentUpdatingData, pageRouteParamId }} />
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Edit:
I had to change the title and question, since while reproducing it with the example code I realized the problem was not about "Skipping Effects inside a custom hook". Before I though I saw a difference when using useEffect directly vs inside a custom hook, and as the comments rightfully mentioned, there should not be any difference - and I came to the same conclusion while reproducing my issue with this sample code:
You can check out a live example here.
As it was suggested in the answer below, it seems like useMemo solves the issue (see line 36)
My guess is that urls is being declared inside a render higher up the tree, and thus getting a new identity every time. You can either useMemo on the place where it is being declared, JSON.stringify the urls in the deps-array, or a useRef which works as an additional guard against re-runs.
Edit: This is being discussed by smarter people than me: https://github.com/facebook/react/issues/14476#issuecomment-471199055.
If urls is an array of strings you can pass that as the second argument to useEffect
useEffect(() => {
loadData();
}, urls);
that way it will check the string values instead of the array reference.

Resources