using svg file to public folder vs. as component in nextjs - javascript

I need to use icons that are the same shape and have different sizes and colors.
At first, I was using a different file in the public folder.
ex) public/same-icon_red.svg, public/same-icon_blue.svg
But I think it's better to use it this way than this
//icon.js
export const Icon = ({fill, width, height}) => {
return (
<svg height={height} width={width} fill={fill} viewBox="0 0 24 24">
<path d="M0 0h24v24.../>
</svg>
);
}
//main.js
import Icon from "./icon"
...
return(
<>
...
<Icon width={24} height={24} fill={#000}/>
</>
)
But in this case, it bothers me that I don't use the public folder.
Which would be the best choice between the two?🤔

Related

How to use linear gradient in rechart

I have to create this graph in React. I am using the rechart npm package to do so. But I am not able to get the corner radiuses and the linear gradient. I am open to use any other library if needed.
Image
What I have tried?
I have used rechart Pie component to achieve this. Here is the code
import React from 'react';
import { PieChart, Pie, Sector } from 'recharts';
import getDevice from '../../styles/devices';
const renderActiveShape = (props) => {
const { cx, cy, startAngle, endAngle, payload, innerRadius, outerRadius, z, cornerRadius } = props;
console.log(startAngle, endAngle)
return (
<g>
<text x={cx} y={cy - 20} dy={8} fontSize={z ? '16px' : "24px"} textAnchor="middle" fill="#001233" fontWeight="bold">
{/* {parseFloat(payload.value / 1000).toFixed(1)}K */}
{payload.value < 1000 ? payload.value : `${parseFloat(payload.value / 1000).toFixed(1)}K`}
</text>
<text x={cx} y={cy + 5} dy={8} fontSize="12px" textAnchor="middle" fill="#5C677D">
{payload.name}
</text>
<Sector
cx={cx}
cy={cy}
innerRadius={innerRadius}
outerRadius={outerRadius + 20}
startAngle={startAngle}
endAngle={endAngle}
fill={"#12A4ED"}
cornerRadius={payload.name === 'Deposit' ? cornerRadius : 0}
/>
<Sector
cx={cx}
cy={cy}
innerRadius={innerRadius - 18}
outerRadius={innerRadius - 10}
startAngle={startAngle}
endAngle={endAngle}
fill={"#7674740f"}
/>
{/* <path d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`} stroke={fill} fill="none" />
<circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" /> */}
{/* <text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} textAnchor={textAnchor} fill="#333">{`${value}%`}</text> */}
</g>
);
};
export default function Chart({ data }) {
// const [activeIndex, setActiveIndex] = useState(1)
// const onPieEnter = (_, index) => {
// setActiveIndex(index)
// };
const { isMobile } = getDevice()
return (
<PieChart width={400} height={350}>
<Pie
activeIndex={1}
activeShape={renderActiveShape}
data={data}
cx={200}
innerRadius={isMobile ? 60 : 80}
outerRadius={isMobile ? 100 : 120}
cornerRadius={isMobile ? 5 : 10}
fill="#7DC762"
dataKey="value"
z={isMobile}
>
</Pie>
</PieChart>
);
}
But what I get by using this is
Can anyone help how to achieve this? Any help is appreciated.
To add gradient color to your chart, you can do the following:
This is just an example:
<PieChart width={400} height={350}>
{/* This will allow to add color based on your need, so update it accordingly */}
<defs>
<linearGradient id="colorUv" x1="1" y1="1" x2="0" y2="0">
<stop offset="30%" stopColor="#6584FF" stopOpacity={0.5} />
<stop offset="95%" stopColor="#FFFFFF" stopOpacity={0.5} />
</linearGradient>
</defs>
<Pie
activeIndex={1}
activeShape={renderActiveShape}
data={data}
cx={200}
innerRadius={isMobile ? 60 : 80}
outerRadius={isMobile ? 100 : 120}
cornerRadius={isMobile ? 5 : 10}
fill="url(#colorUv)"//Add the id 'colorUv' which is used in linearGradient
dataKey="value"
z={isMobile}
></Pie>
</PieChart>

React Anchor Link on Hidden Item - Can't perform a React state update on an unmounted component

I've created a section on a site that is two sliders, the first slider is visible and on click the slide will display the corresponding slide beneath with the bottom slider initially starting as a display: none element. Each top slide also has a button on it that will anchor you down to the bottom slider to put it in a better view position.
If you navigate from the homepage onto this page and click on the anchor button first to display the content, the page does anchor down slightly but the content does not display, it also pastes the anchor link into the URL which doesn't seem to be how the working version behaves. On load the page is getting an error in the console saying Can't perform a React state update on an unmounted component.
You then have to click the object again or reload the page to get the anchor function/content to display as expected. Reloading the page also removes the React state console error as I'm assuming the component does mount.
The slider is built using Slick React slider and I'm using Gatsby Anchor Link Plugin and React Animate Height to perform both the hide/show height and anchor functions as I learnt in the project you can't just add a class to add height or just anchor stuff in React which is fun/backwards.
The component class for the page and the 2 components are below:
class AboutPage extends React.Component {
_isMounted = false;
constructor(props) {
super(props)
this.heroTextRef = React.createRef()
this.teamRef = React.createRef()
this.serviceGroupsRef = React.createRef()
this.state = {
nav1: null,
nav2: null,
isShow: false,
height: 0,
activeColor: false
}
this.openHeight = this.openHeight.bind(this);
this.closeHeight = this.closeHeight.bind(this);
}
openHeight = () => {
const { height } = this.state;
const { activeColor } = this.state;
this.setState({
height: 'auto',
activeColor: true,
});
};
closeHeight = () => {
const { height } = this.state;
const { activeColor } = this.state;
this.setState({
height: 0,
activeColor: false,
});
};
componentDidMount() {
this.setState({
nav1: this.slider1,
nav2: this.slider2
});
this._isMounted = false;
}
render() {
const { height } = this.state;
const team = get(this, 'props.data.contentfulPageContentStudio.theOrcaTeam')
return (
<Layout
location={this.props.location}
inversionRefs={[this.serviceGroupsRef, this.teamRef]}
>
<SEO
siteTitle={'About Us - ' + siteTitle}
/>
<TeamWrapper ref={this.teamRef} ref={this.serviceGroupsRef}>
<Container width={14}>
<Reveal
fraction={ANIMATION.revealFriction}
keyframes={customReveal}
triggerOnce
>
<TeamGridTitle id="topSlider">The Orca Team</TeamGridTitle>
</Reveal>
<Slider
dots={false}
infinite={false}
speed={500}
slidesToShow={3}
slidesToScroll={1}
swipeToSlide={true}
arrows={false}
asNavFor={this.state.nav2}
focusOnSelect={true}
responsive={[
{
breakpoint: 1024,
settings: {
slidesToShow: 2
}
}
]}
ref={slider => (this.slider1 = slider)}
className="top-slider"
>
{team.map((person, i) => (
<>
<TeamCard key={slugify(person.firstName)} index={i} person={person} onClick={this.openHeight} colour={this.state.activeColor}/>
</>
))}
</Slider>
</Container>
<AnchorTag id="bottomSlider" />
<AnimateHeight
duration={ 500 }
height={ height }
>
<SliderContainer>
<Slider
asNavFor={this.state.nav1}
ref={slider => (this.slider2 = slider)}
slidesToShow={1}
fade={true}
adaptiveHeight={true}
arrows={false}
dots={false}
infinite={false}
>
{team.map((person, i) => (
<>
<PersonBio key={slugify(person.lastName)} index={i} person={person} onClick={this.closeHeight}/>
</>
))}
</Slider>
</SliderContainer>
</AnimateHeight>
</TeamWrapper>
</Layout>
)
}
}
Top Slider Component:
const TeamCard = (props) => {
return (
<Card
onClick={props.onClick}
className={props.colour ? 'team-card' : null}
>
{props.person.image && <Img fluid={props.person.image.fluid} />}
<Info image={props.person.image} className="card-item">
<Name>
{props.person.firstName} {props.person.lastName}
</Name>
<Role className="small">{props.person.role}</Role>
<AnchorLink
to="/about/#bottomSlider"
title="Top Link"
>
<DownArrow version="1.1" x="0px" y="0px" viewBox="0 0 64 64" width="24" height="24">
<polyline
fill="none"
stroke="#FFFFFF"
strokeWidth="8"
strokeMiterlimit="10"
points="3.3,17.2 32,46.8 60.7,17.2 "/>
</DownArrow>
</AnchorLink>
</Info>
</Card>
)
}
Bottom Slider Component:
const PersonBio = (props) => {
return (
<Card className="card-block">
<Info>
<Container width={14}>
<FlexRow>
<PersonInfo>
<Name>
{props.person.firstName} {props.person.lastName}
</Name>
<Role className="small">{props.person.role}</Role>
<Meta>
{props.person.dribble && (
<IconLink
href={props.person.dribble}
target="_blank"
rel="noreferrer"
>
<IconDribbleSVG
alt={`${props.person.firstName} ${props.person.lastName} Dribbble link`}
/>
</IconLink>
)}
{props.person.instagram && (
<IconLink
href={props.person.instagram}
target="_blank"
rel="noreferrer"
>
<IconInstagramSVG
alt={`${props.person.firstName} ${props.person.lastName} Instagram link`}
/>
</IconLink>
)}
{props.person.linkedIn && (
<IconLink
href={props.person.linkedIn}
target="_blank"
rel="noreferrer"
>
<IconLinkedInSVG
alt={`${props.person.firstName} ${props.person.lastName} LinkedIn link`}
/>
</IconLink>
)}
</Meta>
</PersonInfo>
<PersonContent className="bio-text" dangerouslySetInnerHTML={{ __html: props.person.bio.bio }}>
</PersonContent>
</FlexRow>
<SvgRow>
<AnchorLink
to="/about/#topSlider"
title="Bottom Link"
>
<CloseBio x="0px" y="0px" viewBox="0 0 64 64" className="bio-close" onClick={props.onClick}>
<circle cx="32" cy="32" r="29.8"/>
<line x1="12.7" y1="32" x2="51.3" y2="32" className="crossLine1"/>
<line x1="12.7" y1="32" x2="51.3" y2="32" className="crossLine2"/>
</CloseBio>
</AnchorLink>
</SvgRow>
</Container>
</Info>
</Card>
)
}

How to change a custom SVG icon color to be compatible with theme provider in Materiel-UI?

I have 2 icons one is a classic icon imported from Mui , the other is a custom logo.svg , the problem is I want to be able make those 2 icons compatible with theme switch using theme provider, if the theme is dark then icons should change colors from dark to white I managed to do this with the imported icon by adding color='primary' , but the problem is the stubborn custom svg, did any one had this issue ?
const useStyles = makeStyles((theme) => ({
icon: {
fontSize: 35,
//color: 'grey',
'&:hover': {
//color: 'white',
backgroundColor: 'transparent',
},
},
}));
const iconStyle = {
padding: 2,
marginTop: 0,
marginBottom: 0,
//backgroundColor: '#ef6c00',
borderRadius: '50%',
//fill: '#222222',
//fill: 'grey',
'&:hover': {
color: 'white',
},
};
// Custom svg icon
<Logo style={iconStyle} width='60' height='60' />
// Mui icon compatible with theme provider.
<EmojiObjectsIcon color='primary' className={classes.icon} onClick={switchTheme} />
There are two steps to achieve the goal.
First, find a way to pass color to a svg icon.
Second, use the pirmary color and theming options for the icon.
First
By creating a Logo component and using the svg on it:
const Logo = ({ fill, width, height }) => (
<svg
fill={fill} // ---> here
width={width} // ---> here
height={height} // ---> here
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17.707 8.293a1 1 0 0 1 0 1.414L15.414 12l2.293 2.293a1 1 0 0 1-1.414 1.414L14 13.414l-2.293 2.293a1 1 0 0 1-1.414-1.414L12.586 12l-2.293-2.293a1 1 0 1 1 1.414-1.414L14 10.586l2.293-2.293a1 1 0 0 1 1.414 0z"
fill={fill} // ---> here
/>
<path
clipRule="evenodd"
d="M22 5a1 1 0 0 0-1-1H9.46a2 2 0 0 0-1.519.698l-5.142 6a2 2 0 0 0 0 2.604l5.142 6A2 2 0 0 0 9.46 20H21a1 1 0 0 0 1-1V5zm-2 13H9.46l-5.143-6L9.46 6H20v12z"
fill={fill} // ---> here
/>
</svg>
);
export default Logo;
It's like your other components, the fill and other props are passed to it.
Now, we have a Logo component (with .jsx extension) as same as other components with some props on it.
Second
Now time to add Logo icon to the AppBar and pass the related color props:
<Logo
fill={
theme
? darkTheme.palette.primary.light
: darkTheme.palette.primary.dark
}
width="16"
height="16"
/>
You may also need to pass the width and height property with the Logo component.
Working version on CodeSandBox.
To make a custom SVG icon behave like other #mui/icons-material, make use of the SvgIcon component from mui.
If you are having multiple custom icons, its better to create an index file in the assets/icons folder and do the following.
import {SvgIcon} from '#mui/material'
export const CustomIcon = (props) => <SvgIcon {...props}>
// <path ... fill='inherit'/>
// <path ... fill='inherit'/>
// <rect ... stroke='inherit'/>
</SvgIcon>
In page, you can use it as simply as
<IconButton><CustomIcon color='primary'/></IconButton> //or any theme
The important things to remember here is to remove the svg element that comes along with the svg file when you copy the path. Only the elements excluding the svg are required.
To make the color change as other mui icons, replace all the custom color of fill property in paths to "inherit". This makes the icon take the color its provided by the theme provider

Can't render Svg Component in React

I have a component like this that accepts array of object as props. the info array has key called icon which value is another component(SVG) Home, People, Bath, Size
const info = [
{
value: 1,
icon: Home
},
{
value: 2,
icon: People
},
{
value: 10,
icon: Size
},
{
value: 1,
icon: Bath
},
]
<Info info={info} />
inside the Info component:
info.map((item, i) => (
<>
<li className="inline-flex">
{item.icon}
</li>
</>
))
Now the problem is that I can't render the icon component, when I log it, it prints a function()
what should i do in order to print the icon component ?
Update:
this is how my svg Component looks like.
import React, { StatelessComponent, SVGAttributes } from "react";
export interface SvgrComponent extends StatelessComponent<SVGAttributes<SVGElement>> {}
export const People: SvgrComponent = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
focusable="false"
>
<path
d="M21,15V13a1,1,0,0,0-1-1H19V6a3,3,0,0,0-6,0v.5a.5.5,0,0,0,1,0V6a2,2,0,0,1,4,0v6H4a1,1,0,0,0-1,1v2a5,5,0,0,0,2.17,4.12l-1,1a.48.48,0,0,0,0,.7.48.48,0,0,0,.7,0l1.24-1.23A5,5,0,0,0,8,20h8a5,5,0,0,0,1.91-.38l1.24,1.23a.49.49,0,0,0,.7-.7l-1-1A5,5,0,0,0,21,15Zm-2.9,3.39a3.74,3.74,0,0,1-1,.43A3.8,3.8,0,0,1,16,19H8a3.8,3.8,0,0,1-1.12-.18,3.74,3.74,0,0,1-1-.43A4,4,0,0,1,4,15V13H20v2A4,4,0,0,1,18.1,18.39Z"
fill="currentColor"
/>
</svg>
);
};
Try the following:
info.map((item, i) => (
<>
<li className="inline-flex">
{item.icon()}
</li>
</>
))
svg should set as source to the img
{
info.map((item, i) => (
<>
<li className="inline-flex">
<img src={item.icon} />
</li>
</>
))}
I'd want to see more code posted since the following works:
import React from "react";
import "./style.css";
const Home = (
<svg class="svg-icon" viewBox="0 0 20 20">
<path d="M17.684,7.925l-5.131-0.67L10.329,2.57c-0.131-0.275-0.527-0.275-0.658,0L7.447,7.255l-5.131,0.67C2.014,7.964,1.892,8.333,2.113,8.54l3.76,3.568L4.924,17.21c-0.056,0.297,0.261,0.525,0.533,0.379L10,15.109l4.543,2.479c0.273,0.153,0.587-0.089,0.533-0.379l-0.949-5.103l3.76-3.568C18.108,8.333,17.986,7.964,17.684,7.925 M13.481,11.723c-0.089,0.083-0.129,0.205-0.105,0.324l0.848,4.547l-4.047-2.208c-0.055-0.03-0.116-0.045-0.176-0.045s-0.122,0.015-0.176,0.045l-4.047,2.208l0.847-4.547c0.023-0.119-0.016-0.241-0.105-0.324L3.162,8.54L7.74,7.941c0.124-0.016,0.229-0.093,0.282-0.203L10,3.568l1.978,4.17c0.053,0.11,0.158,0.187,0.282,0.203l4.578,0.598L13.481,11.723z" />
</svg>
);
const People = (
<svg class="svg-icon" viewBox="0 0 20 20">
<path d="M17.684,7.925l-5.131-0.67L10.329,2.57c-0.131-0.275-0.527-0.275-0.658,0L7.447,7.255l-5.131,0.67C2.014,7.964,1.892,8.333,2.113,8.54l3.76,3.568L4.924,17.21c-0.056,0.297,0.261,0.525,0.533,0.379L10,15.109l4.543,2.479c0.273,0.153,0.587-0.089,0.533-0.379l-0.949-5.103l3.76-3.568C18.108,8.333,17.986,7.964,17.684,7.925 M13.481,11.723c-0.089,0.083-0.129,0.205-0.105,0.324l0.848,4.547l-4.047-2.208c-0.055-0.03-0.116-0.045-0.176-0.045s-0.122,0.015-0.176,0.045l-4.047,2.208l0.847-4.547c0.023-0.119-0.016-0.241-0.105-0.324L3.162,8.54L7.74,7.941c0.124-0.016,0.229-0.093,0.282-0.203L10,3.568l1.978,4.17c0.053,0.11,0.158,0.187,0.282,0.203l4.578,0.598L13.481,11.723z" />
</svg>
);
const info = [
{
value: 1,
icon: Home
},
{
value: 2,
icon: People
}
];
const Info = ({ info }) => (
<div>
{info.map((item, i) => (
<>
<li className="inline-flex">{item.icon}</li>
</>
))}
</div>
);
export default function App() {
return <Info info={info} />;
}
I would recommend embedding in an image. this is a simple code.
{
info.map((item, i) => (
<>
<li className="inline-flex">
<img src={item.icon} />
</li>
</>
))}

Are there alternatives to querySelectorAll in React?

I'm trying to refactor some javascript codes using React. What I'm trying to do is -> I have some elements which have the same className and I want to have control over each element. For this, in javascript I could just use the queryselectorAll and forEach functions, like
<Svg>
<circle cx="100" cy="60" r="20" class="human"></circle>
<line x1="100" y1="80" x2="100" y2="120" class="human"></line>
<line x1="70" x2="100" y1="75" y2="95" class="human"></line>
<line x1="130" x2="100" y1="75" y2="95" class="human"></line>
<line x1="70" x2="100" y1="140" y2="120" class="human"></line>
<line x1="130" x2="100" y1="140" y2="120" class="human"></line>
</Svg>
const wrongLetters = [];
window.addEventListener('keydown", (e) => {
if(e.keyCode >= 65 && e.keyCode <= 90) {
wrongLetters.push(e.key)
}
}
const humans = documentSelectorAll(".human")
humans.forEach((human, index) => {
if (index < wrongLetters.length) {
human.style.display = "block";
}
if (6 === wrongLetters.length) {
popUpContainer.style.display = "flex";
comment.innerText = "You have lost";
}
}); }
However, I assume it'd be different in React even if I change 'class' to 'className'? For this, I've already searched some relevant posts and tried to use 'ref' but still don't know exactly what to do in this case.
Any advice would be appreciated.
+edit/ What I'm trying to do is every time I add an element to 'wrongLetters' array, each element in Svg tag will be displayed. Each Svg element is set to 'display: none' as default in javascript
Put the wrongLetters into state that gets changed instead of pushing to the array. Then, in React, analyze that state to determine how many elements need to be hidden. Since this can encompass all of the functionality you're looking for, there's no need to be able to access the individual DOM elements with useRef or querySelectorAll:
const App = () => {
const [pressed, setPressed] = React.useState([]);
React.useEffect(() => {
window.addEventListener('keydown', (e) => {
if (!pressed.includes(e.keyCode) && e.keyCode >= 65 && e.keyCode <= 90) {
setPressed(pressed => [...pressed, e.keyCode]);
}
});
}, []);
return (
<svg>
<circle style={{display: pressed.length > 5 ? 'block' : 'none'}} cx="100" cy="60" r="20" stroke="black"></circle>
<line style={{display: pressed.length > 4 ? 'block' : 'none'}} x1="100" y1="80" x2="100" y2="120" stroke="black"></line>
<line style={{display: pressed.length > 3 ? 'block' : 'none'}} x1="70" x2="100" y1="75" y2="95" stroke="black"></line>
<line style={{display: pressed.length > 2 ? 'block' : 'none'}} x1="130" x2="100" y1="75" y2="95" stroke="black"></line>
<line style={{display: pressed.length > 1 ? 'block' : 'none'}} x1="70" x2="100" y1="140" y2="120" stroke="black"></line>
<line style={{display: pressed.length > 0 ? 'block' : 'none'}} x1="130" x2="100" y1="140" y2="120" stroke="black"></line>
</svg>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
line {
color: black;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
That's the React way of doing things - use state when you can; avoid accessing/changing the DOM directly, just make the render reflect the state, and change the state when needed.
As I said in my comment, in react you'd have a component that renders those elements, updating them or rendering different elements according to changes in application or component state. You very, very rarely need to access the DOM directly in react.
Here's a rough implementation of the sort of thing I mean. There's a live/working copy of it on codesandbox:
import React, { useEffect } from "react";
import "./styles.css";
// an array of the svg elements; the component below renders
// a subset of these according to number of attempts.
// i'm not sure this is how i'd do this in the real world
// but it's good enough for this demo.
const segments = [
<circle cx="100" cy="60" r="20" class="human"></circle>,
<line x1="100" y1="80" x2="100" y2="120" class="human"></line>,
<line x1="70" x2="100" y1="75" y2="95" class="human"></line>,
<line x1="130" x2="100" y1="75" y2="95" class="human"></line>,
<line x1="70" x2="100" y1="140" y2="120" class="human"></line>,
<line x1="130" x2="100" y1="140" y2="120" class="human"></line>
];
export default function App() {
// track the number of attempts in component state
const [attempts, setAttempts] = React.useState(0);
// handler for keydown events that for this demo
// doesn't do anything besides increment the number of attempts
const onKeyDown = React.useCallback(() => setAttempts(attempts + 1), [
attempts
]);
useEffect(() => {
// when the component mounts, add the keydown listener
window.addEventListener("keydown", onKeyDown);
// when the component unmounts remove the listener
return () => window.removeEventListener("keydown", onKeyDown);
}, [onKeyDown]);
// get the visible subset of segments based on number of attempts
const visibleSegments = segments.slice(0, attempts);
// return the new markup
return (
<div className="App">
<svg viewBox="0 0 200 200" style={{ width: "300px" }}>
{visibleSegments}
</svg>
{attempts >= segments.length && (
<div>
<div>You Lose or whatever</div>
<button onClick={() => setAttempts(0)}>Reset</button>
</div>
)}
</div>
);
}
Brother if you want to access the DOM in React JS tou have to create ref using createRef() Hooks and then you can able to access the Dom in react like javascript
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
you can acess the node by calling this.myRef.current method
const node = this.myRef.current;
You can add the same ref in multiple html element and when u called this.myRef.current then all the element you put the ref you can access...

Categories