How to Style React-Icons - reactjs

I am trying to figure out how to style icons that I import using react-icons.
In particular, I would like to be able to create a look similar to this:
That is to say, I'd like to add a background color, padding, border-radius, etc. However, I can't find an easy way to do that.
I can add a size and color prop and that will change the actual size and color of the icon. But there is no easy way for me to change the other elements.
Does anyone know how I can do this (or can they recommend a different library that can help me with this)?

Use IconContext as mentioned in the Docs.
function BlueLargeIcon() {
return (
<IconContext.Provider
value={{ color: 'blue', size: '50px' }}
>
<div>
<FaBeer />
</div>
</IconContext.Provider>
);
}
To target specific Icon Components, you can use the style prop or use the same API (see Configurations) on the component itself:
const style = { color: "white", fontSize: "1.5em" }
<FaFacebookF style={style} />
// API
<FaFacebookF color="white" fontSize="1.5em" />

Colours of some icons can't be changed. For example, I tried to change the colour of { GrClose } icon by doing this:
<GrClose
className="icon"
style={{
position: 'absolute',
top: '20px',
right: '20px',
}}
size="50px"
color="white"
onClick={handleExit}
/>
It just didn't change, when I replaced my icon with { AiOutlineClose } icon it worked!

I think the easiest way here would be to just pass the className prop directly to the Icon you'd like to apply styles to. Source Code in a CodeSandbox. I've used tailwindcss classes here, but you could use your own just as easily.
import React from "react";
import "./styles.css";
import {
FaFacebookF,
FaTwitter,
FaInstagram,
FaPinterest
} from "react-icons/fa";
export default function App() {
let circleClasses = "inline-block p-7 rounded-full w-20 mx-auto";
let iconStyles = { color: "white", fontSize: "1.5em" };
return (
<div className="App grid grid-cols-2 sm:grid-cols-4 gap-2 w-3/4 mx-auto">
<h1 className="col-span-full">Icon Demo</h1>
<span style={{ background: "#3B5998" }} className={circleClasses}>
<FaFacebookF style={iconStyles} />
</span>
<span style={{ background: "#1DA1F2" }} className={circleClasses}>
<FaTwitter style={iconStyles} />
</span>
<span style={{ background: "black" }} className={circleClasses}>
<FaInstagram style={iconStyles} />
</span>
<span style={{ background: "#BD081C" }} className={circleClasses}>
<FaPinterest style={iconStyles} />
</span>
</div>
);
}
If you'd like a little more context as to how this works, we can take a dive into the source code for react-icons.
If you take a look at where the IconContext is defined within react-icons, you can see the allowed props:
export interface IconContext {
color?: string;
size?: string;
className?: string;
style?: React.CSSProperties;
attr?: React.SVGAttributes<SVGElement>;
}
export const DefaultContext: IconContext = {
color: undefined,
size: undefined,
className: undefined,
style: undefined,
attr: undefined,
};
export const IconContext: React.Context<IconContext> = React.createContext && React.createContext(DefaultContext);
Indeed, you're able to pass in color, size, style, additional svg attributes, and even a className string. But, this requires that you wrap the Icon component in the context.
When you install react-icons, all of the icons are added into the node_modules directory in a format that looks like this:
// THIS FILE IS AUTO GENERATED
var GenIcon = require('../lib').GenIcon
module.exports.Fa500Px = function Fa500Px (props) {
return GenIcon({"tag":"svg","attr":{"viewBox":"0 0 448 512"},"child":[{"tag":"path","attr":{"d":"M103.3 344.3c-6.5-14.2-6.9-18.3 7.4-23.1 25.6-8 8 9.2 43.2 49.2h.3v-93.9c1.2-50.2 44-92.2 97.7-92.2 53.9 0 97.7 43.5 97.7 96.8 0 63.4-60.8 113.2-128.5 93.3-10.5-4.2-2.1-31.7 8.5-28.6 53 0 89.4-10.1 89.4-64.4 0-61-77.1-89.6-116.9-44.6-23.5 26.4-17.6 42.1-17.6 157.6 50.7 31 118.3 22 160.4-20.1 24.8-24.8 38.5-58 38.5-93 0-35.2-13.8-68.2-38.8-93.3-24.8-24.8-57.8-38.5-93.3-38.5s-68.8 13.8-93.5 38.5c-.3.3-16 16.5-21.2 23.9l-.5.6c-3.3 4.7-6.3 9.1-20.1 6.1-6.9-1.7-14.3-5.8-14.3-11.8V20c0-5 3.9-10.5 10.5-10.5h241.3c8.3 0 8.3 11.6 8.3 15.1 0 3.9 0 15.1-8.3 15.1H130.3v132.9h.3c104.2-109.8 282.8-36 282.8 108.9 0 178.1-244.8 220.3-310.1 62.8zm63.3-260.8c-.5 4.2 4.6 24.5 14.6 20.6C306 56.6 384 144.5 390.6 144.5c4.8 0 22.8-15.3 14.3-22.8-93.2-89-234.5-57-238.3-38.2zM393 414.7C283 524.6 94 475.5 61 310.5c0-12.2-30.4-7.4-28.9 3.3 24 173.4 246 256.9 381.6 121.3 6.9-7.8-12.6-28.4-20.7-20.4zM213.6 306.6c0 4 4.3 7.3 5.5 8.5 3 3 6.1 4.4 8.5 4.4 3.8 0 2.6.2 22.3-19.5 19.6 19.3 19.1 19.5 22.3 19.5 5.4 0 18.5-10.4 10.7-18.2L265.6 284l18.2-18.2c6.3-6.8-10.1-21.8-16.2-15.7L249.7 268c-18.6-18.8-18.4-19.5-21.5-19.5-5 0-18 11.7-12.4 17.3L234 284c-18.1 17.9-20.4 19.2-20.4 22.6z"}}]})(props);
};
If we take a look at the source code for GenIcon we can get a bit more context for how this is working.
import * as React from 'react';
import { IconContext, DefaultContext } from './iconContext';
export interface IconTree {
tag: string;
attr: {[key: string]: string};
child: IconTree[];
}
function Tree2Element(tree: IconTree[]): React.ReactElement<{}>[] {
return tree && tree.map((node, i) => React.createElement(node.tag, {key: i, ...node.attr}, Tree2Element(node.child)));
}
export function GenIcon(data: IconTree) {
return (props: IconBaseProps) => (
<IconBase attr={{...data.attr}} {...props}>
{Tree2Element(data.child)}
</IconBase>
);
}
export interface IconBaseProps extends React.SVGAttributes<SVGElement> {
children?: React.ReactNode;
size?: string | number;
color?: string;
title?: string;
}
export type IconType = (props: IconBaseProps) => JSX.Element;
export function IconBase(props:IconBaseProps & { attr?: {} }): JSX.Element {
const elem = (conf: IconContext) => {
const {attr, size, title, ...svgProps} = props;
const computedSize = size || conf.size || "1em";
let className;
if (conf.className) className = conf.className;
if (props.className) className = (className ? className + ' ' : '') + props.className;
return (
<svg
stroke="currentColor"
fill="currentColor"
strokeWidth="0"
{...conf.attr}
{...attr}
{...svgProps}
className={className}
style={{ color: props.color || conf.color, ...conf.style, ...props.style}}
height={computedSize}
width={computedSize}
xmlns="http://www.w3.org/2000/svg"
>
{title && <title>{title}</title>}
{props.children}
</svg>
)
};
return IconContext !== undefined
? <IconContext.Consumer>{(conf: IconContext) => elem(conf)}</IconContext.Consumer>
: elem(DefaultContext);
}
So, GenIcon is a function that accepts an object with an interface of IconTree which includes tag, attr and child properties. We can see this in action in the generated code above. GenIcon returns a function itself. This function accepts props as an argument which implements the IconBaseProps interface. The IconBaseProps interface extends React.SVGAttributes which include className and style. Those are then passed as props to the IconBase component here:
export function GenIcon(data: IconTree) {
return (props: IconBaseProps) => (
<IconBase attr={{...data.attr}} {...props}>
{Tree2Element(data.child)}
</IconBase>
);
}
To get a sense of how IconBase actually works with respect to the IconContext let's check out its return statement:
return IconContext !== undefined
? <IconContext.Consumer>{(conf: IconContext) => elem(conf)}</IconContext.Consumer>
: elem(DefaultContext);
Here, we can see that IconBase will invoke a function called elem (defined inside the body of IconBase) either way. So, IconBase will use the IconContext if one is defined, wrapping the returned element in IconContext.Consumer and calling elem with the IconContext. But, if IconContext is not defined in this scope, IconBase will use the DefaultContext instead.
To understand how this is working, we need to look at the elem function defined inside of IconBase. Here's the full source again:
const elem = (conf: IconContext) => {
const {attr, size, title, ...svgProps} = props;
const computedSize = size || conf.size || "1em";
let className;
if (conf.className) className = conf.className;
if (props.className) className = (className ? className + ' ' : '') + props.className;
return (
<svg
stroke="currentColor"
fill="currentColor"
strokeWidth="0"
{...conf.attr}
{...attr}
{...svgProps}
className={className}
style={{ color: props.color || conf.color, ...conf.style, ...props.style}}
height={computedSize}
width={computedSize}
xmlns="http://www.w3.org/2000/svg"
>
{title && <title>{title}</title>}
{props.children}
</svg>
)
};
So, if you take a look at the code relating to className, you can see that we can actually add className to the Context and/or the props directly (<FaFacebook className="bg-blue" />) and they'll actually be combined and applied to the returned svg
let className;
if (conf.className) className = conf.className;
if (props.className) className = (className ? className + ' ' : '') + props.className;
Also, the styles defined in either the Context or the props will be combined and applied to the svg
style={{ color: props.color || conf.color, ...conf.style, ...props.style}}
One thing to note here is that all of the styles and classes are being applied directly to the returned svg element. So, in your case, you'd probably need to have a wrapping element to build out the border-radius and background color and then apply the relevant styles for color and size directly to the svg.

Simply add "color" property to
Like This:
<Icon color="blue" />

import {IconContext} from "react-icons";
class App extends component {
return (
<div>
<IconContext.Provider value={{ className="myReact-icons"}}>
<FaBeer />
</IconContext.Provider>
</div>
);
}
css example below
.myreact-icons { color: red;
height: 40px;
}

There is a simpler way to change the color of a react-icon.you can choose everything inside the svg icon with .icon > * use the css fill to fill the svg path.
```
.icon > * {
fill: #B3B3B3;
}
.icon > *:hover {
fill: #747474;
}
```

You can do it by simply adding colour property to icon, same goes for other properties like size.
<BiXCircle size={40} color="red" />

<HiOutlineInformationCircle
size= {40}
color="#777C92"
className = "m-1 cursor-pointer hover: stroke-[#E5765D]"
/>

Color changes on Grommet Icons don't apply. You can try with others either by passing a class and target it with css or directly inside <IconExample color={'blue'} />

function blackReactIcons () {
return(
<div>
<FaTwitter fill='#000' />
<FaLinkedinIn fill='#000' />
<FaGithub fill='#000' />
</div>
);
}

It is so easy just capsule your **icon** in **span tag**
<div className="social-icons">
<span>
<FaGithub/>
</span>
</div>
then style it using **CSS**
.social-icons span{
margin: 10px;
font-size: 20px;
color: var(--grey);
}
.social-icons span:hover{
color:black
}

You can export them as an object(s) with other properties and map through them.
import { FaInstagram, FaTwitter } from 'react-icons/fa';
export const socialIcons = [
{
icon: <FaInstagram
style={{ height: '30px', width: '30px' }}
/>,
title: 'Instagram',
link: '',
color: '#C13584',
isOpen: false,
id: 'instagram',
},
{
icon: <FaTwitter
style={{ height: '30px', width: '30px' }}/>,
title: 'Twitter',
link: '',
color: '#00acee',
isOpen: false,
id: 'twitter',
},
];
import { socialIcons } from '../';
// ...
<div>
{socialIcons.map((social) => (
<div>
<a href={social.link}>
<button style={{ color: social.color }}
id={social.id}>
{social.icon}
</button>
</a>
</div>
))}
</div>

In your styles.scss or css
.iconFaFacebook {
color: blue;
font-size: 2.5rem;
}
<FaFacebook className={styles.iconFaFacebook} />

Related

Material UI Rating wrapping long values

I am currently trying to work around the Material UI rating component and how to do a flex-wrap if the icons overflow the width of the parent component.
If I try to add flex-wrap: wrap to the rating component, it actually wraps the icons but the interactive functionality stops working pas the first line.
Here is a code example below to better demonstrate this:
Code Example in CodeSandbox
Is there a way to make it work with flex-wrap? If anyone could help I will very much appreciate.
I have decided that was better to build one by myself with the ability to wrap if the max value is big.
Will leave it here so someone who might have the same issue as me can use it.
CustomRating.js
import React, { useState } from 'react'
import { Tooltip } from '#mui/material'
import './CustomRating.css'
function CustomRating({ max, value, onChange, icon, emptyIcon }) {
const [innerValue, setInnerValue] = useState(value)
const checkIfIconInsideValue = (index) => {
return value >= index + 1
}
const handleMouseHover = (e, index) => {
if (e.type === 'mouseenter') {
setInnerValue(index)
return
}
setInnerValue(value - 1)
}
return (
<Tooltip title={innerValue} placement='top'>
<div className='custom-rating-main-div'>
{Array.from({ length: max }).map((elem, index) => {
return (
<div
className={`custom-rating-icon-div ${checkIfIconInsideValue(index) ? 'filled' : ''}`}
key={index}
onClick={() => onChange(index + 1)}
onMouseEnter={(e) => handleMouseHover(e, index)}
onMouseLeave={(e) => handleMouseHover(e, index)}
>
{checkIfIconInsideValue(index) || innerValue >= index ? icon : emptyIcon}
</div>
)
})}
</div>
</Tooltip>
)
}
export default CustomRating
CustomRating.css
.custom-rating-main-div {
display: flex;
flex-wrap: wrap;
}
.custom-rating-icon-div {
cursor: pointer;
}
.custom-rating-icon-div.filled > svg {
fill: #61634f
}
.custom-rating-icon-div > svg {
fill: rgba(97, 99, 79, 0.5)
}
.custom-rating-icon-div:hover > svg {
fill: #61634f;
transform: scale(1.2);
}
As you may notice this is specific to my problem but can be very easily adapted to any case.
keep in mind that this is very rough and can be updated to better follow conventions and for better performance, but for now it is my solution

How to resolve typescript error while exporting map component

I'm facing typescript error while exporting map component with GoogleApiWrapper. It is working fine I'm not wrapping it in GoogleApiWrapper, I tried multiple solutions but no one is working for me. Anyone please look over the code and provide any suggestion. with this issue I'm getting props issues when I'm calling this component with needful props.
The issue might be related to wrapping my component inside GoogleApiWrapper.
import { GoogleApiWrapper,Map, Marker } from "google-maps-react";
import React from "react";
import "./GoogleMap.css";
import Autocomplete from 'react-google-autocomplete';
import {connect} from 'react-redux'
export interface GoogleMapProps {
google?: any | undefined,
position: any,
mapStyles?:any,
mapColor?:any,
handleSelect:(place: any)=>void,
onAddressChange: (address: any) => void;
countryID:string,
}
const GoogleMap: React.FC<GoogleMapProps> = (props:GoogleMapProps ) => {
const {position,mapStyles,mapColor,handleSelect,countryID}=props;
const renderMap = () => {
return (
<div>
<div style={{ width: '100%', height: '270px' }}>
{
<Map
google={props.google}
initialCenter={position}
center={position}
zoomControl={false}
mapTypeControl={false}
style={mapStyles}
scaleControl={false}
streetViewControl={false}
fullscreenControl={false}
containerStyle={{
position: 'absolute',
width: '95%',
height: '40%',
borderRadius: '12px',
}}
onReady={(mapProps, map) => {
console.log("Map is ready")
}}
styles={mapColor}
>
<Marker
position={position}
/>
</Map>
}
</div>
<Autocomplete
className={"autocomplete_input"}
style={{ width: '100%' }}
placeholder={"Type your address"}
onPlaceSelected={(place: any) => {
handleSelect(place)
}}
types={['(regions)']}
componentRestrictions={{ country: countryID }}
/>
<svg className="svg_icon" width="23" height="25" viewBox="0 0 23 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.5789 20.7516L18.3102 16.0781C18.1175 15.8672 17.8563 15.75 17.5823 15.75H16.8844C18.0661 14.0953 18.7683 12.0141 18.7683 9.75C18.7683 4.36406 14.7822 0 9.86273 0C4.94326 0 0.957153 4.36406 0.957153 9.75C0.957153 15.1359 4.94326 19.5 9.86273 19.5C11.9307 19.5 13.8317 18.7313 15.3431 17.4375V18.2016C15.3431 18.5016 15.4501 18.7875 15.6428 18.9984L19.9115 23.6719C20.3139 24.1125 20.9647 24.1125 21.3629 23.6719L22.5746 22.3453C22.9771 21.9047 22.9771 21.1922 22.5789 20.7516ZM9.86273 15.75C6.83569 15.75 4.38238 13.0688 4.38238 9.75C4.38238 6.43594 6.83141 3.75 9.86273 3.75C12.8898 3.75 15.3431 6.43125 15.3431 9.75C15.3431 13.0641 12.8941 15.75 9.86273 15.75Z" fill="#96A7AF" />
</svg>
</div>
)
}
return (
<React.Fragment>
{renderMap()}
</React.Fragment>
)
}
export default GoogleApiWrapper(
() => ({
apiKey: "....key here...."
}
))(GoogleMap)
There are a lot of materials on google on React Higher-Order-Components, like this for example
Here's my suggestion, try if this works (at least TS compiler is not complaining about anything):
// make 'google' not optional to satisfy props for wrapper
export interface GoogleMapProps {
google: any,
position: any,
mapStyles?:any,
mapColor?:any,
handleSelect:(place: any)=>void,
onAddressChange: (address: any) => void;
countryID:string,
}
...
// compose components like this:
export default GoogleApiWrapper(({apiKey: "....key here...."}))<GoogleMapProps>((props) => (<GoogleMap {...props} />));

Why is gatsby-plugin-image missing image prop?

I am working on improving my image sizes and noticed that gatsby image was deprecated so I decided to try on the gatsby-plugin-image. On static images like this:
<StaticImage
src="../images/image.png"
alt="software design"
layout={'fullWidth'}
formats={['auto', 'webp']}
/>
is working fine. But when working on the images from netlify cms I get that following error Missing image prop even I have following:
<GatsbyImage
image={refImage}
alt={refImage}
layout={'fullWidth'}
formats={['auto', 'webp']}
/>
The whole file is as follows.
import React from 'react'
import PropTypes from 'prop-types'
import { GatsbyImage, getImage } from 'gatsby-plugin-image'
import * as S from './styled'
import './postitem.css'
const ReferenceItem = ({
slug,
background,
category,
date,
title,
description,
image,
timeToRead,
}) => {
const refImage = getImage(image)
return (
<S.BlogColumn>
<article className="post" key={slug}>
<S.BlogColumn>
{image && (
<GatsbyImage
image={refImage}
alt={refImage}
layout={'fullWidth'}
formats={['auto', 'webp']}
/>
/* <img
style={{
display: 'block',
width: '100%',
height: 'auto',
}}
src={`/${image}`}
alt={image}
/> */
)}
{!image && (
<img
style={{
display: 'block',
width: '100%',
height: 'auto',
}}
src={require('../../../static/assets/img/cover.webp')}
alt="cover"
/>
)}
</S.BlogColumn>
<S.BlogColumn>
<div className="post-content">
<h2 className="post-title">{title}</h2>
<p className="post-item-description">{description}</p>
<span className="post-date">
{date} —
</span>
<span className="post-words">
{timeToRead} minute read
</span>
</div>
</S.BlogColumn>
</article>
</S.BlogColumn>
)
}
ReferenceItem.propTypes = {
slug: PropTypes.string.isRequired,
background: PropTypes.string,
category: PropTypes.string,
date: PropTypes.string.isRequired,
timeToRead: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string,
}
export default ReferenceItem
The image needs to be of type GatsbyImageData as processed by gatsby-plugin-sharp or another source plugin that generates the correct format.
Additionally, the props you're passing to GatsbyImage will not work. StaticImage takes props, GatsbyImage needs that information to be passed to the sharp query that generates the images. For example,
{
image {
childImageSharp {
gatsbyImageData(layout: FULL_WIDTH)
}
}
}
They both can take props but Static Image can't get passed props from another component. No passing down to static image. The documentation for the plugin lists which props are for both, and which aren't.
The one that can take the passing down props is Dynamic one, GatsbyImage.
It seems that problem was react native version 0.65 where the `headerTransparent: true makes the button not work properly in android device. And is fixed in the next version.

React-bootstrap cards not wrapping

My Code is:
import React, { useEffect, useState } from "react";
import styles from "./Cards.module.css";
import { CardDeck, Card } from "react-bootstrap";
const Cards = ({ animeArray }) => {
const [aanimeArray, setAnimeArray] = useState([]);
useEffect(() => {
setAnimeArray(animeArray);
}, [animeArray]);
if (!aanimeArray) {
return;
}
console.log("Anime Array", aanimeArray);
return (
<div className={styles.container}>
{aanimeArray === [] ? (
<h1>Search</h1>
) : (
<CardDeck>
{aanimeArray.map((anime) => {
return (
<Card>
<Card.Img variant = "top" src={anime.image_url} />
<Card.Body>
<Card.Title>{anime.title}</Card.Title>
</Card.Body>
<Card.Footer>
<small className="text-muted">{anime.rated}</small>
</Card.Footer>
</Card>
);
})}
</CardDeck>
)}
</div>
);
};
export default Cards;
I am not using any custom styling whatsoever.
The result of the above mentioned code is as seen on this image:
Image of the issue
You have to make the effort of making them wrap. In fact, as seen on the documentation, majority of the developers' examples includes the CSS property width with a value of 18rem.
Here is an example by leveraging minWidth:
const sampleStyle = {
minWidth: "20%",
flexGrow: 0
};
<Card style={sampleStyle}>
First thing.
aanimeArray === []
won't work since you are comparing an array with another array.
Best way in Javascript for this is to check the length of the array.
aanimeArray.length === 0
means it is an empty array.
About the styling I think you need to show us the CSS code as well. I'm not sure what CardDeck component does...

Nested Match routes with React Router v4 and MatchWithFade

This question is a follow-up to this:
Trouble with React Router v4 and MatchWithFade
I've got another (potentially silly) question about using MatchWithFade and React Router v4. What I'd like to do is have nested routes, so a top-level component might have this:
<MatchWithFade pattern='/one' component={One} />
...and then One might have this:
<Match pattern='/one/one' Component={OneOne} />
This doesn't strike me as an unusual pattern (though maybe it is). In any event, the behavior I'm observing is that, using the above example, if I load OneOne, it gets mounted, and then componentWillUnmount is immediately called. Had I to guess, I'd say that TransitionMotion is keeping track of a (perhaps hidden) instance of OneOne, and once the transition is complete, it unmounts that hidden component. As far as the basic UI is concerned, OneOne is rendered. However, if componentWillUnmount does any cleanup (like, say, removing something from Redux), then of course that action is fired, and any data tied to OneOne is blown away.
Here's a complete example that illustrates the problem:
import React, { Component } from 'react';
import BrowserRouter from 'react-router/BrowserRouter'
import { TransitionMotion, spring } from 'react-motion'
import Match from 'react-router/Match'
import Link from 'react-router/Link';
const styles = {
fill: { position: 'absolute', top: 0, left: 0 }
};
const MatchWithFade = ({ component:Component, ...rest }) => {
const willLeave = () => ({ zIndex: 1, opacity: spring(0) })
return (
<Match {...rest} children={({ matched, ...props }) => {
return (
<TransitionMotion
willLeave={willLeave}
styles={matched ? [ {
key: props.location.pathname,
style: { opacity: 1 },
data: props
} ] : []}
>
{interpolatedStyles => {
return (
<div>
{interpolatedStyles.map(config => (
<div
key={config.key}
style={{...styles.fill, ...config.style }}>
<Component {...config.data}/>
</div>
))}
</div>
)
}}
</TransitionMotion>
)
}}/>
)
}
const TwoOne = () => {
return (
<div>Two One</div>
)
}
class TwoTwo extends Component {
componentWillUnmount() {
console.log("TwoTwo will unmount")
}
render () {
return (
<div>Two Two</div>
)
}
}
const TwoHome = () => {
return (
<div>Two Home</div>
)
}
class One extends Component {
componentWillUnmount () {
console.log("ONE UNMOUNTING")
}
render () {
return (
<div style={{ width: 300, border: '1px solid black', backgroundColor: 'orange', minHeight: 200}}>
One one one one one one one one one one<br />
One one one one one one one one one one<br />
</div>
)
}
}
const Two = () => {
return (
<div style={{ width: 300, border: '1px solid black', backgroundColor: 'yellow', minHeight: 200}}>
<Match pattern='/two/one' component={TwoOne} />
<Match pattern='/two/two' component={TwoTwo} />
<Match pattern='/two(/)?' exactly={true} component={TwoHome} />
</div>
)
}
class App extends Component {
render () {
return (
<BrowserRouter>
<div style={{padding: 12}}>
<div style={{marginBottom: 12}}>
<Link to='/one'>One</Link> || <Link to='/two'>Two</Link>
|| <Link to='/two/one'>Two One</Link>
|| <Link to='/two/two'>Two Two</Link>
</div>
<div style={{position: 'relative'}}>
<MatchWithFade pattern='/one' component={One} />
<MatchWithFade pattern='/two' component={Two} />
</div>
</div>
</BrowserRouter>
)
}
}
export default App;
If you load this and open a console, toggle between the One and Two links. You'll see the cross fade happen in the UI, and you'll see "ONE UNMOUNTING" in the console when the transition from One to Two completes. So that's right.
Now, click between Two One and Two Two. In this case, when Two One is clicked, you'll immediately see "TwoTwo will unmount" in the console, which is good. However, if you click Two Two, you'll see "TwoTwo will unmount" after about a second--which I take to be the amount of time the parent MatchWithFade takes to execute.
So I'm not sure what's going on here. Is my code just busted? Am I doing something that RRv4 cannot support? Have I uncovered a bug?
Any help/guidance is appreciated!
Your issue is your use of props.location.pathname as a key. This should always be the same for a component, but the way that you have written it, it changes each time that you navigate. Try changing this:
const styles = {
fill: { position: 'absolute', top: 0, left: 0 }
};
to:
const styles = {
fill: { position: 'relative', top: 0, left: 0 }
};
and you will see that you are rendering two instances of <Two> (one for each key).
If you were to use a constant key, such as rest.pattern (the pattern associated with this <Match>), your issue would go away.
styles={matched ? [ {
key: rest.pattern,
style: { opacity: 1 },
data: props
} ] : []}

Resources