Encapsulate a NavLink component and style it with Styled Component - reactjs

I am trying to create a menu with hyperlinks from the react-rounter-dom library component NavLink
and style it with styled component
I Created a component called Link which is where NavLink is so you don't repeat this same line of code multiple times, then pass this component to styled component to inherit its properties so you can apply styles to it.
but the styles are not being applied to my component
NavLink
import { NavLink as NavLinkReactRouterDom } from "react-router-dom";
const Link = function ({ to, children, ...props }) {
return (
<>
<NavLinkReactRouterDom
{...props}
className={({ isActive }) =>
// console.log(isActive)
isActive ? "is-active" : undefined
}
to={to}
>
{children}
</NavLinkReactRouterDom>
</>
);
};
export default Link;
sidebarStyled.js (css)
import styled from "styled-components";
import Link from "./NavLink";
// NavLink
export const Prueba = styled(Link)`
color: white;
font-size: 50px;
text-decoration: none;
display: flex;
justify-content: flex-start;
align-items: stretch;
flex-direction: row;
&.is-active {
color: green;
}
`
Sidebar
const Sidebar = function () {
return (
<SidebarContainer>
<LogoContainer>
<img src={Logo} alt="Logo" />
</LogoContainer>
<h1>Sidebe Here</h1>
<Divider />
<Menu>
<NavList>
<NavItem>
<Prueba to="/">
<LinkIcon icon="typcn:home-outline" />
Inicio
</Prueba>
</NavItem>
</NavList>
</Menu>
</SidebarContainer>
);
};
export default Sidebar;

Issue
The className prop of the styled component, Prueba isn't passed through to the component it's attempting to style, the NavLinkReactRouterDom component. Or rather, it is passed implicitly when the props are spread into it, but NavLinkReactRouterDom is overriding and setting it's own className prop.
const Link = function ({ to, children, ...props }) {
return (
<>
<NavLinkReactRouterDom
{...props} // <-- styled-component className pass here
className={({ isActive }) => // <-- overridden here!
// console.log(isActive)
isActive ? "is-active" : undefined
}
to={to}
>
{children}
</NavLinkReactRouterDom>
</>
);
};
Solution
The solution is to merge the styled-component's className prop with the active classname used for the NavLinkReactRouterDom component.
Example:
const Link = function ({ to, children, className, ...props }) {
return (
<NavLinkReactRouterDom
{...props}
className={({ isActive }) =>
[className, isActive ? "is-active" : null].filter(Boolean).join(" ")
}
to={to}
>
{children}
</NavLinkReactRouterDom>
);
};

you may need to use Prueba, instead of Link. since you inherit the Link component, applied custom CSS and store it in the variable named Prueba.
hence import it in the sidebar.js file and use it there
refer: https://codesandbox.io/s/objective-smoke-1bflsh?file=/src/App.js
add
import 'Prueba' from sidebarStyled.js'
change
....
<Prueba to="/">
<LinkIcon icon="typcn:home-outline" />
Inicio
</Prueba>
....

Related

.map() in reactJs is not returning anything

i'm new to react and im trying to add render a list of items from an external SidebarData.js file (in the same root /components/..)
i'm not sure why my map function is not returning anything.
i get a list of elements thats correct, but the item.title and item.path seem not to render...
I feel there's a problem with the props.
I tried to write just
render(){
<h1>{SubmenuData[1].title}</h1>
}
and it works fine, but when i try to map on the full array, it doesn't seem to render anything. it renders the correct number of elements, but the title and path are not returning...
Here's my two components : Sidebar (Main one)
import React from 'react'
import styled from 'styled-components'
import { SidebarData } from './SidebarData'
import Submenu from './Submenu'
const Nav = styled.div`
background: #f5f5f5;
color: #7d7d7d;
display:flex;
justify-content:flex-start;
height:100%;
width:15%;
`
const Sidebar = () => {
return (
<>
<Nav>
{SidebarData.map((item, index)=>{
return <Submenu item={item} key={item.index} />
})}
</Nav>
</>
)
}
export default Sidebar (Where i think there's a problem)
and Submenu
import React, { Component } from 'react'
import styled from 'styled-components'
import { Link } from "react-router-dom"
const SidebarLink = styled(Link)`
display: flex;
color: #404040;
`
const SidebarLabel = styled.span`
color:#000;
`
const Submenu = (item)=>{
return (
<SidebarLink to={item.path} >
<SidebarLabel>{item.title}</SidebarLabel>
</SidebarLink>
)
}
export default Submenu
Your style of receiving props is mistake i guess. Destructure the props like:
const Submenu = ({item})=>{
return (
<SidebarLink to={item.path} >
<SidebarLabel>{item.title}</SidebarLabel>
</SidebarLink>
)
}
export default Submenu

How do I avoid 'Function components cannot be given refs' when using react-router-dom?

I have the following (using Material UI)....
import React from "react";
import { NavLink } from "react-router-dom";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
function LinkTab(link){
return <Tab component={NavLink}
to={link.link}
label={link.label}
value={link.link}
key={link.link}
/>;
}
In the new versions this causes the following warning...
Warning: Function components cannot be given refs. Attempts to access
this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of ForwardRef.
in NavLink (created by ForwardRef)
I tried changing to...
function LinkTab(link){
// See https://material-ui.com/guides/composition/#caveat-with-refs
const MyLink = React.forwardRef((props, ref) => <NavLink {...props} ref={ref} />);
return <Tab component={MyLink}
to={link.link}
label={link.label}
value={link.link}
key={link.link}
/>;
}
But I still get the warning. How do I resolve this issue?
Just give it as innerRef,
// Client.js
<Input innerRef={inputRef} />
Use it as ref.
// Input.js
const Input = ({ innerRef }) => {
return (
<div>
<input ref={innerRef} />
</div>
)
}
NavLink from react-router is a function component that is a specialized version of Link which exposes a innerRef prop for that purpose.
// required for react-router-dom < 6.0.0
// see https://github.com/ReactTraining/react-router/issues/6056#issuecomment-435524678
const MyLink = React.forwardRef((props, ref) => <NavLink innerRef={ref} {...props} />);
You could've also searched our docs for react-router which leads you to https://mui.com/getting-started/faq/#how-do-i-use-react-router which links to https://mui.com/components/buttons/#third-party-routing-library. The last link provides a working example and also explains how this will likely change in react-router v6
You can use refs instead of ref. This only works as it avoids the special prop name ref.
<InputText
label="Phone Number"
name="phoneNumber"
refs={register({ required: true })}
error={errors.phoneNumber ? true : false}
icon={MailIcon}
/>
In our case, we were was passing an SVG component (Site's Logo) directly to NextJS's Link Component which was a bit customized and we were getting such error.
Header component where SVG was used and was "causing" the issue.
import Logo from '_public/logos/logo.svg'
import Link from '_components/link/Link'
const Header = () => (
<div className={s.headerLogo}>
<Link href={'/'}>
<Logo />
</Link>
</div>
)
Error Message on Console
Function components cannot be given refs. Attempts to access this ref will fail.
Did you mean to use React.forwardRef()?
Customized Link Component
import NextLink from 'next/link'
import { forwardRef } from 'react'
const Link = ({ href, shallow, replace, children, passHref, className }, ref) => {
return href ? (
<NextLink
href={href}
passHref={passHref}
scroll={false}
shallow={shallow}
replace={replace}
prefetch={false}
className={className}
>
{children}
</NextLink>
) : (
<div className={className}>{children}</div>
)
}
export default forwardRef(Link)
Now we made sure we were using forwardRef in the our customized Link Component but we still got that error.
In order to solve it, I changed the wrapper positioning of SVG element to this and :poof:
const Header = () => (
<Link href={'/'}>
<div className={s.headerLogo}>
<Logo />
</div>
</Link>
)
If you find that you cannot add a custom ref prop or forwardRef to a component, I have a trick to still get a ref object for your functional component.
Suppose you want to add ref to a custom functional component like:
const ref = useRef();
//throws error as Button is a functional component without ref prop
return <Button ref={ref}>Hi</Button>;
You can wrap it in a generic html element and set ref on that.
const ref = useRef();
// This ref works. To get button html element inside div, you can do
const buttonRef = ref.current && ref.current.children[0];
return (
<div ref={ref}>
<Button>Hi</Button>
</div>
);
Of course manage state accordingly and where you want to use the buttonRef object.
to fix this warning you should wrap your custom component with the forwardRef function as mentioned in this blog very nicely
const AppTextField =(props) {return(/*your component*/)}
change the above code to
const AppTextField = forwardRef((props,ref) {return(/*your component*/)}
const renderItem = ({ item, index }) => {
return (
<>
<Item
key={item.Id}
item={item}
index={index}
/>
</>
);
};
Use Fragment to solve React.forwardRef()? warning
If you're using functional components, then React.forwardRef is a really nice feature to know how to use for scenarios like this. If whoever ends up reading this is the more hands on type, I threw together a codesandbox for you to play around with. Sometimes it doesn't load the Styled-Components initially, so you may need to refresh the inline browser when the sandbox loads.
https://codesandbox.io/s/react-forwardref-example-15ql9t?file=/src/App.tsx
// MyAwesomeInput.tsx
import React from "react";
import { TextInput, TextInputProps } from "react-native";
import styled from "styled-components/native";
const Wrapper = styled.View`
width: 100%;
padding-bottom: 10px;
`;
const InputStyled = styled.TextInput`
width: 100%;
height: 50px;
border: 1px solid grey;
text-indent: 5px;
`;
// Created an interface to extend the TextInputProps, allowing access to all of its properties
// from the object that is created from Styled-Components.
//
// I also define the type that the forwarded ref will be.
interface AwesomeInputProps extends TextInputProps {
someProp?: boolean;
ref?: React.Ref<TextInput>;
}
// Created the functional component with the prop type created above.
//
// Notice the end of the line, where you wrap everything in the React.forwardRef().
// This makes it take one more parameter, called ref. I showed what it looks like
// if you are a fan of destructuring.
const MyAwesomeInput: React.FC<AwesomeInputProps> = React.forwardRef( // <-- This wraps the entire component, starting here.
({ someProp, ...props }, ref) => {
return (
<Wrapper>
<InputStyled {...props} ref={ref} />
</Wrapper>
);
}); // <-- And ending down here.
export default MyAwesomeInput;
Then on the calling screen, you'll create your ref variable and pass it into the ref field on the component.
// App.tsx
import React from "react";
import { StyleSheet, Text, TextInput, View } from "react-native";
import MyAwesomeInput from "./Components/MyAwesomeInput";
const App: React.FC = () => {
// Set some state fields for the inputs.
const [field1, setField1] = React.useState("");
const [field2, setField2] = React.useState("");
// Created the ref variable that we'll use down below.
const field2Ref = React.useRef<TextInput>(null);
return (
<View style={styles.app}>
<Text>React.forwardRef Example</Text>
<View>
<MyAwesomeInput
value={field1}
onChangeText={setField1}
placeholder="field 1"
// When you're done typing in this field, and you hit enter or click next on a phone,
// this makes it focus the Ref field.
onSubmitEditing={() => {
field2Ref.current.focus();
}}
/>
<MyAwesomeInput
// Pass the ref variable that's created above to the MyAwesomeInput field of choice.
// Everything should work if you have it setup right.
ref={field2Ref}
value={field2}
onChangeText={setField2}
placeholder="field 2"
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
app: {
flex: 1,
justifyContent: "center",
alignItems: "center"
}
});
export default App;
It's that simple! No matter where you place the MyAwesomeInput component, you'll be able to use a ref.
I just paste here skychavda solution, as it provide a ref to a child : so you can call child method or child ref from parent directly, without any warn.
source: https://github.com/reactjs/reactjs.org/issues/2120
/* Child.jsx */
import React from 'react'
class Child extends React.Component {
componentDidMount() {
const { childRef } = this.props;
childRef(this);
}
componentWillUnmount() {
const { childRef } = this.props;
childRef(undefined);
}
alertMessage() {
window.alert('called from parent component');
}
render() {
return <h1>Hello World!</h1>
}
}
export default Child;
/* Parent.jsx */
import React from 'react';
import Child from './Child';
class Parent extends React.Component {
onClick = () => {
this.child.alertMessage(); // do stuff
}
render() {
return (
<div>
<Child childRef={ref => (this.child = ref)} />
<button onClick={this.onClick}>Child.alertMessage()</button>
</div>
);
}
}

Change hover style under custom component in styled-component

I have a below structure.
<Nav>
<Title/>
<DropDown />
</Nav>
<Nav /> is a class component and I need to show Dropdown when I hover over <Nav />.
Here is the code snippet of <Nav />.
export default class HeaderLink extends React.PureComponent {
...
}
Here is the code snippet of <DropDown />.
const Container = styled.ul`
opacity: 0;
visibility: hidden;
transform: translateY(20px);
transition: all .3s ease-in-out;
${Nav}:hover & {
opacity: 1;
visibility: visible;
transform: translateY(-2px);
}
`;
const DropDown = ({ items }) => (
<Container>
{items.map(({ title, url }) => (
<a href={url}>{title}</a>
))}
</Container>
);
DropDown.propTypes = {
items: PropTypes.array.isRequired
};
export default DropDown;
This is not working but I figured that If I define <Nav /> component as a styled-component, it works
i.e. const Nav = styled.ul''
But it's not working for the class component.
Any thoughts on this?
Thanks.
You're attempting to use a parent as a selector, which is not currently possible in CSS (see: Is there a CSS parent selector?). Your :hover should be on your Nav component, which in turn targets the appropriate child element.
See example CodeSandbox here: https://codesandbox.io/s/x9lmkply4.

Parent component that can get different components

I have a <Panel/> component that has to get different changing components.
For example one time- the <Panel/> needs to contain a <Dropdown/> component, a second time a <TextInput/> and in the third time a <Checkbox/>.
How can I make this <Panel/> component to get different components?
The <Panel/> component:
import React from "react";
import { css } from "emotion";
import colors from '../../styles/colors';
import PanelHeader from "./PanelHeader";
export default function Panel({ active, panelHeader}) {
const styles = css({
borderRadius: 4,
backgroundColor: "white",
border: `1px solid ${ active ? colors.blue : colors.grayLight }`,
width: 540,
padding: 32,
});
return (
<div className={styles}>
{panelHeader && <PanelHeader headerType={panelHeader} />}
</div>
);
}
The Panel story:
import React from "react";
import { storiesOf } from "#storybook/react";
import Panel from "../components/Panel";
import colors from '../styles/colors';
import PanelHeader from "../components/Panel/PanelHeader";
storiesOf("Panel", module)
.add("Default", () => (
<Panel></Panel>
))
.add("Active", () => (
<Panel active></Panel>
))
storiesOf("Panel/PanelHeader", module)
.add("Default", () => (
<PanelHeader headerType="Identity document" color={colors.gray}>1</PanelHeader>
))
.add("Active", () => (
<PanelHeader headerType="Identity document" color={colors.blue}>1</PanelHeader>
))
You can change Panel to accept children prop, pass it where you Render <Panel> and pass in the corresponding component.
For example:
// PropType for children is `PropTypes.node`
export default function Panel({ active, panelHeader, children}) {
// ...
return (
<div className={styles}>
{children}
</div>
);
}
// ...
<Panel><Dropdown /></Panel>
// or
<Panel><TextInput /></Panel>
Or, you could pass in a component class/function and render it inside:
export default function Panel({ active, panelHeader, ChildComponent}) {
// ...
return (
<div className={styles}>
{/* This is like any other component,
you can pass in props as usual. */}
{/* It's important for the name to start with an uppercase letter,
otherwise the JSX compiler will turn this in a string! */}
<ChildComponent />
</div>
);
}
// ...
<Panel ChildComponent={Dropdown}></Panel>
// or
<Panel ChildComponent={TextInput}></Panel>
This pattern is called component composition. You can read more in React docs: https://reactjs.org/docs/composition-vs-inheritance.html

disabling navlink react router

I am using react router in and I want to disable the to attribute in a certain state. I passed empty string, but that doesn't disable the link instead it takes to the base route of the page. I even tried to pass null but that breaks the code. Is it even possible to do so?
<NavLink className="nav-link" to={this.state.role == 4 ? "/pages" : ""}></NavLink>
You could try disabling the button with a custom click handler.
handleClick = (e) => {
const { linkDisabled } = this.state
if(linkDisabled) e.preventDefault()
}
render() {
return (
<NavLink
onClick={this.handleClick}
className="nav-link"
to="/pages"
>
...
</NavLink>
)
}
You might want to add some css for when the button is disabled
Alternatively you could just not show the button at all
{
this.state.linkDisabled ?
null :
<NavLink className="nav-link" to="/pages"></NavLink>
}
I used the same as Stretch0 , but i just change to functional component
only my confirmation is disabled
my styled links is the same of NavLink :
NavBar --> index.js
export default function NavBar() {
const handleClick = (e) => {
e.preventDefault()
}
return (
<Body>
<Header>
<Nav>
<StyledLink exact activeClassName="current" to="/">
<MenuLinks>CART</MenuLinks>
</StyledLink>
<StyledLink activeClassName="current" to="/payment">
<MenuLinks>PAYMENT</MenuLinks>
</StyledLink>
<StyledLink activeClassName="current" onClick={handleClick} to="/confirmation">
<MenuLinks>CONFIRMATION</MenuLinks>
</StyledLink>
</Nav>
</Header>
</Body>
)
}
NavBar ---> Styles.js
export const StyledLink = styled(NavLink)`
text-decoration: none;
color: #d6d6d6;
display: flex;
cursor: pointer;
${(props) => props.disabled && `
cursor: default;`}
&.${(props) => props.activeClassName} {
color: #fe8d3b;
}
&:focus,
&:hover,
&:visited,
&:link,
&:active {
text-decoration: none;
}
`
{
this.state.role !== 4 ?
:
<NavLink className="nav-link" to="/pages"></NavLink>
}
Another option would be to create your custom link wrapper component and to render the NavLink or not conditionally. In the following example, the property active determines if the link will be rendered or simply the text of the link.
function HeaderLink(props) {
if(props.active) {
return <NavLink {...props}>{props.children}</NavLink>
}
return <div className='link-disabled'>{props.children}</div>
}
Usage with e.g. state dependency within a navigation element:
<ul className='main-navigation'>
<li><HeaderLink to='/'>Personal</HeaderLink></li>
<li><HeaderLink to='/contact' active={state.personalDataComplete}>Contact</HeaderLink></li>
<li><HeaderLink to='/signup' active={state.contactDataComplete}>Sigup</HeaderLink></li>
</ul>

Resources