Use 'active' state from React Router in Styled Components - react-router

React Router adds an active class to NavLinks when you are on the page that they link to. How can I access this property with Styled Components. I need to styled menu items differently when you are on their page.
const LinkElem = styled(NavLink)`
font-size: 16px;
font-weight: 400;
${props => console.log(props)};
&:hover {
color: ${props => props.theme.colorOrange};
}
`;
const Menu = props => {
const { me } = props;
return (
<MenuElem>
<li>
{me ? (
<LinkElem to="/account">Account</LinkElem>
) : (
<LinkElem to="/login">Log in / sign up</LinkElem>
)}
</li>
</MenuElem>
);
};

The prop className is getting added to the children of NavLink and so its not accessible at NavLink level. The docs were not clear about this. Therefore, we cannot check for props.className === 'active' and add styles.
Instead, you could just resort to css inside styled components for your use:
const LinkElem = styled(NavLink)`
// example style
&.active {
color: ${props => props.theme.orange }
}
`;

As of react-router v4, you can style the active state of NavLink without any dependencies using activeClassName and Styled Components' attrs() function:
export const StyledNavLink = styled(NavLink).attrs({
activeClassName,
})`
&.${activeClassName} {
background: red;
}
`;
Related documentation:
activeClassName
attrs()

const StyledLink = styled(NavLink)`
color: blue;
&.active {
color: red;
}
`;

If you are using object syntax in styled-components, you can implement the next solution:
const activeClassName = 'nav-item-active'
export const StyledLink = styled(NavLink).attrs({
activeClassName,
})(props => ({
height: '20px',
width: '20px',
backgroundColor: 'green',
['&.' + props.activeClassName]: {
backgroundColor: 'red'
}
}))
Declare variable with the name of the active class of react router
Style the NavLink and pass as attribute activeClassName
From the props get the activeClassName
Declare the conditional styles in case of the activeClassName being present
You can check a live example in the next StackBlitz project:
https://stackblitz.com/edit/react-hcudxz

Styled component:
import { NavLink } from 'react-router-dom';
export const MyNavLink = styled(NavLink)`
&.${props => props.activeClassName} {
color: red;
}
`;
Usage:
<MyNavLink to="/dashboard" activeClassName="anyClassNameWillWork">Dashboard</MyNavLink>
Tested with:
react-router-dom 5.1.2
styled-components 5.0.1

The string you added to NavLink's activeClassName will join to className later when you hit the specific route. Since styled components support pure css,
&.{yourActiveClassName} { #css goes here}
should work.

I'm not particularly keen on the &.active approach if you're trying to build a styled-component that is router independent, so I created asNavLink to deal with this:
npm install as-nav-link --save
Given a component:
const MyNavAnchor = styled(({
as: T = 'a',
active, // destructured so it is not passed to anchor in props
...props
}) => <T {...props} />)({
textDecoration: 'blink',
color: 'blue',
}, ({ active }) => (active && {
color: 'red',
}));
You can use it like this:
import asNavLink from 'as-nav-link';
const MyNavLink = asNavLink(config)(MyNavAnchor);
And it will pass down the active prop to your styled component.
config is optional and can include an isActive function (as per ReactRouter's NavLink) and an activeProp string (the prop name that is passed to your component).

I think it's simplest decision.
const StyledLink = styled(NavLink)`
color: blue;
&.${props => props.activeClassName} {
color: red;
}
`;

react-router now has activeStyle props, which can be used to style active link easily:
<NavLink
to="/faq"
activeStyle={{
fontWeight: "bold",
color: "red"
}}
>
FAQs
</NavLink>
With styled-components:
const LinkElem = styled(NavLink)`
font-size: 16px;
font-weight: 400;
`;
<LinkElem
activeStyle={{
fontWeight: 'bold',
color: 'red',
}}
></LinkElem>;

Related

How to customized an input component from another component?

For this input component, I want to make it as a basic component for other components.
import React, { InputHTMLAttributes } from "react";
import styled, { css } from "styled-components";
export const TextField: React.FC<InputHTMLAttributes<HTMLInputElement>> = (
props
) => {
return (
<Input type="text" {...props} />
);
};
const Input = styled.input`
${({ theme }) => css`
width: 100%;
color: ${theme.color.text};
`}
`;
This is a customized one and add some styles:
import React from "react";
import styled, { css } from "styled-components";
import { TextField } from "~/components";
export type ProductFieldProps = {
name: string;
value?: string;
};
type ProductFieldTypeProps = {
textAlign: string;
};
export const ProductField: React.FC<ProductFieldProps> = ({
name,
value,
}) => {
return (
<StyledTextField textAlign="center">
<TextField name={name} value={value} />
</StyledTextField>
);
};
const StyledTextField = styled(TextField)`
${({ theme }) => css<ProductFieldTypeProps>`
&:hover {
border-color: ${theme.color.hover};
}
`}
`;
When use the new component ProductField,
# pages/index.tsx
import { ProductField } from "~/components";
...
return {
<>
<ProductField name="aaa" value="bbb" />
</>
}
I got a server error:
Error: input is a void element tag and must neither have children nor use dangerouslySetInnerHTML.
How to set correctly?
You are using styled-components to style your Input component.
You can see on this line that you are applying styles to the TextField component:
const StyledTextField = styled(TextField)
And yet in the new ProductField component you import both, the styled version and the original one and nest them in each other:
<StyledTextField textAlign="center">
<TextField name={name} value={value} />
</StyledTextField>
This essentially means you are nesting two input field into each other, which is not allowed and hence the error:
Error: input is a void element tag and must neither have children nor use dangerouslySetInnerHTML.
One way to approach this is to simply remove the nested TextField like so, and apply all the props to the styled one instead:
<StyledTextField textAlign="center" name={name} value={value} />
Another solution would be to just export and use the styled version directly because the additional wrapper seems redundant.
More info on how to style custom components with styled-components

Getting active background color using useRef

Given the css
.App {
font-family: sans-serif;
text-align: center;
}
.App:hover {
background-color: red;
}
and jsx code
import "./styles.css";
import { useRef } from "react";
export default function App() {
const r = useRef();
const handleClick = () => {
console.log(r.current.style.backgroundColor);
};
return (
<div className="App" ref={r} onClick={handleClick}>
something
</div>
);
}
See codesandbox
I want to get the active background color of div (which is red). But the code give me nothing. What's the right way of doing that?
.style only tells you the inline style on the element, and this div has no inline style. If you want to know the style that results when combining inline style and css selectors, you need getComputedStyle
const handleClick = () => {
console.log(window.getComputedStyle(r.current).backgroundColor);
}
el.style.X is not working correctly. Insteadof this use window.getComputedStyle(el) and u can get full list of styles. As a example u can see it.

Utilise button-onClick event instead of input-onChange event to change state in React

I am currently experimenting with React and I am trying to change the background colour of a div according to what the user enters. I have created an Input-component with the input-element with a button, apart from the App-component, however I am unable to type in the input-element without the onChange-event, which I expected. I am unsure of how to change the state, of the state variable ('color' in App.js) with a button click instead of the onChange-event.
My App.js
const AppDiv = styled.div
`margin: 0;
box-sizing: border-box;
display: flex;
width: 100%;
`
class App extends Component {
state = {
color: ' ',
name: null
}
colorchange = (event) => {
event.preventDefault();
this.setState({
color: event.target.value
})
}
render(){
return (
<AppDiv>
<Input
name = {this.state.name}
colour = {this.state.color}
colourChange = {this.colorchange}
Changecolour = {this.changecolour}
/>
</AppDiv>
)
}
}
Input.js
const ColorButton = styled.button
`
width: 100px;
height: 50px;
border-radius: 24px/50%;
background-color: green;
margin: 10px;
`
const ColorDiv = styled.div
`
height: 100vh;
flex-basis: 300px;
background-color: ${props => props.colour}; //Changing background with 'colour' prop
`
const input = (props) => {
return (
<ColorDiv>
<h2>What is your name</h2>
<input type = "text" value = {props.name}/>
<h2>Choose your colour</h2>
<input type = "text" value = {props.colour} />
<ColorButton onClick = {props.colourChange}> Change Colour </ColorButton>
</ColorDiv>
)
}
I am using styled-components to apply styling. Any suggestions or help will be appreciated. Thank you
The best way to achieve what you are doing is to have 2 pieces of state. One for the text you have inputted in the <input /> and one for the color you want the background to be.
In the App
state = {
colorText: null,
backgroundColor: null,
}
onTextChange = (e) => {
this.setState({
colorText: event.target.value
})
}
onUpdateBackgroundColor = () => {
this.setState({
backgroundColor: this.state.colorText
})
}
.......
<Input
colour = {this.state.colorText}
backgroundColor={this.state.backgroundColor}
onTextChange = {this.onTextChange}
updateBackgroundColor = {this.onUpdateBackgroundColor}
/>
then in Input.tsx
background-color: ${props => props.backgroundColor};
<input type="text" value={props.colour} onChange={props.onTextChange} />
<ColorButton onClick={props.updateBackgroundColor}> Change Colour </ColorButton>
Note: To make this better, you should move the state into Input as keeping state in a parent component and updating it on every input change will cause a lot of renders
You can either user a ref for you input and not having it controlled
export default function App() {
const [color, setColor] = React.useState("");
const inputRef = React.useRef();
return (
<div className="App">
<h1>Your Color: {color}</h1>
<input ref={inputRef} />
<button
onClick={() => {
setColor(inputRef.current.value);
}}
>
Update Color
</button>
</div>
);
}
Or having separated state for your input:
export default function App() {
const [color, setColor] = React.useState("");
const [inputValue, setInputValue] = React.useState("");
return (
<div className="App">
<h1>Your Color: {color}</h1>
<input
value={inputValue}
onChange={({ target: { value } }) => {
setInputValue(value);
}}
/>
<button
onClick={() => {
setColor(inputValue);
}}
>
Update Color
</button>
</div>
);
}

How change the size of UI Chip dynamically

In the following example, I am trying to change the size the UI chip in dynamically in order to respond to the font size of its parents using the em css unit.
My goal: I want to do something like this
style={{size:'1em'}}
My problem: the chip element in material-ui is not resizable like most of the material-UI components.
I tried:
style={{transform:'scale(1em)'}} but it did not work at all. I don't know how to change the anchor point of transform.
I also tried to create my own chip with <img style={{float: 'left', width: '1em', borderRadius: '50%',}}/> but it does not look original as the material UI chip.
import Avatar from '#material-ui/core/Avatar'
import Chip from '#material-ui/core/Chip'
function Chips() {
const classes = useStyles()
const handleDelete = () => {
console.info('You clicked the delete icon.')
}
const handleClick = () => {
console.info('You clicked the Chip.')
}
return (
<div className={classes.root}>
<h1>
<Chip
//style={{size:'1em'}}
avatar={<Avatar alt="Natacha" src="/static/images/avatar/1.jpg" />}
label="Deletable"
onDelete={handleDelete}
/>
</h1>
<h4>
<Chip
//style={{size:'1em'}}
avatar={<Avatar alt="Natacha" src="/static/images/avatar/1.jpg" />}
label="Deletable"
onDelete={handleDelete}
/>
</h4>
</div>
)
}
currently, Chip doesn't support the prop for size (Hope they support it in the future 🤞).
For this, you've to make your own custom component. I've created one name CustomChip.
In this, pass a prop named size, and the rest of the sizes of the chip scales accordingly.
CustomChip.js
function CustomChip(props) {
const { size = 1, ...restProps } = props;
const classes = useStyles({ size });
return (
<Chip
className={classes.root}
classes={{ avatar: classes.avatar, deleteIcon: classes.deleteIcon }}
{...restProps}
/>
);
}
const useStyles = makeStyles((theme) => ({
root: {
fontSize: (props) => `${props.size * 0.8125}rem`,
height: (props) => `${props.size * 32}px`,
borderRadius: "9999px"
},
avatar: {
"&&": {
height: (props) => `${props.size * 24}px`,
width: (props) => `${props.size * 24}px`,
fontSize: (props) => `${props.size * 0.75}rem`
}
},
deleteIcon: {
height: (props) => `${props.size * 22}px`,
width: (props) => `${props.size * 22}px`,
color: "green"
}
}));
Here the provided size gets multiplied by the default sizes of the parts.
use:-
<CustomChip
size={2}
avatar={<Avatar alt="Natacha" src="/static/images/avatar/1.jpg" />}
label="Deletable"
onDelete={handleDelete}
/>
Working sandbox link:-

React customize Material-UI Icons styles

I have a React.js app with Typescript. I want to disable visited Material Icons coloring on an anchor tag and I have the following stylesheet.
const useStyles = makeStyles((theme: Theme) =>
createStyles(
myAnchor: {
"&:visited": {color: "inherit"},
"&:hover": {color: "inherit"},
"&:active": {color: "inherit"}
}
...
)
const classes = useStyles();
But it did not work when I did <a className={classes.myAnchor}><FacebookIcon /></a>. Did I get anything wrong with "&:visited"?
You can use Material-UI IconButtn
import React from "react";
import "./styles.css";
import { makeStyles, IconButton } from "#material-ui/core";
import FacebookIcon from "#material-ui/icons/Facebook";
const useStyles = makeStyles(theme => ({
icon: {
"& :visited": { color: "red" },
"& :hover": { color: "red" },
"& :active": { color: "red" }
}
}));
export default function App() {
const classes = useStyles();
return (
<div className="App">
<IconButton
className={classes.icon}
// component={Link}
// to={`/url`}
>
<FacebookIcon />
</IconButton>
</div>
);
}