I have a component that I use twice in the same code, it looks like this:
import React from 'react';
import Container from 'Base/Grid/Container';
import styles from './index.css';
const Columns = props => <Container {...props} className={styles.root} block/>;
export default Columns;
How can i, when importing, apply another style class to the second used Columns?
thanks in advance
You can define another style beside of your styles.root that is passed from the props. Like below:
const Columns = props => <Container {...props} className={[styles.root,props.newStyles]} block/>;
So when you make a Columns component you can pass the specific styles. For example:
<Columns newStyles={{color: 'red'}} />
So you can customize the style for each component which you use.
or if you don't want to use the root style you can make it conditional that if there the newStyle was passed use it. If not just use the styles.root. And the code would be like this:
Columns = props => <Container {...props} className={props.newStyle || styles.root} block/>;
A clean solution is to use react composition. Declare the "base" component in one file, and then export in two different files with two different names the styled one.
// BaseComponent.jsx
export default Column = () => <div>Column</div>;
// RedColumn.jsx
import Column from './Column';
const RedColumn = () => <Column style={{color: "red"}} />;
export default RedColumn;
// BlueColumn.jsx
import Column from './Column';
const BlueColumn = () => <Column style={{color: "blue"}} />;
export default BlueColumn;
Related
I have created a custom component that I want to reuse multiple times throughout my app. The component is itself using a TouchableHighlight inside it.
I want the usage of the component to be able to pass through TouchableHighlight props without having to declare them separately inside the custom component.
A very simple example:
Component:
const CustomComponent = () => (
<TouchableHighlight>
<Text>Custom component</Text>
</TouchableHighlight>)
Usage:
<CustomComponent activeOpacity={0.5} underlayColor=“red” onPress={someAction} />
So to summarise, I’d like the TouchableHighlight to use activeOpacity, underlayColor, and onPress without having to single them out inside the component individually.
Hope that makes sense and I’ve explained it sufficiently.
Try this:
const CustomComponent = ({touchableHighlightProps, ...otherProps}) => {
return (
<TouchableHighlight {...touchableHighlightProps}>
<Text>Custom component</Text>
</TouchableHighlight>
)
}
Then the usage will be:
<CustomComponent touchableHighlightProps={touchableHighlightProps} />
You can spread the props if you just want it to pass through.
const CustomComponent = (props) => (
<TouchableHighlight ...props>
<Text>Custom component</Text>
</TouchableHighlight>)
Currently I import Icons like this
//index.ts file for svgs
import BackArrow from './back-arrow.svg'
export {
BackArrow,
...
}
In a component i can now import and use it easily
import { BackArrow } from '../../assets/images'
....
return <BackArrow />
Now I tried to make a (child) component which should display two buttons and a text, which are passed by the parent component.
Parent uses the child like this:
<Button
label='hi'
iconLeft={BackIcon}
buttonTheme={BUTTONS_THEME.dark}
/>
Child looks like:
//Styled components (ThemedButton, Icon, Label)
const Button = ({ label, iconLeft, iconRight, theme}) => {
<ThemedButton>
{iconLeft && <Icon theme={theme} xml={iconLeft} />}
<Label theme={theme}>{label}</Label>
{iconRight && <Icon theme={theme} xml={iconRight} />}
</ThemedButton>
and this all works fine, if in the parent component I import the svg like this:
import BackArrow from '../../assets/images/back-arrow.svg'
but i want to use the "normal" way to import it as I do everywhere else in the App, like this: import { BackArrow } from '../../assets/images'
Anyone any idea how to solve this? I tried to pass the Icons like this <Button leftIcon={<BackArrow />}... />, but I can't get this working, as I don't know how I can use and style the icon in the child.
I think what you are trying to do is use your BackArrow in your child as <BackArrow />
The below approach is for a CRA app where you can import svg as a ReactComponent
I solved a similar issue by following this approach.
import is as
import { BackArrow } from './back-arrow.svg
Continue to pass it to child as
<Button
label='hi'
iconLeft={BackIcon}
buttonTheme={BUTTONS_THEME.dark}
/>
but, now in child do not destructure your props,
const Button = (props) => {
<ThemedButton>
{props.iconLeft && <props.iconLeft ... />}
<Label theme={props.theme}>{props.label}</Label>
{props.iconRight && <props.iconRight ... />}
</ThemedButton>
EDIT:
In the child component when you have <props.iconLeft ... /> then this is equivalent to <BackArrow /> so now you are free to style it, essentially treat it the way you would in the parent when you had <BackArrow />. Destructuring your props in the child doesn't quite work here.
I don't know what you are using for your build processes. but it's probably because of loaders that you use for loading svg. If you import your svg like this: import BackArrow from '../../assets/images/back-arrow.svg' it's probably load it as dataURL. And if you import your svg like this: import { BackArrow } from '../../assets/images' it imports as react component. Thus you can not use this as xml props of a Icon.
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 have a page made up of many sub-components. The page folder holds the page file, external styles file and each sub-components folders - which also consists of their own style.
I'm not sure how to set up an external shared common style file for all sub-components along with external styles for each of the sub-components.
Ie. DemoStyles.js is the common styles and Demo.js is where are the sub-components are called.
Demo.js:
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import styles from "./DemoStyles";
import Red from "./red/Red";
import Blue from "./blue/Blue";
function SharedStyles(props) {
return (
<React.Fragment>
<Red />
<Blue />
</React.Fragment>
);
}
SharedStyles.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(SharedStyles);
DemoStyles.js:
export default theme => ({
title: {
color: "white",
fontSize: 30
}
});
The title style is not being applied.
The className is set in the Red.js file:
TL;DR:
I need one common external style file to apply to all subcomponents living in one folder; and each subcomponent needs their own external style specific to it.
Here is the code sample: https://codesandbox.io/s/rypoqk07lo
SOLVED:
I solved this by importing the demoStyles into the RedStyles.js file and then calling it with the spread operator and passing theme as an argument like so:
RedStyles.js
import demoStyles from "../DemoStyles";
export default theme => ({
...demoStyles(theme),
red: {
backgroundColor: "red"
}
});
code sample updated as well
You forgot to pass the classes props on Demo.js to your components like you do on <Red /> and <Blue />.
const { classes } = props;
return (
<React.Fragment>
<Red classes={classes} />
<Blue classes={classes} />
</React.Fragment>
);
Its good to remember that Material-UI has themes support. It's better to use it on Demo.js depending of what you trying to do.
Lets say I am using a <FormattedNumber > that I am importing from react-intl
It has a property called minimumSignificantDigits, so that if I set it all my numbers look awesome like 1.00... great when you are working with currencies.. so I can use it like this:
<FormattedNumber minimumSignificantDigits={3} value={somevar}>
I have about 100 of these on the page and I don't want to keep setting this minimumSignificantDigits property on every single on of them, and then when the client changes his/her mind I have to update all of them.
Is there any way that I can set/override some default properties on that component when I import it.
Obviously yes, make a wrapper around <FormattedNumber>
// TreeCharFormattedNumber.jsx
export default TreeCharFormattedNumber = ({ value }) => (
<FormattedNumber minimumSignificantDigits={3} value={value}>>
);
// YourComponent.jsx
import TreeCharFormattedNumber from "./TreeCharFormattedNumber";
...
<div>
<TreeCharFormattedNumber value={myAwsomeValue} />
</div>
...
You can also put TreeCharFormattedNumber in the same file leaving export default
Wrap the imported component with another.
In this example, the default value for minimumSignificantDigits would be 3 with any other props passed through as is. (This allows you to also override your default on a per component basis if required)
function FormattedNumberWithDefault(props) {
return (
<FormattedNumber minimumSignificantDigits={3} {...props}>
)
}
Wrap it with your own component:
export const MyFormattedNumber = (props) => (
<FormattedNumber minimumSignificantDigits={3} {...props}>
);
Now you can import it whenever it's needed, and everything you'll pass to MyFormattedNumber will be passed to the wrapped FormattedNumber:
<MyFormattedNumber value={3} />
You can easily override the default if you pass the property minimumSignificantDigits, because spreading the props can replace the default prop as well:
<MyFormattedNumber minimumSignificantDigits={15} value={somevar}>
I have found that the following also works:
import React from 'react'
import {FormattedNumber} from 'react-intl'
import {Link} from 'react-router-dom'
FormattedNumber.defaultProps = {
style: 'decimal',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}