Most effective approach to managing state in a React UI Component

I recently delved into the world of React. I've been using it to build a design system library for my work, which consists of standalone UI components tailored for use in React applications.

I've developed a specific approach to managing the state and events of my components, exemplified by this button component:


import { composeEventHandlers } from '../../utils/events';
import './button.scss';
import classNames from 'classnames';
// ...

type Size =
  | 'small'
  | 'medium'
  | 'large';

type Type = 
  | 'primary'
  | 'secondary'
  | 'outline';

interface ButtonProps {
  id?: string;
  children: string | string[];
  size?: Size;
  type?: Type;
  fullWidth?: boolean;
  disabled?: boolean;
  loading?: boolean;
  pressed?: boolean;
  textAlign?: TextAlign; 

  onClick?(): void;
  onFocus?(): void;
  onBlur?(): void;
  onMouseEnter?(): void;
  onMouseLeave?(): void;
}

/**
 * Button component
 */
export const Button = ({
  children,
  size,
  type,
  disabled = false,
  loading = false,
  pressed = false,
  fullWidth,
  onClick,
  onFocus,
  onBlur,
  onMouseEnter,
  onMouseLeave,
}: ButtonProps) => {
  const [isHovered, setIsHovered] = useState(false);
  const [isFocused, setIsFocused] = useState(false);
  const [isPressed, setIsPressed] = useState(pressed);

  const handleClick = _ => {
    setIsPressed(true);
  }

  const handleFocus = _ => {
    setIsFocused(true);
  }

  const handleBlur = _ => {
    setIsFocused(false);
  }

  const handleMouseEnter = _ => {
    setIsHovered(true);
  }

  const handleMouseLeave = _ => {
    setIsHovered(false);
  }

  const prefix = usePrefix();
  const buttonClasses = classNames(
    `${prefix}--btn`,
    `${prefix}--btn--${type}`,
    `${prefix}--btn--${abbreviate(size)}`,
    {
      [`${prefix}--btn--full-width`]: fullWidth,
      [`${prefix}--btn--disabled`]: disabled,
      [`${prefix}--btn--loading`]: loading,
      [`${prefix}--btn--pressed`]: isPressed,
      [`${prefix}--btn--hovered`]: isHovered,
      [`${prefix}--btn--focused`]: isFocused,
    }
  );
  const eventHandlers = {
    onClick: composeEventHandlers([onClick, handleClick]),
    onFocus: composeEventHandlers([onFocus, handleFocus]),
    onBlur: composeEventHandlers([onBlur, handleBlur]),
    onMouseEnter: composeEventHandlers([onMouseEnter, handleMouseEnter]),
    onMouseLeave: composeEventHandlers([onMouseLeave, handleMouseLeave]),
  }

  const buttonMarkup = 
    <button
      type="button"
      className={buttonClasses}
     {...eventHandlers}
    >
      {loading
        ? <Loader size='small' type={type === 'primary' ? 'secondary' : 'primary'></Loader>
        : children
      }
    </button>
  ;

  return buttonMarkup;
};

In my CSS file, I dynamically adjust the component's style based on the CSS classes determined during runtime by the component's state:

.#{$prefix}--btn {
  // ...

  // SIZES
  &.#{$prefix}--btn--sm {
    width: auto;
    height: 32px;
    font-size: 14px;
  }
  // ...

  // COLORS
  &.#{$prefix}--btn--primary {
    baclground-color: $primary-color
  }
  // ...

  // STATES
  &.#{$prefix}--btn--pressed, &.#{$prefix}--btn--hovered, &.#{$prefix}--btn--focused {
    cursor: pointer;
    @include darken-background();
  }

  &.#{$prefix}--btn--disabled {
    background-color: $disbaled-color;
    color: $secondary-color;
    outline: none;
    cursor: no-drop !important;
  }
}

Classnames is a handy JavaScript utility that allows for conditional concatenation of class names.

I also utilize a utility function composeEventHandlers to enable consumers to add their own event listeners in addition to the default ones defined in the component.

My queries are:

  1. Is this method of handling component style states efficient? How does it compare to using CSS pseudo-classes like :hover or :active? I have concerns about introducing a layer of JS to update styles, which might impact performance negatively.

  2. Are there any poor practices in my implementation elsewhere? Is there room for improvement or a better approach?

This marks one of the initial components in my design system, and I am dedicated to adhering to best practices right from the start. Your insights would be greatly valued.

Answer №1

Styled components can be utilized to manage styles based on state changes. Here is an example:

const Input = styled.input<{ $inputColor?: string; }>`
  padding: 0.5em;
  margin: 0.5em;
  color: ${props => props.$inputColor || "#BF4F74"};
  background: papayawhip;
  border: none;
  border-radius: 3px;
`;

// Render a styled text input with the standard input color, and one with a custom input color
render(
  <div>
    <Input defaultValue="@probablyup" type="text" />
    <Input defaultValue="@geelen" type="text" $inputColor="rebeccapurple" />
  </div>
);

You can add this dependency from styled component official website

For more examples, visit: https://styled-components.com/docs/basics#adapting-based-on-props

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

Tips on how to show a personalized <Errormessage> within a Formik field component while turning off the standard message

In my current web project, I am utilizing a combination of React, Material-UI, Formik, and formik-material-ui. Specifically, I have implemented a Formik form that includes validation using yup. const schema = yup.object({ name: yup.string().trim().low ...

What is the source of these additional pixels that appear when percentages are used for defining height?

I have been working on implementing a sticky footer that I've successfully used in the past for various websites. You can find the link to the original method here: However, this time I am facing a challenge as I want to make the footer more responsi ...

Setting up Stylelint in a Next.js project: A step-by-step guide

I'm looking to incorporate Stylelint into my Next.js app. Can I modify the next.config.js file to include the stylelint-webpack-plugin, in order to receive style errors during compilation? // next.config.js module.exports = { reactStrictMode: true, ...

Tips on displaying inline span text within an anchor element, above the anchor text

Check out the code I've created below: .tooltip1 { z-index: 1; position: relative; } .tooltip1 .tooltiptext { visibility: hidden; width: 300px; ...

What could be the reason that the height: auto property is not creating a content area big enough to accommodate my content

When a user is not logged in, the header displays a logo and a login form that requires ample space. Once the user logs in, the full navigation menu appears in a smaller header size. To achieve this, adjusting the height:auto property of the "site-header" ...

Button with CSS Sprite

These Sprite buttons are making me lose my mind. I'm so close to getting them to work, but not quite there yet. I've been playing around with this really simple sprite image: If you want to see the jsfiddle project, it's available here, bu ...

Issue encountered with the file path to my translation file following the deployment of my react application on GitHub pages

I'm encountering difficulties with getting my website to function properly. It's a react + vite setup using the i18next.js library for text translation. The site is hosted on GitHub pages with a custom domain name. I keep seeing the error message ...

When running `npm install`, it attempts to install version 1.20.0 of grpc even though version 1.24.2 is specified in

After running npm install on my React-Native project, an error occurred stating that it was attempting to install gRPC version 1.20.0, but my package.json and package-lock.json specified gRPC version 1.24.1. I attempted to fix the issue by adjusting the v ...

Is it possible to use async in the onChange handler of a React event?

Can async be used to pause execution until a function completes within an onChange event? Here is an example: const onChange = async (e) => { console.log(current[e.target.name]); await setCurrent({ ...current, [e.target.name]: e.targe ...

Is it possible to create a development build using Npm with React and Typescript?

I have successfully set up a TypeScript React app by using the command below: npx create-react-app my-app --template typescript However, running "npm start" generates development javascript files and launches a development server which is not id ...

Capybara - Navigating a page by clicking on a link related to another value

I'm currently facing a challenge in locating and selecting a particular link on a web page. All links within an HTML table contain the term 'Edit' (it's a link for editing configuration settings). However, the exact link changes depend ...

Include money symbols within input fields with padding and vertical alignment

I am looking to create an input field that includes either € or $ currency symbols. The challenge is ensuring perfect vertical centering inside the input, especially when using padding. Is there a foolproof method to achieve this? https://i.stack.imgur ...

What is the process of integrating TextField modifications into state using MaterialUI and Redux?

When using React / Redux and MaterialUI, I have set up a component that needs to either check the user's location or retrieve the latitude/longitude based on the address input. Upon clicking a button, I want the address entered in a TextField to be v ...

The rounding of my image is uneven across all sides

I'm attempting to give my image rounded borders using border-radius, but I'm unsure if the overflow property is affecting the image. Image 1 shows my current image, while Image 2 depicts what I am trying to achieve. I am utilizing React, HTML, no ...

React/NextJS: Firebase Realtime Database displaying the message "Error: Client is offline"

Encountering an occasional error when using React with NextJS to fetch data from a Firebase Realtime Database. Unhandled Runtime Error Error: Error: Client is offline. Currently utilizing Firebase version 9.0.1 for React. The initialisation and configura ...

Using jQuery to create radial-gradients with the background-css feature

I'm a newcomer to this online community and English is not my first language. Despite that, I will do my utmost to clearly explain the issue at hand. Recently, I utilized the .css function in jQuery to create a design element successfully. However, I ...

I am attempting to store the primary array in local storage, but unfortunately, the value is not being saved within the React context API

I attempted to store the main array in local storage and retrieve it as global state, but I am facing an issue where the data is not being saved in the local storage. This file represents my context. import { createContext, useReducer, ReactNode, FC, use ...

Encountering the error "eisdir illegal operation on a directory read" while setting up a Next.js app on an Azure

Encountering an issue with my web app hosted on a Linux app service plan. The static Next.js site runs perfectly fine in Azure storage with CDN, as well as a Windows-based web app (with additional files like web.config and server.js). I am attempting to r ...

What causes the numbers to switch back and forth when utilizing React Hooks useState() for counting?

In an intriguing React Hooks experiment utilizing useState(), a peculiar behavior was observed when the + button was clicked. The number displayed started alternating between 7001 and 7000, eventually flashing rapidly through various numbers. Surprisingly ...

Rendering images from Laravel's storage folder in a React component

When uploading an image from a React frontend to a Laravel backend, I encountered an issue where the uploaded image path was saved in the database but the image wouldn't display. React code ` useEffect(() => { axiosClient .g ...