What is the best way to send props to a styled component without needing to convert them to transient props beforehand

Recently, I designed a custom Text component that accepts several props. These props are then forwarded to the styled component where specific styles are applied. However, I am facing an issue where I do not want these props to be passed down to the DOM, but rather be accessible only within the styled component.

While converting props to transient props is a solution, it leads to repetitive code which unnecessarily increases the size of the component. Is there a way to pass certain props to the styled component without having them propagate to the DOM, and without having to redefine each prop individually? I experimented with using shouldForwardProps, but unfortunately, I encountered difficulties integrating it with TypeScript.

type Props = {
  children: React.ReactNode;
  size: number;
  height: number;
  color: string;
};

const StyledText = styled.p<{ $color: string; $size: number; $height: number }>`
  color: ${({ $color }) => $color};
  font-size: ${({ $size }) => `${$size}px;`};
  line-height: ${({ $height }) => `${$height}px;`};
  ...
`;

const Text: React.FC<Props & React.HTMLAttributes<HTMLParagraphElement>> = ({ children, size, height, color, ...rest }) => {
  return (
    <StyledText $size={size} $height={height} $color={color} {...rest}>
      {children}
    </StyledText>
  );
};

Answer №1

Implementing the solution with shouldForwardProps as previously suggested addresses your specific need. While attempting to integrate customAttributeNames with the Props, I encountered challenges and decided to abandon that approach.

const customAttributeNames = ["size", "height", "color"] as const;

type TCustomAttribute = typeof customAttributeNames[number];

type Props = React.HTMLAttributes<HTMLParagraphElement> & {
  children: React.ReactNode;
  size: number;
  height: number;
  color: string;
};

const StyledTextWithoutDomAttributes = styled.p.withConfig({
  shouldForwardProp: (prop) =>
    !(customAttributeNames as readonly string[]).includes(prop)
})<Pick<Props, TCustomAttribute>>`
  color: ${({ color }) => color};
  font-size: ${({ size }) => `${size}px;`};
  line-height: ${({ height }) => `${height}px;`};
`;

const StyledTextWithDomAttributes = styled.p<Pick<Props, TCustomAttribute>>`
  color: ${({ color }) => color};
  font-size: ${({ size }) => `${size}px;`};
  line-height: ${({ height }) => `${height}px;`};
`;

const Text: React.FC<Props> = ({ children, size, height, color, ...rest }) => {
  return (
    <>
      <StyledTextWithDomAttributes
        size={size}
        height={height}
        color={color}
        {...rest}
      >
        {children}
      </StyledTextWithDomAttributes>
      <StyledTextWithoutDomAttributes
        size={size}
        height={height}
        color={color}
        {...rest}
      >
        {children}
      </StyledTextWithoutDomAttributes>
    </>
  );
};

To observe this setup in action, visit this sandbox link.

Answer №2

One way to enhance your design system is by starting with a default StyledText component and then customizing it using the styled function.

const StyledTextPrimary = styled.p`
  color: #000;
  font-size: 1rem;
  line-height: 130%;
`;

You can further customize it like this:

const StyledTextLarge = styled(StyledText)`
  color: #333333;
  font-size: 1.2rem;
  line-height: 140%;
`;

In a well-structured design system, it is advisable to define various text/typography variants such as headings or subheadings. This approach enables you to create distinct components instead of relying on variable inputs that may not align with the intended design.

If managing multiple text components becomes overwhelming, consider creating a single component that accepts a "variant" attribute and dynamically selects the appropriate component based on predefined options.

Here's an example implemented in TypeScript:

export enum ETextVariants {
  primary,
  large,
}

const variants = {
  [ETextVariants.primary]: StyledTextPrimary,
  [ETextVariants.large]: StyledTextLarge,
};

export interface ITextProps extends ComponentPropsWithoutRef<'p'> {
  variant?: ETextVariants;
}
export function StyledText({
  variant = ETextVariants.primary,
  children,
  ...props
}: ITextProps) {
  const TextComponent = variants[variant];
  return (
    <TextComponent {...props}>
      {children}
    </TextComponent>
  );
}

To use this setup, simply include the following:

  <StyledText>my text</StyledText>
  // or
  <StyledText variant={ETextVariants.large}>my text</StyledText>

While strings can be used instead of enums for defining variants, utilizing enums is a common development practice to optimize build space and memory usage.

For a live demo, check out this CodeSandbox: https://codesandbox.io/s/variants-on-a-styled-components-1pcjii

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Executing PHP function through AJAX

I have thoroughly researched various resources regarding my issue but still have not been able to find a solution. My goal is to retrieve the result of a PHP function using jQuery AJAX. function fetch_select(){ val_name = $('#name').val(); ...

What could be the reason my component is not displaying the ContentChild associated with a directive?

It appears that utilizing a directive to target a content child from another directive is the recommended approach (source). However, why isn't my component able to recognize the component marked with the directive? ./my.component.ts import { Comp ...

What is the best way to have a div gradually glide out (utilizing CSS's transition feature) upon the click of a particular button?

I want the hint-bubble div to smoothly slide out (using 'transition: 0.5s') whenever the hint-btn is clicked. I have successfully implemented it so that the hint-bubble appears when the button is clicked, but it appears instantly and I am struggl ...

Angularjs application and bash script generating different SHA256 hashes for the same file

In my AngularJS app, I am struggling to get the digest of an uploaded file. The issue is that the resulting hash is not matching the one obtained using bash locally. Initially, I used jshashes, but when I noticed discrepancies in the hashes generated by t ...

Convert a JSON array with a single element into a valid JavaScript object

Within my programming scripts, I frequently utilize PHP arrays with numeric keys. However, these keys are not necessarily sequential from 0 to n; they can be randomly chosen. Specifically, I am working on a script that organizes scheduled events at specifi ...

Adding pixels to the top position with jQuery in Angular 2

I am trying to adjust the position of my markers when the zoom level is at 18 or higher by adding 10px upwards. However, my current approach doesn't seem to be working as expected. Can someone please assist me with this issue? You can view the code sn ...

Enhancing class functionality with decorators in TypeScript

Over on the TypeScript's Decorator reference page, there is a code snippet showcasing how to override a constructor with a class decorator: function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) { return class extends con ...

Changing JavaScript functions into jQuery functions

Currently, I have this Javascript function that I am interested in converting to jQuery: function confirm() { var http = new XMLHttpRequest(); var url = "index.php?tag=' . $dt . '"; var params = "confirm_ref=' . urlencode(encry ...

Receiving multiple Firebase notifications on the web when the same application is open in multiple tabs

I have implemented firebase push notifications in Angular 7 using @angular/fire. However, I am facing an issue where I receive the same notification multiple times when my application is open in multiple tabs. receiveMessage() { this.angularFireMess ...

Converting Firebase TIMESTAMP values to human-readable date and time

Utilizing Firebase in my chat application, I am adding a timestamp to the chat object using the Firebase.ServerValue.TIMESTAMP method. I want to display the time when the message was received in the chat application using this timestamp. If it is the cur ...

Customizing Echart tooltip appearance

I've integrated echart () into my Vue.js application, and I'm attempting to personalize the tooltip on a ring chart. However, I'm facing challenges in achieving this customization. My goal is to show my own JSON data in the tooltip when hove ...

Sending an identifier to a PHP file using JavaScript

I am facing an issue with my JavaScript code where the id is supposed to be passed to my PHP script at intervals, but if the id stops being passed (indicating that the user has closed the JavaScript page), a specific block in the if statement should run in ...

Why does xpath insist on choosing spaces instead of actual elements?

Here is a list of countries in XML format: <countries> <country> <code>CA</code> <name>Canada</name> </country> ... etc... </countries> I am looking to extract and loop through these nodes, so ...

Formik's setFieldValue function exhibits a delay in updating the state, resulting in a one-render lag

I've set up a formik form with initial values of { email: "", password: "", method: "manual" }. In my GoogleLogin component from react-oauth/google, I want to update the value of method to 'google' inside the on ...

How Axios triggers CanceledError using Abort controller in a React application

I have created a custom axios instance with interceptors to handle authentication requests. The customized axios instance looks like this: const BASE_URL = 'http://localhost:8000'; export const axiosPrivate = axios.create({ baseURL: BASE_URL, ...

Make sure to load the Mantine styles only after the Tailwind preflight styles

Trying to integrate Mantine and Tailwind together has been a bit challenging. The issue is that Tailwind's "preflight" base styles are taking precedence over Mantine's styles, resulting in a simple button becoming invisible. https://i.sstatic.ne ...

Creating an alarm clock with customizable time and date formats using ReactJS with Material-UI integration

Here is the code I have written: const date = new Date(); const currentTime = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; const getDay = `${date.getDay()} ${date.getMonth()} ${date.getDate()}`; return ( <Box> < ...

Is there a sophisticated method for breaking down a nested property or member from TypeScript imports?

Just curious if it's possible, not a big deal otherwise. import * as yargs from 'yargs'; // default import I'm looking to extract the port or argv property. This would simplify the code from: bootstrap(yargs.argv.port || 3000) to: ...

node js retrieves information from the request body

Hey there! I'm diving into the world of Node.js and JavaScript, but I've hit a roadblock. I'm trying to fetch data from a URL using node-fetch, then parse it as JSON. However, I keep running into the issue of getting 'undefined' in ...

The backend is serving HTML content, but the frontend is not initiating any redirects

After hitting an API endpoint and examining the network call responses, I identified that the HTML response returned with a status code of 302. Despite this, I am perplexed as I do not witness the expected redirect on the user interface. The intended redir ...