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>
);
}
}
I'm able to change a styled components styles based on state but only at page load, if I toggle the state after page load, the state changes successfully but the styles remain the same. The styled component whose styles I'm trying to change is "S.Search" which is inside of a "Search" component whose parent is "Navbar". I'm passing "Search" an "isVisible" state as a prop that is toggled by the onClick event handler in the parent component "Navbar".
The parent component:
import React, { useContext, useState } from "react"
import Search from "../components/Search"
import * as S from "../styled/Navbar"
const Navbar = () => {
const [isVisible, setVisibility] = useState(false)
return (
<S.Navbar>
/* S.SearchButton toggles the isVisible state onclick */
<S.SearchButton onClick={() => setVisibility(!isVisible)}>
<S.SearchButtonText>Search</S.SearchButtonText>
</S.SearchButton>
/* Passing isVisible state into the Search component as props */
<Search isVisible={isVisible} setVisibility={setVisibility} />
</S.Navbar>
)
}
export default Navbar
The search component:
import React, { useState } from "react"
import Icon from "../components/Icon"
import * as S from "../styled/Search"
const Search = props => {
return (
<S.Search>
<S.InputWrapper>
<S.SearchButtonIcon>
<Icon icon={"search"} />
</S.SearchButtonIcon>
<S.Input
type="text"
placeholder="Search for a character"
autocomplete="off"
/>
<S.ChampList></S.ChampList>
</S.InputWrapper>
</S.Search>
)
}
export default Search
I'm sure my state is being toggled correctly, but I have no idea why the style isn't being updated when toggling the state after page load. This is the styled component that has access to the isVisible state via props passed to the search component:
import styled from "styled-components"
export const Search = styled.div`
height: ${p => (!p.isVisible ? `0` : `2.5rem`)};
visibility: ${p => (!p.isVisible ? `hidden` : `visible`)};
opacity: ${p => (!p.isVisible ? `0` : `100`)};
display: flex;
justify-content: center;
transition: ${p => p.theme.easings.easeOut};
`
How can I change the style after page load when I toggle the state? Thank you for your time!
Your style isnt being applied because you are not passing isVisible prop to Search style-component.
You just need to do this in your Search Component:
const Search = props => {
return (
// HereĀ“s the trick
<S.Search isVisible={props.isVisible}>
.....
.....
.....
What I want:
I'm trying to add a dynamic theme option to a react-styleguidist project I'm working on. Following the idea laid out in this unfinished and closed pr, I added a custom ThemeSwitcher component, which is a select menu that is rendered in the table of contents sidebar. Selecting an option should update the brand context, which renders the corresponding theme using styled-components' BrandProvider. It should function like the demo included with the closed pr: https://fancy-sg.surge.sh/.
What's not working:
I can't access the same context in my ThemedWrapper as is provided and updated in the StyleguideWrapper and ThemeSwitcher. Examining the tree in the React Components console, it looks like react-styleguidist may render ReactExample outside of the StyleguideRenderer, which means it loses the context from the provider in that component.
Assuming I'm correct about the context not updating in ThemedWrapper due to it being located outside of StyleGuideRenderer, two high level ideas I have (but haven't been able to figure out how to do) are:
Find the correct component that is an ancestor of both StyleGuideRenderer and ReactExample in the react-styleguidist library and add the BrandProvider there so that ThemedWrapper now has context access
Some other context configuration that I haven't found yet that will allow two components to consume the same context without having a provider as an ancestor (is this possible??)
What I have:
Here are the condensed versions of the relevant code I'm using.
brand-context.js (exports context and provider, inspired by Kent C Dodds
import React, { createContext, useState, useContext } from 'react';
const BrandStateContext = createContext();
const BrandSetContext = createContext();
function BrandProvider({ children, theme }) {
const [brand, setBrand] = useState(theme);
return (
<BrandStateContext.Provider value={brand}>
<BrandSetContext.Provider value={(val) => setBrand(val)}>
{children}
</BrandSetContext.Provider>
</BrandStateContext.Provider>
);
}
function useBrandState() {
return useContext(BrandStateContext);
}
function useBrandSet() {
return useContext(BrandSetContext);
}
export { BrandProvider, useBrandState, useBrandSet };
StyleGuideWrapper.jsx (Copy of rsg-components/StyleguideRenderer, with addition of ThemeSwitcher component to toggle theme from ui; passed in styleguide config as StyleGuideRenderer)
import React from 'react';
import cx from 'clsx';
import Styled from 'rsg-components/Styled';
import ThemeSwitcher from './ThemeSwitcher';
import { BrandProvider } from './brand-context';
export function StyleGuideRenderer({ children, classes, hasSidebar, toc }) {
return (
<BrandProvider>
<div className={cx(classes.root, hasSidebar && classes.hasSidebar)}>
<main className={classes.content}>
{children}
</main>
{hasSidebar && (
<div className={classes.sidebar} data-testid="sidebar">
<section className={classes.sidebarSection}>
<ThemeSwitcher classes={classes} />
</section>
{toc}
</div>
)}
</div>
</BrandProvider>
);
}
StyleGuideRenderer.propTypes = propTypes;
export default Styled(styles)(StyleGuideRenderer);
ThemeSwitcher.jsx
import React from 'react';
import Styled from 'rsg-components/Styled';
import { useBrandSet, useBrandState } from './brand-context';
const ThemeSwitcher = ({ classes }) => {
const brand = useBrandState();
const setBrand = useBrandSet();
const onBrandChange = (e) => setBrand(e.target.value);
const brands = ['foo', 'bar'];
return (
<label className={classes.root}>
Brand
<select value={brand} onChange={onBrandChange}>
{brands.map((brand) => (
<option key={brand} value={brand}>{brand}</option>
))}
</select>
</label>
);
};
export default Styled(styles)(ThemeSwitcher);
ThemedWrapper.jsx (passed in styleguide config as Wrapper, and wraps each example component to provide them to styled-components)
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { BrandStateContext } from './brand-context';
const LibraryProvider = ({ brand, children }) => {
return (
<ThemeProvider theme={brand}>{children}</ThemeProvider>
);
};
function ThemedWrapper({ children }) {
return (
<BrandStateContext.Consumer>
{brand => (
<LibraryProvider brand={brand}>{children}</LibraryProvider>
)}
</BrandStateContext.Consumer>
);
}
export default ThemedWrapper;
What I would like to do is to compose multiple styled components into one.
With plain css this is very easy:
<button class="btn large red"></button>
This is my attempt with emotion in React:
import styled from "#emotion/styled/macro";
const A = styled.button({
color: "red"
});
const B = styled.button({
fontSize: 32
});
// I know how to add single styled component. But how to also add B here?
const C = styled(A)({
// some additional styles
});
function App() {
return (
<div className="App">
<A>A</A>
<B>B</B>
<C>C</C>
</div>
);
}
Please check the demo:
Demo
It seems like styled is not capable of combining multiple styled components by default.
You might want to look at the css functionality of emotion here. This allows the composition of multiple defined css styles. Even though this required more lines of code for the extra definition of the css objects.
Using css your example could look like this:
import styled from "#emotion/styled/macro";
import { css } from "#emotion/core";
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const red = css({
color: "red"
});
const A = styled.button(red);
const bolt = css({
fontSize: 32
});
const B = styled.button(bolt);
const C = styled.button(red, bolt);
function App() {
return (
<div className="App">
<A>A</A>
<B>B</B>
<C>C</C>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Demo
Ok, let say I want to create my custom react "native" elements (<a>,<h1>, ...) with some custom styles.
Which one of these <A> elements is the best practice to choose?
/*
*
*`<A>` unwrapped
*
*/
import React from 'react';
import styles from './styles.scss';
const A = () => {
render() {
const { className, children, ...attrs } = this.props;
const classes = className ? [styles.base, className] : [styles.base];
return (
<a
className={classes.join(' ')}
{...attrs}>
{children}
</a>
);
}
}
A.propTypes = {
className: React.PropTypes.string,
children: React.PropTypes.any,
};
export default A;
OR
/*
*
*`<A>` wrapped by a div
*
*/
import React from 'react';
import styles from './styles.scss';
const A = () => {
render() {
const { className, children, ...attrs } = this.props;
const classes = className ? [styles.base, className] : [styles.base];
return (
<div className={classes.join(' ')}>
<a {...attrs}>
{children}
</a>
</div>
);
}
}
A.propTypes = {
className: React.PropTypes.string,
children: React.PropTypes.any,
};
export default A;
Considering:
Developer perspective
CSS selectors perspective
HTML perspective