Reusable Component in React - reactjs

I wanted to create a reusable component in my React app using Styled-Components. My problem is that i got the error
Error: Element type is invalid: expected a string (for built-in
components) or a class/function (for composite components) but got:
undefined. You likely forgot to export your component from the file
it's defined in, or you might have mixed up default and named imports.
pls check my code below:
import styled from 'styled-components'
import React from 'react'
const CustomSpinner = styled.svg`
animation: rotate 1s linear infinite;
width: 50px;
height: 30px;
& .path {
stroke: ${(props) => props.theme.colors.black};
stroke-linecap: round;
animation: dash 1.5s ease-in-out infinite;
}
#keyframes rotate {
100% {
transform: rotate(360deg);
}
}
#keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
`
export const Spinner = ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}

Just need to change the export const Spinner = (...) => to export default (...) =>, like so:
export default ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
But I would suggest exporting like this instead so you can get full autocomplete from your code editor when importing components:
const Spinner = ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
export default Spinner;

It has to do with how you are importing this component.
When you do
export const Spinner = ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
Then you need to import it like
import {Spinner} from 'path/to/Spinner
and you are likely importing it like
import Spinner from 'path/to/Spinner'
The above line imports the default export from spinner, but you don't have a default export, so you need to change your import, or change your export to
export default ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
instead

Related

Animated rotate circle SVG in reactjs

https://i.stack.imgur.com/kG5Sg.png
How can i dot blue running around circle. I have tried using transform:rotate in css but no success. Here is my code.
import './loadingicon.css'
const LoadingIcon = () => {
return (
< >
<div className='circle'>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
<circle
class='track'
cx="80"
cy="80"
r="70"
fill="none"
stroke="#374a67"
stroke-dasharray="26.416, 5"
stroke-width="4"
/>
<circle class="filled" cx="80" cy="80" r="70" />
</svg>
</div>
</>
)
}
export default LoadingIcon;
loadingicon.css
.track,
.filled {
stroke-width: 10;
fill: none;
}
.track {
stroke: #eee;
}
.filled {
stroke: blue;
stroke-dashoffset: 420;
stroke-dasharray: 440;
}

React useState() & useEffect: DOM element changed but not rendering

I'm trying to achieve in react: click the heart to like/unlike a move, using useState() & useEffect(), but seems the element is applied with new class name, but the color didn't change when i clicked.
export function MovieCard2(props) {
const [heartClassName, setHeartClassName] = useState("heart heart-white")
useEffect(() => {
console.log(ref.current)
if(isLiked === false) {
setHeartClassName("heart heart-white")
} else {
setHeartClassName("heart heart-red")
}
}, [isLiked]);
return (
<>
<div
className={heartClassName}
onClick={function (ev) {
ev.stopPropagation();
setLiked(!isLiked)
>
❤
</div>
<style jsx>{`
.heart {
padding: 16px 0 auto auto;
}
.heart-white {
color: white;
}
.heart-red {
color: red;
}
`}</style>
</>
);
}
try this one:
const [heartClassName, setHeartClassName] = useState("heart heart-white");
const [isLiked,setLiked]=useState(false);
useEffect(() => {
if (isLiked === false) {
setHeartClassName("heart heart-white");
} else {
setHeartClassName("heart heart-red");
}
}, [isLiked]);
you HTML
<style jsx>{`
.heart {
padding: 16px 0 auto auto;
}
.heart-white {
color: white;
}
.heart-red {
color: red;
}
`}</style>
<>
<div
className={heartClassName}
onClick={function (ev) {
ev.stopPropagation();
setLiked(!isLiked);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
width="1em"
height="1em"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 1024 1024"
style={{ height: '10px', width:'10px' }}
>
<path
fill="currentColor"
d="M923 283.6a260.04 260.04 0 0 0-56.9-82.8a264.4 264.4 0 0 0-84-55.5A265.34 265.34 0 0 0 679.7 125c-49.3 0-97.4 13.5-139.2 39c-10 6.1-19.5 12.8-28.5 20.1c-9-7.3-18.5-14-28.5-20.1c-41.8-25.5-89.9-39-139.2-39c-35.5 0-69.9 6.8-102.4 20.3c-31.4 13-59.7 31.7-84 55.5a258.44 258.44 0 0 0-56.9 82.8c-13.9 32.3-21 66.6-21 101.9c0 33.3 6.8 68 20.3 103.3c11.3 29.5 27.5 60.1 48.2 91c32.8 48.9 77.9 99.9 133.9 151.6c92.8 85.7 184.7 144.9 188.6 147.3l23.7 15.2c10.5 6.7 24 6.7 34.5 0l23.7-15.2c3.9-2.5 95.7-61.6 188.6-147.3c56-51.7 101.1-102.7 133.9-151.6c20.7-30.9 37-61.5 48.2-91c13.5-35.3 20.3-70 20.3-103.3c.1-35.3-7-69.6-20.9-101.9z"
/>
</svg>
</div>
</>
From what i see on your browser you are using Next.js which automatically updates and renders out when a variable changes, and you are also not initializing the variable isLiked
This should fix your issue.
import {useState} from "react";
export function MovieCard2(props) {
const [heartClassName, setHeartClassName] = useState("heart heart-white");
const [isLiked, setIsLiked] = useState(false);
function handleLike() {
setIsLiked(!isLiked);
if (isLiked) {
setHeartClassName("heart heart-red");
} else {
setHeartClassName("heart heart-white");
}
}
return (
<>
<div className={heartClassName} onClick={handleLike}>
❤
</div>
<style jsx>
{`
.heart {
padding: 16px 0 auto auto;
}
.heart-white {
color: white;
}
.heart-red {
color: red;
}
`}
</style>
</>
);
}

Apply fill color to all circles in an SVG in React

I'm trying to use React to work with SVG's in a procedural manner.
Something I'd like to do is use a parent element to set the color of different circles in my svg:
const MySVG = (): JSX.Element => {
return (
//all circles in this SVG should have a yellow fill
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
style={{ width: "fit-content", height: "100%" }}
viewBox="0 0 971 191"
>
<SVGProcessor>
<circle cx="730.9" cy="109.6" r="52.9" />
<g>
<path d="M587.8,39.6c10.7,8,25.9,5.9,33.9-4.8L641.9,91c-13-3.1-26.1,4.9-29.2,17.9L587.8,39.6z" />
</g>
<circle cx="816.2" cy="53.9" r="33.4" />
<g>
<circle cx="547.5" cy="93.1" r="67" />
</g>
<circle cx="915.5" cy="95.5" r="55" />
</SVGProcessor>
</svg>
);
};
//should apply a yellow fill to all circles in the svg
const SVGProcessor = ({
children,
}: {
children: JSX.Element[];
}): JSX.Element => {
useEffect(() => {
children.forEach((child) => {
if (child.type.displayName === "circle") {
const c = child as React.SVGProps<SVGCircleElement>;
c.fill = "yellow"
}
});
}, []);
return <>{children}</>;
};
But this doesn't work with nested svg elements, as they are not direct children of the wrapper component.
Is there a good way to compose SVG's procedurally?

Using Styled Components to change the color of an SVG's stroke

I have an SVG I'm using as an <img> tag. Using Styled Components I am trying to get to a point where I change the stroke color upon hover.
I imported the SVG:
import BurgerOpenSvg from '../../images/burger_open.svg';
I Created a Styled Components for it:
const BurgerImageStyle = styled.img`
&:hover {
.st0 {
stroke: red;
}
}
`;
And I use it:
<BurgerImageStyle alt="my-burger" src={BurgerOpenSvg}/>
The result is, my SVG is displayed correctly, but no color change upon hovering.
Source for the SVG I use:
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 38 28.4" style="enable-background:new 0 0 38 28.4;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#221f1f;stroke-width:2;stroke-miterlimit:10;}
</style>
<g>
<g id="XMLID_7_">
<line class="st0" x1="0" y1="1" x2="38" y2="1"/>
</g>
<g id="XMLID_6_">
<line class="st0" x1="0" y1="14.2" x2="38" y2="14.2"/>
</g>
<g id="XMLID_5_">
<line class="st0" x1="0" y1="27.4" x2="38" y2="27.4"/>
</g>
</g>
</svg>
The SVG Renders as follows:
Is it even possible to update the class on an SVG loaded in an <img> tag? or must it be inline <svg> tag?
So I looked into this. Turns out you cannot CSS style an SVG image you're loading using the <img> tag.
What I've done is this:
I inlined my SVG like this:
<BurgerImageStyle x="0px" y="0px" viewBox="0 0 38 28.4">
<line x1="0" y1="1" x2="38" y2="1"/>
<line x1="0" y1="14.2" x2="38" y2="14.2"/>
<line x1="0" y1="27.4" x2="38" y2="27.4"/>
</BurgerImageStyle>
Then I used Styled Components to style BurgerImageStyle:
const BurgerImageStyle = styled.svg`
line {
stroke: black;
}
&:hover {
line {
stroke: purple;
}
}
`;
This worked.
If you are looking to avoid writing separate components or copying your raw SVG file, consider react-inlinesvg;
https://github.com/gilbarbara/react-inlinesvg
import React from "react";
import styled from "styled-components";
import SVG from "react-inlinesvg";
import radio from "./radio.svg";
interface SVGProps {
color: string;
}
const StyledSVG = styled(SVG)<SVGProps>`
width: 24px;
height: 24px;
& path {
fill: ${({ color }) => color};
}
`;
export default function App() {
const color = "#007bff";
return <StyledSVG color={color} src={radio} />;
}
Code Sandbox: https://codesandbox.io/s/stack-56692784-styling-svgs-iz3dc?file=/src/App.tsx:0-414
If you want to have some styling shared across multiple SVGs and you don't want to have an extra dependency on react-inlinesvg you can use this thing instead:
In src prop it accepts SVG React component
import styled from 'styled-components';
import React, { FC, memo } from 'react';
type StyledIconProps = {
checked?: boolean;
};
const StyledIconWrapper = styled.div<StyledIconProps>`
& svg {
color: ${(p) => p.checked ? '#8761DB' : '#A1AAB9'};
transition: 0.1s color ease-out;
}
`;
export const StyledIcon = memo((props: StyledIconProps & { src: FC }) => {
const { src, ...rest } = props;
const Icon = src;
return (
<StyledIconWrapper {...rest}>
<Icon/>
</StyledIconWrapper>
);
});
And then you can use it like:
import { StyledIcon } from 'src/StyledIcon';
import { ReactComponent as Icon } from 'assets/icon.svg';
const A = () => (<StyledIcon src={Icon} checked={false} />)
In addition to what JasonGenX I propose the next case when you're using a SVG component (like one generated using SVGR). This is even on the styled-components documentation and in combination with its API it solves it seamlessly.
First import your icon
import React from 'react';
import styled from 'styled-components';
import YourIcon from '../../icons/YourIcon';
In my case I added a styled button like so:
const StyledButton = styled.button`
...
`;
// Provide a styled component from YourIcon
// You can also change the line for path and stroke for fill for instance
const StyledIcon = styled(YourIcon)`
${StyledButton}:hover & line {
stroke: #db632e;
}
`;
const YourButton = () => {
return (
<StyledButton>
<StyledIcon /> Click me
</StyledButton>
);
};
export default YourButton;
After that you'll see your icon changes its color.

Manipulate svg circle origin to create rotation animation around center

I have a loader (spinner) drawn on a page via two <circle />. Need to spin both paths in a different direction with origin centered, so, circles spin around the center of an SVG and don't translate, per say.
Trying to animate it transform: rotate(360deg). Paths go haywire and have origin somewhere else. Tried managing viewBox for intended results and didn't succeed.
import React, { PureComponent } from 'react';
import styled from 'styled-components';
import { prop } from 'styled-tools';
class Loader extends PureComponent {
render() {
return (
<Spinner
xmlns="http://www.w3.org/2000/svg"
width="200"
height="200"
preserveAspectRatio="xMidYMid"
viewBox="0 0 100 100"
>
<circle
className='outer'
cx="50"
cy="50"
r="40"
fill="none"
stroke="#374a67"
stroke-dasharray="63 63"
stroke-linecap="round"
stroke-width="4"
/>
<circle
className='inner'
cx="50"
cy="50"
r="35"
fill="none"
stroke="#d50000"
stroke-dasharray="55 55"
stroke-dashoffset="55"
stroke-linecap="round"
stroke-width="4"
/>
</Spinner>
)
}
}
const Spinner = styled.svg`
& .outer {
animation: rotate 2s linear infinite;
}
& .inner {
animation: reverseRotate 2s linear infinite;
}
#keyframes rotate {
100% {
transform: rotate(360deg);
}
}
#keyframes reverseRotate {
100% {
transform: rotate(-360deg);
}
}
`;
export default Loader;
Don't know how to make an actual working snippet out of my piece of code, sry
Here's an example of my current animation:
You need to set the transform-origin in the center of your svg. However you may do it differently. Instead of animating the transform you may animate the stroke-dashoffset like this:
.outer {
stroke-dashoffset:0;
animation: rotate 2s linear infinite;
}
.inner {
animation: reverseRotate 2s linear infinite;
}
#keyframes rotate {
100% {
stroke-dashoffset:126px;
}
}
#keyframes reverseRotate {
100% {
stroke-dashoffset:-55px;
}
}
svg{border:1px solid}
<svg xmlns="http://www.w3.org/2000/svg"
width="200"
height="200"
preserveAspectRatio="xMidYMid"
viewBox="0 0 100 100"
>
<circle
class='outer'
cx="50"
cy="50"
r="40"
fill="none"
stroke="#374a67"
stroke-dasharray="63"
stroke-linecap="round"
stroke-width="4"
/>
<circle
class='inner'
cx="50"
cy="50"
r="35"
fill="none"
stroke="#d50000"
stroke-dasharray="55"
stroke-dashoffset="55"
stroke-linecap="round"
stroke-width="4"
/>
</svg>
Welcome to Stack.
You need to make a few small tweaks to get it working.
Just use one animation that goes from 0% to 100%.
Animate from 0deg to 360deg
#keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
For the reverse animation, you can reverse the direction using
animation-direction: alternate; in your CSS

Resources