I'm pretty new to React,
And I was given a simple home assignment.
I was asked to create 3 kinds of a clickable circle component.
The JSX is common to all (SVG),
But their
size
background color
onClick callback
are different.
What is the correct way nowadays (2019, React 16) to get such a common base class/
interface in React ?
HOC ? React Hooks ? some other way ?
Say this is my "base" Circle component
(Please ignore the hard coded props):
export default function Circle(props) {
return (
<svg height="100" width="100">
<circle cx="50" cy="50" r="26" fill="#9cc6d4" />
<circle
cx="50"
cy="50"
r="20"
stroke="#FFF"
strokeWidth="1"
fill="none"
/>
</svg>
);
I want to make myself clear:
I've only 3 different types of that basic Circle
(Say Red, Blue and Yellow...)
And I need to put them on screen according
to a JSON.
I don't want to DRY and create
3 different classes: RedCircle, BlueCircle and YellowCircle...
which eventually will be based on the same JSX
Your help is highly appreciated.
You can easily do these cutomizations with props..
const {size, onClick, bgColor} = props;
// now use the size, onClick and bgColor props in your JSX markup
return (
<svg height="100" width="100">
<circle cx="50" cy="50" r="26" fill="#9cc6d4" />
<circle
cx="50"
cy="50"
r="20"
stroke="#FFF"
strokeWidth="1"
fill="none"
/>
</svg>
);
Using the Circle component would look like
<Circle size={'40'} onClick={()=>{ console.log('im red circle'}) bgColor={'red'} />
<Circle size={'35'} onClick={()=>{ console.log('im green circle'}) bgColor={'green'} />
You can pass these infos, by using the props parameter of the function component.
Your component would look something like that:
export default function Circle(props) {
return (
<svg height={props.size} width={props.size}>
<circle cx="50" cy="50" r="26" fill={props.color} />
<circle
cx="50"
cy="50"
r="20"
stroke="#FFF"
strokeWidth="1"
fill="none"
/>
</svg>
);
}
Then, when used in parent components:
<Circle size={yourSize} color={yourColor}/>
In this case, size etc... would be available in the function as props.size etc...
You can do the same and pass event handlers like onClick etc...
Reference:
https://reactjs.org/docs/components-and-props.html
Related
I made a component for a Svg and I'm trying to apply onClick event so I change the state, but it doesnt work for some reason, I'm not sure what I'm doing wrong. I tried applying the onCLick on too , but it doesnt work either.
my code
import React, { useState } from "react";
import './style.scss'
const AverageSvg=() => {
const [active, setActive] = useState(false);
return (
<svg className="average" onClick={() => setActive(false)}
xmlns="http://www.w3.org/2000/svg"
width="170.539"
height="51.974"
viewBox="0 0 170.539 51.974"
>
<g data-name="The average" transform="translate(-1223 -2501)" >
<g className={active ? "clicked-fill" : "fill "}
// fill="none"
stroke="#707070"
strokeWidth="1"
data-name="Rectangle 60"
transform="translate(1223 2501)"
>
<rect
width="170.539"
height="51.974"
stroke="none"
rx="25.987"
></rect>
<rect
width="169.539"
height="50.974"
x="0.5"
y="0.5"
rx="25.487"
></rect>
</g>
<text className="text"
// fill="#464646"
data-name="The average"
fontFamily="ArialMT, Arial"
fontSize="17"
transform="translate(1261 2532)"
>
<tspan x="0" y="0" >
The average
</tspan>
</text>
</g>
</svg>
);
}
export default AverageSvg;
Have you tried wrapping the svg with another tag like a div or span and attaching the onClick on that wrapper ?
Also inside the setActive() you should pass true instead of false.
you can warp the SVG in another tag as Alexader says, also you can pass the event to the child element in the SVG tag directly and it will work. but you should be aware of the browser compatibility
check this link from MDN web Docs
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/onclick
for example, this is an SVG delete icon
export const Delete = ({ ...props }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" className={props?.className} >
<path onClick={props?.onClick} d="M7 21q-.825 0-1.412-.587Q5 19.825 5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413Q17.825 21 17 21Zm2-4h2V8H9Zm4 0h2V8h-2Z" />
</svg>
)
}
when you call it just <Delete onClick={yourEvent} />
the onClick will apply on the <path/> tag
I can change the fill of an SVG using it as a component
import { ReactComponent as Icon} from '../assets/icon.svg'
...
<Icon fill='#000' />
With "current" on the fill field inside the SVG file
<path fill="current" />
But I have two differents path. How can I set them to have differents fills?
<path fill="current" />
<path fill="otherColor?" />
I managed to use different colors by creating a component that return that SVG as JSX passing colors as props
const Icon = ({inside, outside}) => (
<svg>
<g>
<path fill={outside} />
<path fill={inside} />
</g>
</svg>
)
<Icon outside='#000' inside='red'/>
I expect the svg to turn red but it isn't changing at all from the color #D8D8D8. I see the fill in the svg and if I turn this into a parameter I can use it then, but I'd rather just set the fill using React props so I can set a hover via my css:
The svg code (which I got my my Sketch export):
<svg width="57px" height="33px" viewBox="0 0 57 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Rectangle</title>
<g id="Design" stroke="none" stroke-width="1" fill-rule="evenodd">
<g id="Mobile-Copy-6" transform="translate(-196.000000, -69.000000)" fill="#D8D8D8">
<rect id="Rectangle" x="196" y="69" width="57" height="33"></rect>
</g>
</g>
</svg>
My react code:
import { ReactComponent as Logo } from "../../assets/rect.svg";
export const Example = () => {
return (
<div>
<Logo fill="#C94141" />
</div>
);
};
I used create-react-app with the typescript template and the Logo is identified as the type:
React.FunctionComponent<React.SVGProps<SVGSVGElement>
you need to create you Logo as a component with passing props to it which you want to keep dynamic. Try creating a component like:
export default ({ fill = '#D8D8D8' }) => (
<svg width="57px" height="33px" viewBox="0 0 57 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Rectangle</title>
<g id="Design" stroke="none" stroke-width="1" fill-rule="evenodd">
<g id="Mobile-Copy-6" transform="translate(-196.000000, -69.000000)" fill={fill}>
<rect id="Rectangle" x="196" y="69" width="57" height="33"></rect>
</g>
</g>
</svg>
);
and then use this component simply by
import Logo from "../../components/logo.js";
export const Example = () => {
return (
<div>
<Logo fill="#C94141" />
</div>
);
};
If I render SVG elements to a defined SVG object, then all is well.
If I render an entire SVG object, the resulting image doesn't scale.
If you look at the following demo, the 1st svg renders correctly. The svg fills the width of the browser and everything scales.
The second svg is a 300x150 image that doesn't scale at all.
https://codepen.io/brunnock/pen/ydMdgv
// The following renders as expected.
function SVG1() {
return (
<g>
<rect height="100%" width="100%" fill="#ccc" />
<circle cx={150} cy={75} r={50} fill="red" />
<text x="100" y="275" font-size="50">
This is the right size.
</text>
</g>
);
}
// svg1 is a predefined svg element in the HTML file.
ReactDOM.render(<SVG1 />, svg1);
// The following renders a 300x150 image that doesn't scale.
function SVG2() {
return (
<svg viewbox={"0 0 600 600"}>
<rect height="100%" width="100%" fill="#ccc" />
<circle cx={150} cy={75} r={50} fill="red" />
<text x="100" y="275" font-size="50">
This is not the right size.
</text>
</svg>
);
}
// svg2 is a div.
ReactDOM.render(<SVG2 />, svg2);
I couldn't find any difference in the rendered html via Chrome's inspector.
Remove the parentheses from the viewBox
function SVG2() {
return (
<svg viewbox="0 0 600 600">
<rect height="100%" width="100%" fill="#ccc" />
<circle cx={150} cy={75} r={50} fill="red" />
<text x="100" y="275" font-size="50">
This is not the right size.
</text>
</svg>
);
}
I'm an idiot. Change "viewbox" to "viewBox" and "font-size" to "fontSize".
I am trying to use a chip with SVG delete icon,
The icon code is
const icon = (props) => {
return (
<SvgIcon>
<img src={'ic_check.svg'} style={{width: '20px'}} width={'20px'}/>
</SvgIcon>
)
};
The content of SVG file
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g fill="none" fill-rule="evenodd">
<rect width="20" height="20" x="2" y="2" fill="#6FB934" rx="10"/>
<path fill="#FFF" fill-rule="nonzero" d="M9.5 15.475L6.025 12l-1.183 1.175L9.5 17.833l10-10-1.175-1.175z"/>
<path d="M0 0h24v24H0z"/>
</g>
</svg>
and the chip finally is
<Chip
label={ViewUtils.NOT_EXPIRED}
className={classes.chip}
onDelete={() => {}}
deleteIcon={<icon/>}/>
But this is not working and I checked for the path and it is correct as I can render same svg in img tag.
Nayan SvgIcon takes a svg path that you can further style. But in your case your svg is already styled. It doesn't take svg file directory path which actually lose SvgIcon API purpose.
You just need to remove SvgIcon from img tag:
<Chip
label={ViewUtils.NOT_EXPIRED}
className={classes.chip}
onDelete={() => {console.log('You Deleted this icon')}}
deleteIcon={icon}
/>
and Make you svg as const or import from assets file directory I haven't tried that,
const icon = <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g fill="none" fill-rule="evenodd">
<rect width="20" height="20" x="2" y="2" fill="#6FB934" rx="10"/>
<path fill="#FFF" fill-rule="nonzero" d="M9.5 15.475L6.025 12l-1.183 1.175L9.5 17.833l10-10-1.175-1.175z"/>
<path d="M0 0h24v24H0z"/>
</g>
</svg>
There is a reason why we can't make <icon/> component. If we make it as component as following:
const Icon = (props) => {
return (
<SvgIcon>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<g fill="none" fill-rule="evenodd">
<rect width="20" height="20" x="2" y="2" fill="#6FB934" rx="10" />
<path
fill="#FFF"
fill-rule="nonzero"
d="M9.5 15.475L6.025 12l-1.183 1.175L9.5 17.833l10-10-1.175-1.175z"
/>
<path d="M0 0h24v24H0z" />
</g>
</svg>
</SvgIcon>
)
};
It works like a charm but onDelete doesn't get fired on this component.I've reported this issue as well on material UI. In first case onDelete gets called every time. Feel free to ask any question.
EDITED Fixed the above issue for Icon as Component rather than const. here is the codesandbox link for working example:
https://codesandbox.io/s/98842r4yy4
I am using React and typescript, created a component and added the tags from HTML and it worked normally.
export const IconTable: React.FC = () => {
return (
<svg width="130" height="130" viewBox="0 0 1024 1024">
<path d="M505.947 123.597a23.096 23.096 0 0 0-16.99-7.477h-6.837c-17.929 0-32.631 13.468-34.198 "/>
</svg>
);