How to change font family dynamically in a functional component? - reactjs

I have a component that fetches a random font from Google Font and pass it down to a functional component to render it.
function Palette() {
const url = `https://www.googleapis.com/webfonts/v1/webfonts?key=${key}`;
const [font, setFont] = useState("");
useEffect(() => {
const fetchFont = async url => {
const res = await axios(url);
const items = res.data.items;
const idx = Math.floor(Math.random() * items.length - 1);
setFont(items[idx].family);
};
fetchFont(url);
}, [url]);
return (
<div className="App">
<h1>Palette</h1>
<Card fontFamily={font} bgColor="green" color="white">
{font}
</Card>
</div>
);
}
How can I import this font dynamically inside the child component for styling?

How about this?
<Card style={{fontFamily: font, backgroundColor: 'green', color: 'white'}}>
{font}
</Card>
You can just set inline-css style in react like above. :)

You can append the the link tag that loads the font like this
const font = null;
const id = 'dynamic-font';
if (document.getElementById(id)) {
font = document.getElementById(id);
} else {
font = document.createElement('link');
font.rel='stylesheet'
font.id = id
document.head.appendChild(font)
}
font.href = // url to the font;
Then you can use the font in your CSS.
Another solution would be to actually download the .ttf / .otf files, then write the appropriate #font-face css class, create a CSS class that has the font-family set to the created font-face and apply the class to the desired element.

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.

Fallback for Next JS Image component

In Next JS Image component for using images from external URL we've to specify the base URL in next.config.js but what if the external urls are dynamic i.e., we don't know from where the images are coming from, then how do I tackle this case?
you must use custom loaders for that
next js document
import Image from 'next/image'
const myLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
const MyImage = (props) => {
return (
<Image
loader={myLoader}
src="me.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}

How can I use a variable value for class name when using makeStyles?

In my React app, I have a component which takes in some data. Depending on the value of that data, I want to show the component with a different coloured background.
The styles are defined as:
const useStyles = makeStyles((theme) => ({
class1: {
backgroundColor: "red",
},
class2: {
backgroundColor: "pink",
},
}));
The component is:
const MyBox = ({ data }) => {
let classes = useStyles();
let innerClassName;
if (data.value) {
innerClassName = "class1";
} else {
innerClassName = "class2";
}
return (
<div className={innerClassName}>
Content goes here
</div>
);
};
export default MyBox;
However, this gives the component a class of "class1" or "class2", which doesn't get picked up by makeStyles. I also tried <div className={classes.innerClassName}> but then it looks for a class called 'innerClassName' which obviously it can't find.
I think I need to use some kind of variable string within <div className={????}> but I've tried various template literal strings and none of them have worked. What should I be doing?

Cannot crop image using react-image-crop

I trying to crop image using react-image-crop (https://www.npmjs.com/package/react-image-crop/v/6.0.7). Actually I cannot move cropped area or stretch it. I also use there React DragZone to upload image. Could you explain what I'm doing wrong and what could be the problem ? React-image-crop css is also imported. Can anybody help?
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
import ReactCrop from 'react-image-crop'
import ReactDOM from "react-dom";
import 'react-image-crop/dist/ReactCrop.css';
import "../Styles/ImageUpload.css"
function ImageUpload(files, addFile) {
const crop = {
disabled: false,
locked: false,
unit: 'px', // default, can be 'px' or '%'
x: 130,
y: 50,
width: 200,
height: 200
}
const onCrop = (image, pixelCrop, fileName) => {
}
const onImageLoaded = (image) => {
}
const onCropComplete = async crop => {
}
const onDrop = useCallback((acceptedFiles, rejectedFiles) => {
if (Object.keys(rejectedFiles).length !== 0) {
}
else {
files.addFile(acceptedFiles);
let popupBody = document.getElementsByClassName("popup-container")[0];
popupBody.innerHTML = "";
let cropElement = (<ReactCrop src = {URL.createObjectURL(acceptedFiles[0])} crop={crop}
onImageLoaded={onImageLoaded}
onComplete={onCropComplete}
onChange={onCrop} />);
ReactDOM.render(cropElement, popupBody);
}, [])
const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop, noKeyboard: true})
return (
<div className = "popup-container">
<p>Upload new photo</p>
{
(() => {
if (files.length == undefined) {
return (<div>
<input className = "testinput" {...getInputProps()} />
<div className = "popup-body" {...getRootProps()}>
<img src={require("../Resources/upload-image.png")} className = "image-upload-img"/>
<p style = {{'font-family':'Verdana'}}>Chouse a <b className = "file-manualy-
upload">file</b> or drag it here</p>
</div> </div>)
}
else {
}
})()}
</div>
)
}
I also face the same issue, Here you are trying to add disabled, locked in crop property but it is invalid because crop property takes following values:
{
unit: 'px', // default, can be 'px' or '%'
x: 130,
y: 50,
width: 200,
height: 200
}
To solve this add disabled, locked as a props in component instead of crop property.
I'm not sure sure why you would do a ReactDom.render from within the function. It seems to be counter to what React is and guessing that the library isn't binding events properly.
I would suggest, after the user drops the image, you load the image using a file reader and set it as state and change the display to show the <ReactCrop src=={this.state.img} />. The crop also needs to be in a state, so that when it changes, it updates the crop rectangle.
Here's a working example from the creator himself: https://codesandbox.io/s/72py4jlll6

Images Rerendering inside Styled Component when Chrome Dev Tools is open

This is a bit of a strange one and not sure why it's happening exactly.
When the component mounts, I call a function that in my application makes an HTTP request to get an array of Objects. Then I update 3 states within a map method.
enquiries - Which is just the response from the HTTP request
activeProperty - Which defines which object id is current active
channelDetails - parses some of the response data to be used as a prop to pass down to a child component.
const [enquiries, setEnquiries] = useState({ loading: true });
const [activeProperty, setActiveProperty] = useState();
const [channelDetails, setChannelDetails] = useState([]);
const getChannels = async () => {
// In my actual project,this is an http request and I filter responses
const response = await Enquiries;
const channelDetailsCopy = [...channelDetails];
setEnquiries(
response.map((e, i) => {
const { property } = e;
if (property) {
const { id } = property;
let tempActiveProperty;
if (i === 0 && !activeProperty) {
tempActiveProperty = id;
setActiveProperty(tempActiveProperty);
}
}
channelDetailsCopy.push(getChannelDetails(e));
return e;
})
);
setChannelDetails(channelDetailsCopy);
};
useEffect(() => {
getChannels();
}, []);
Then I return a child component ChannelList that uses styled components to add styles to the element and renders child elements.
const ChannelList = ({ children, listHeight }) => {
const ChannelListDiv = styled.div`
height: ${listHeight};
overflow-y: scroll;
overflow-x: hidden;
`;
return <ChannelListDiv className={"ChannelList"}>{children}</ChannelListDiv>;
};
Inside ChannelList component I map over the enquiries state and render the ChannelListItem component which has an assigned key on the index of the object within the array, and accepts the channelDetails state and an onClick handler.
return (
<>
{enquiries &&
enquiries.length > 0 &&
!enquiries.loading &&
channelDetails.length > 0 ? (
<ChannelList listHeight={"380px"}>
{enquiries.map((enquiry, i) => {
return (
<ChannelListItem
key={i}
details={channelDetails[i]}
activeProperty={activeProperty}
setActiveProperty={id => setActiveProperty(id)}
/>
);
})}
</ChannelList>
) : (
"loading..."
)}
</>
);
In the ChannelListItem component I render two images from the details prop based on the channelDetails state
const ChannelListItem = ({ details, setActiveProperty, activeProperty }) => {
const handleClick = () => {
setActiveProperty(details.propId);
};
return (
<div onClick={() => handleClick()} className={`ChannelListItem`}>
<div className={"ChannelListItemAvatarHeads"}>
<div
className={
"ChannelListItemAvatarHeads-prop ChannelListItemAvatarHead"
}
style={{
backgroundSize: "cover",
backgroundImage: `url(${details.propertyImage})`
}}
/>
<div
className={
"ChannelListItemAvatarHeads-agent ChannelListItemAvatarHead"
}
style={{
backgroundSize: "cover",
backgroundImage: `url(${details.receiverLogo})`
}}
/>
</div>
{activeProperty === details.propId ? <div>active</div> : null}
</div>
);
};
Now, the issue comes whenever the chrome dev tools window is open and you click on the different ChannelListItems the images blink/rerender. I had thought that the diff algorithm would have kicked in here and not rerendered the images as they are the same images?
But it seems that styled-components adds a new class every time you click on a ChannelListItem, so it rerenders the image. But ONLY when the develop tools window is open?
Why is this? Is there a way around this?
I can use inline styles instead of styled-components and it works as expected, though I wanted to see if there was a way around this without removing styled-components
I have a CODESANDBOX to check for yourselves
If you re-activate cache in devtool on network tab the issue disappear.
So the question becomes why the browser refetch the image when cache is disabled ;)
It is simply because the dom change so browser re-render it as you mentioned it the class change.
So the class change because the componetn change.
You create a new component at every render.
A simple fix:
import React from "react";
import styled from "styled-components";
const ChannelListDiv = styled.div`
height: ${props => props.listHeight};
overflow-y: scroll;
overflow-x: hidden;
`;
const ChannelList = ({ children, listHeight }) => {
return <ChannelListDiv listHeight={listHeight} className={"ChannelList"}>{children}</ChannelListDiv>;
};
export default ChannelList;
I think it has to do with this setting to disable cache (see red marking in image)
Hope this helps.

Resources