emotion use css prop with custom components - reactjs

In my application I have a component that I want to style with the css prop from outside.
function Component({css}:{css?: React.CSSProperties}) {
// some stuff going on here
return (
<div
css={{
color: blue,
...css
}}
>
// some stuff going on here
</div>
)
}
The background is as follows:
I want to use Component in different scenarios where I have to style the container based on the surrounding layout. E.g. flex, grid or in combination with some components I have to add different margins.
Now instead of introducing many props for all possible scenarios, I want to be able to style the container from outside the component.
E.g. usages of the component could be:
function Layout() {
return (
// some other components
<Component css={{margin: 12}}/>
// some other components
)
}
or
import {css} from "#emotion/react"
const style = css({margin: 12})
function Layout() {
return (
// some other components
<Component css={style}/>
// some other components
)
}
or
import {css} from "#emotion/react"
const style1 = css({margin: 12})
const style2 = css({color: 'red'})
function Layout() {
return (
// some other components
<Component css={[style1, style2]}/>
// some other components
)
}
I have the following problems:
If I use css as the prop name (as in the above example) the style is not applied. If I change the name of the prop to e.g. newCss it works as expected
React.CSSProperties is not the right prop type to handle all the possibilities of emotions css prop.
How can I merge the different css prop possibilities (object, list) with the css prop from Component?

In fact, we don't need to use the extra props. As Ben Laniado mentioned, the official documentation states
Any component or element that accepts a className prop can also use the css prop.
https://emotion.sh/docs/css-prop#use-the-css-prop
So what we need is accepting className and css as props and add className to the component. (We don't need css to the component but need it for types)
type ComponentProps = {
css?: SerializedStyles;
className?: string;
};
const Component: VFC<ComponentProps> = ({ className }) => {
return (
<div css={{ color: blue }} className={className}>
hello world
</div>
);
};
export default function App() {
return (
<div className="App">
<Component css={{ margin: 12 }} />
</div>
);
}
This is the full working example.
https://codesandbox.io/s/react-component-accepting-emotion-css-prop-wskbh?file=/src/App.tsx

The right way of achieving this functionality is modifying the component to accept extra props. This way the css prop passed into the component would be merged with the one within the component.
function Component({prop1, prop2, propN, ...props}) {
// some stuff going on here
return (
<div
css={{
color: blue,
}}
{...props}
>
// some stuff going on here
</div>
)
}
Now you can use additional styles on your component and it will be rendered properly.
function Layout() {
return (
// some other components
<Component css={{marginTop: "1em"}}/>
// some other components
)
}
The side effect of this solution that any additional prop would be passed directly to the HTML element inside the component that takes {...props}.

Related

My button gives this error. Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? [duplicate]

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>
);
}
}

Tailwind, Twin-macro, Styled Components className override priority

I'm creating a custom Section component using styled-components, twin-macro, tailwind css. The default styling is stored in StyledSection constant.
import tw, { styled } from "twin.macro";
const Section = (props) => {
return (
<StyledSection
className={props.className}
style={{ backgroundImage: `url(${props.backgroundImage})` }}
>
{props.children}
</StyledSection>
);
};
const StyledSection = styled.section(tw`py-20 bg-no-repeat bg-cover bg-center`);
export default Section;
However, I want to override the styling if I pass something on the component, for it to more reusable.
Example of passing override class in the Section component:
<Section className="my-override-class" />
However, the styled-components classes is being priotized. Please refer to the picture below for the render output.
Image reference for the render output

How to add optional React components inside another React component via props

What is the best approach for adding optional components inside a component?
I have one like this:
type Props = {
children: React.Node,
title?: string,
/**May add any component next to the header. Should be inside a fragment. */
headerComponents?: React.Node,
className?: string,
}
export const Content = ({ children, className, title, headerComponents }: Props) => (
<div className={`page-content ${className}`}>
<div className='page-content-header'>
{
title && (
<h2 className='content-title'>{title}</h2>
)
}
{
headerComponents && (
<div className='page-header-right'> {headerComponents} </div>
)
}
</div>
{children}
</div>
);
The headerComponent acts as a prop that can receive another component, like this:
<Page.Content
headerComponents={
<>
<Button>First</Button>
<Button>Second</Button>
</>
}
title='Example title'
>
<div>Example text</div>
</Page.Content>
And it works. But I'm wondering if there's a better approach.
This approach looks fine to me. It is readable and intuitive to pass props to the component, followed by conditionally rendering the optional components based on the values of the props.

How does React Context work by using a function inside of a component

How does this code work ? How can a function be called inside a
component?
import React from 'react'
const ThemeContext = React.createContext('blue');
const App = () =>
<ThemeContext.Provider value={'green'}>
<ThemeContext.Consumer>
{(value) => <button style={{ background: value }}>Hello Context!</button>}
</ThemeContext.Consumer>
</ThemeContext.Provider>
export default App
I am trying to understand React Context internals , While it is clear how
Context/Provider/Consumer can be used I just don't seem to understand how this line actually works calling a function inside of a component
<ThemeContext.Consumer>
{(value) => <button style={{ background: value }}>Hello Context!</button>}
</ThemeContext.Consumer>
Is it possible have the same pattern work inside of a custom component? This throws a warning 'Functions are not valid as a React child.
<div>
{(value)=><span>{value}</span>}
</div>
React Functions as Child Components
So if I'm getting this right, you are basically asking how you could get a component which is in the following format:
<MyComponent>
{(name) => (
<div>{name}</div>
)}
</MyComponent>
These are called functions as children. You do it by managing the state or a variable in a component locally and you pass that state or variable to any other component in the app by implementing the children as a function in MyComponent.
So your MyComponent component will look something as follows:
class MyComponent extends React.Component {
render() {
return (
<div>
{this.props.children('Scuba Steve')}
</div>
);
}
}
MyComponent.propTypes = {
children: React.PropTypes.func.isRequired,
};
This allows you to reuse MyComponent anywhere with the exact same state or variables but you could render it differently.
You would find this pattern quite a lot in libraries like react-final-form for example, where the library maintains a state and the users can "consume" that state and render it in anyway they want.
You can read more about it at this link and at this link as well.
Understand React Context internals
The React Context Consumer children is a function instead of typical string or React Element
<ThemeContext.Consumer>
{(value) => <button style={{ background: value }}>Hello Context!</button>}
</ThemeContext.Consumer>
<div>
Hey, I'm normal JSX
</div>
The above code will be transpiled to
React.createElement(ThemeContext.Consumer, null, function (value) {
return React.createElement("button", {
style: {
background: value
}
}, "Hello Context!");
})
React.createElement("div", null, "Hey, I'm normal JSX"))
You can see that the children (props.children) is a function.
<div>
{(value)=><span>{value}</span>}
</div>
This code is mean that you declared a function inside <div>. (Not calling that function any more)
<ThemeContext.Consumer>
{(value) => <button style={{ background: value }}>Hello Context!</button>}
</ThemeContext.Consumer>
This function will be called inside ThemeContext.Consumer then your element will render
I see that it as more of a javascript question than a react specific; react components at the end the day will become a function; javascript support function as first-class, so a function can be passed to other function as arguments ( or returned as value ) hence the higher the components and context API. So your question can be roughly translated to this code snippet:
function Theme (color) {
/* some code maybe */
return function Nav (TotalItems){
return `I'll render a ${color} with ${TotalItems} TotalItems`;
}
}
let redThemed = Theme( "dark red");
let navComp = redThemed(17);
console.log( navComp );
console.log( redThemed(12) );
let blueThemed = Theme( "light blue");
console.log( blueThemed(4) );

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>
);
}
}

Resources