For example, I have component like this:
const Button = ({borderColor, children}) => {
return (
<button style={{borderColor: borderColor ? `border: 1px solid ${borderColor}` : null}}>
{children}
</button>
)
}
Main goals of this component are:
pass children prop further
handle custom border color
With children - it's usual situation. But with css I don't know, should I test it, or not.
Some articles about RTL tell that we don't have to test css. But
in my opinion, in this case, our css prop is important for end user
(e.g. some other developer who will use this component) and this css
should be tested. What's the right way?
Testing a React Application should be about behaviors. That is, how the application behaves when controlled by an end user. That's one of the guiding principles of react-testing-library, and one I agree with.
Therefore, going by this statement you can do two different things:
Test the complete behavior that causes the border color to change. That is Integration Testing
Type check the component to guarantee that you cannot pass an incorrect value for borderColor. That is Unit Testing
In my opinion with this use case, testing anything else would be testing React itself or, as the other answer noted, you ability to write a correct css string. The later can be tested with your own eyes anyway and isn't likely to change
Type checking example
Giving you an example for integration testing is hard to do without knowing your complete use case. As for type checking you have two options:
Using PropTypes
Switching to Typescript
Note: These options aren't equivalent. Proptypes is a library that checks the props at runtime with a developement build. They are ignored with a production build. Typescript on the other hand is a complete compiler, that runs during the build. The correct solution depends on your setup
With PropTypes
You can use PropTypes.oneOf :
Button.propTypes = {
// Edit as needed
borderColor: PropTypes.oneOf(["red", ,blue"])
}
Typescript
interface ButtonProps {
borderColor: "blue" | "red";
}
const Button: React.FunctionComponent<ButtonProps> = ({borderColor, children}) => {...}
Potentially misreading your question, but maybe you're only asking because "is this string a valid CSS color" feels like a silly or annoying detail to test, even though you're aware of the problems that not writing tests will have on other developers who will use this component.
Is this a silly thing to test? I agree conditionally.
IMO this is far better solved using Typescript to enforce constraints on what form the string can take. Some ideas here: TypeScript - is it possible validate string types based on pattern matching or even length?
A better thing to test is probably a functionality question like "does this component genuinely create a button wrapping the children with the 1px solid borderColor that we asked for?
In that case, yes I'd write a test for that functionality.
Related
A problem, I keep running into with Chakra Ui are "magic strings". Let's look at an example of muted text:
function Example() {
const color = useColorModeValue("gray.400", "gray.200");
return (
<Text color={color} />
)
}
What's the problem with this code: In my app, I want to share the muted color between many components. However, defining it explicitly as a string means that I have to remember that muted text has a value of "gray.400". If I have another component that wants to use muted text, I have to copy the string "gray.400" to all other components. I will end up with lots of strings that make it really hard to change things across the entire app. I explored two solutions so far:
Solution 1 - TextStyles API: Chakra comes with a textStyles API out of the box but this doesn't work well for more complicated situations (what if I want to have a hover and active state with different colors?).
Solution 2 - Create a global object: I've created a hookuseConsistantStyles() that returns a theme-like object with values, e. g.: {"borderLight": "gray.200"}. However, this feels like I'm fighting the library.
I'd really love to have a better solution since I keep running into this.
This is probably late but for any new onlookers, since Styled System v1.17.0, chakra ui has semantic tokens. The changelog and docs have some details but the important bit is:
Semantic tokens provide the ability to create css variables which can change with a CSS condition.
CSS conditions would be states like dark mode or hover etc.
In this case, you would define a semantic token like so (with a better token name):
const customTheme = extendTheme({
semanticTokens: {
colors: {
nicelyNamedToken: {
default: 'gray.400',
_dark: 'gray.200',
},
},
},
})
import the theme however you currently do, and then anywhere the theme is present, you should be able to just use the token
function Example() {
return (
<Text color='nicelyNamedToken' />
)
}
I don't think Solution 2 is actually that bad -- but I agree that it feels like everything should be accessible since it already exists.
If you're using a chakra theme, you can actually import the theme (i.e: import { theme } from "#chakra-ui/react"
And then you can start accessing things off of that object, like colors (t is the theme import)
Note: you still have to 'know' the keys on the object, but this is at least an existing, consistent dictionary that you can reference.
I have not tested this with different color themes, etc.
seemingly simple problem:
Example Background: we have many lists of items and render them in the UI with <List>.
Example Problem: we usually (but not always) want to remove the extra padding that is injected on <List> (ex: <UnpaddedList>).
As another example, perhaps we want to have most lists be collapsible by default with a <List onClick={...}> trigger to become <CollapsableList>. For most examples below, the unpadded is sufficient to illustrate.
On first guess, I'd expect something like CustomList extends List (but see Facebook inheritance advice). There are many ways to do it, but what are the advantages/disadvantages...they seem to conflict:
createStyled()
This is intended to entirely replace the theme... in times intended to break brand standards. Although we could go back and merge the existing theme, this seems against the purpose of this tool, and deepmerge perhaps could slow performance? this would work well if it could inherit the current theme context. perhaps lots of extra component names is bad practice? ex: List, UnpaddedList UnpaddedLargeList, UnpaddedLargeGreenList, etc.
see https://mui.com/system/styled/#create-custom-styled-utility
createTheme()
this allows total override of styles and props for each component. The only issue is that it may not be clear why a component is styled a certain way and keeps its name. (ex: <List> isn't renamed to <UnpaddedList>)This could be confusing if you have a component that is inheriting a theme from a parent component somewhere up in the tree and appears with an unexpected style. the developer would have to trace each parent component to find where the theme was injected with <ThemeProvider>. But perhaps such injecting sub-themes in unexpected ways would be its own anti-pattern.
Also, without clear docs/typescript, it took a lot of reading to determine how best to pass and modify the current theme:
function extendThemeWithGreen(theme: Theme) {
let themeOptions: ThemeOptions = { palette: { primary: { main: "green" } } };
return createTheme(theme, themeOptions);
}
function Example() {
return <ThemeProvider theme={extendThemeWithGreen}> example</ThemeProvider>;
}
another issue is that some components have arbitrary white-space injected into them. this is not documented and has no type-hinting. the only way to discover those is to hunt down the source code. from there you would likely have to create another set of styles that override the native styles and introduce bloat into the app.
see https://mui.com/system/styled/#custom-components
see https://mui.com/customization/how-to-customize/#2-reusable-style-overrides
return <List {...props} />
Wrapping components would be nice. (ex: const UnpaddedList = (props) => <List disablePadding {...props} />;)
so far this has been a lot of trouble. for example, should the props be ListProps or OverridableComponent<ListTypeMap<{}, "ul">>? Or suppose both the end component and wrapped component have sx... do I have to set a deepMerge to deal with duplicate props. Maybe I just need to spend more time to get it going?
see https://mui.com/guides/composition/#wrapping-components
sx={...} or style={...} or class=...
these props generally allow customization, however they are effectively one-off. ex: you have to <List style={{padding:0}} /> also, this loses type safety (expect things to break in the future) class can be separate, but still hard to retain type safety. Legacy makeStyles( is similar
see https://mui.com/system/the-sx-prop/
see https://mui.com/styles/api/#createstyles-styles-styles
<UnstyledList component={...}
This is largely an upcoming feature and not fully implemented yet. Although this could avoid future css conflicts, it still doesn't facilitate props override (or if it does... it isn't clear from the docs)
see https://mui.com/customization/unstyled-components/#main-content
<Box component={List} ... />
Use the Box wrapper to create a custom component. The docs aren't entirely clear on this. It may be something like:
function UnpaddedList(props: ListProps){
return <Box component={List} disablePadding {...props} />
}
Many of the component props have generic modifiers... however it isn't clear how this new component would be written to match (or extend) the original component props
see https://mui.com/system/box/#overriding-mui-components
variant="unpadded"
this is close, however it appears to create a typing issue, or special treatment to import. i could see this causing confusion down the road, but at least linting would throw a warning if not implemented properly. it doesn't have a good way to do composition (ex: variant="unpadded&collapsible"). it also isn't clear how this is done for components that do not have variants built in.
see https://mui.com/customization/theme-components/#adding-new-component-variants
props
of course some, but not all, can be done through props keep type safety. (ex: <List disablePadding={true}>) however this isn't DRY. it is overall worse to manage (no brand standard). it also makes the code much more difficult read and excessively verbose
other methods?
perhaps I missed another way. I also can't find any graceful solution to the hardcoded whitespace in MUI components
If you have a component that is used in multiple places and only one of them needs to have some special styles or you need to tweak or fix an edge case in a specific layout, use sx prop. It's suitable for one-off styles like this:
<List sx={{ p: 0 }}>
If you need to apply some styles occasionally in a component, use styled. For example if a component is used in 100 places but only in 20 places it needs to have the padding disabled then add a padding prop to enable the styles in the rarer case:
const options = {
shouldForwardProp: (prop) => prop !== 'disablePadding',
};
const List = styled(
MuiList,
options,
)(({ theme, disablePadding = false }) => ({
...(disablePadding && {
padding: 0,
}),
}));
// I dont always use disablePadding but sometimes I need it
<List disablePadding>
I'm currently working on a project which has all of its styles declared in JSS. One of the "benefits" highlighted on many articles and the library docs is that it encapsulates styles. However I really am having a hard time customizing them, specially when it comes to styling that depends on the component's surrounding context (by context I mean parent elements, siblings, etc.).
Consider the following styles exported along a component called FieldDescriptor via the withStyles HOC:
info: {
fontFamily: theme.typography.fontFamily.light,
fontSize: "12px",
padding: "0 24px 8px 24px",
letterSpacing: 0.3,
},
This class will be found as FieldDescriptor-info-xxx on the element having that class. Now suppose that this component is child to another one that attempts to target the error class. You could target that class with something like [class*=FieldDescriptor-error] (personally I already consider this a very unclean approach) and it will work only on a development environment.
On production, classes will become unique (e.g. jss-xxx) and selectors like the one above will no longer be useful. My question is, what is the cleanest or "correct" approach to customizing component styles like this in JSS? I am either missing something really obvious or perhaps facing the limitations of JSS.
I am looking forward to solutions that do not require more tooling or code bloating, that would really miss the purpose of adopting JSS in the first place.
You can find an example using both withStyles and useStyles here
Try to think of a component as of a black box that intentionally hides implementation details from the outside world.
With this model, only component itself is responsible for it's presentation, so you need to ask that component to change something. In React world you do that most of the time by passing props to that component.
Inside of that component you can go multiple ways, combining the predefined classes depending on the props (preferred because static) or using function rules/values which let you access the props and define the styles per component instance.
We're attempting to upgrade a codebase from styled-components v3 to v4/5. A pattern we were using no longer seems to work, despite it seeming like it should.
The pattern (in pseudocode):
// In reusable component file
const LeafComponent = (props) => whatever
const StyledLeafComponent = styled(LeafComponent)` styles which might reference connect props here `
const ReusableComponent = connectOrWithTheme(StyledLeafComponent)
// In some other file where the reusable component is being customized
const CustomizedComponent = styled(ReusableComponent)` additional context-specific styles here `
In v4 and v5, this pattern fails for both connect() and withTheme, with the corresponding props (either store props, or theme), being undefined in the reusable component (props from connect() are also undefined in the leaf component's styles, where we might need to reference them). In fact, mapStateToProps in connect() doesn't seem to even run at all!
The following workaround which just adds a pass-through component in between seems to fix things:
const CustomizedComponent = styled(props => <ReusableComponent {...props} />)` additional context-specific styles here `
But the problem with this workaround is that it's easy to accidentally omit and introduce sneaky runtime errors, not to mention that the codebase we're upgrading becomes a minefield of possible runtime errors until we do a thorough review of every usage of styled-components that we have along with connect, withTheme, and possibly other similar HOCs (which is a lot). (We're using TypeScript, so compiler errors would be valuable).
Obviously the best solution would be to get this fixed in styled-components itself, but that doesn't seem likely at this point. We've been spending months trying to get a better answer than this, filing an issue against the styled-components repo on GitHub and reaching out in Spectrum. We have a simple and clear reproduction of the issue on codesandbox:
codesandbox example for connect
codesandbox example for withTheme.
No responses.
Is there a better workaround than the one above? Or is that what we're stuck with? Are we misguided in using this pattern in the first place, and should be doing something else? Surely styled-components, react, and redux are all popular libs and are being used together by many. This has to have been encountered and solved before.
I'm a developer of a fairly large react application. A part of the application is a detail form that is loaded dynamically and can consist of about 100 input fields of different data types (different components).
The initial render of this form takes about 500ms on my laptop and i'd like to make it faster. I'm not quite sure what causes this render time but i share with you what i have:
This is a screenshot of the react profiler. As you can see there are some commits happening but one (orange) stands out. This is where the actual form is rendered. The form is a hierarchy of composed layouting boxes, other containers and the field container with the label and the data-type component inside. A single component does not take too long to render, but added up is why I end up with 500ms.
if we take a closer look at a small part of this screenshot above we can see this:
This is a small section inside a date edit field that gets rendered because we have a field of data type date. The first element is a styled component
const StyledDateAbstractWrapper = styled.div`
&& {
align-items: center;
cursor: ${props => props.immutable ? 'not-allowed' : 'default'};
display: flex;
input {
display: ${props => props.immutable ? 'none' : 'block'}
&:last-of-type {
display: ${props => props.immutable ? 'block' : 'none'}
}
}
}
`
as you see, nothing fancy. But it takes 2.3ms to render. And inside a styled button that takes 5 ms. Let's say i have a couple of these and that's how i end up with 500ms.
At this point i really think styled-components is the problem because i have some components take a couple of milliseconds as expected and many many small styled-component wrappers that each take more than a millisecond.
So my question... is there something i can further investigate? Or are there clear no-goes for styled components that are rendered many times such as element selectors?
Im using styled-components 5.0.1
Another approach would be using something like https://github.com/bvaughn/react-window but i dont have a list really. more some composed components.
Providing a running application is a bit tricky at the moment.
thank you for any suggestions
You are right, Styled Components is slowing down your application. It's easy to reason when you think about what each styled object does:
parses the injected props;
generates the actual CSS code;
generates an unique class identifier;
applies the unique identifier to the className of the underlying React object;
injects the generated CSS at the end of the <head>, under the unique class identifier.
Only then the browser renderer will apply the CSS onto the DOM elements.
Remember: this is done for each styled object you create. With many elements, it's easy to see how this blows up, because it doesn't scale up well.
My suggestion is to move away from Styled Components and adopt SCSS modules. You'll end up with a plain static CSS file, and unique class names generated at buildtime, and nothing done at runtime. Can't go faster than that.
Foo.module.scss
.myTitle {
font-size: 3em;
}
Foo.tsx
import css from './Foo.module.scss';
function Foo() {
return <div className={css.myTitle}>Hello</div>;
}
export default Foo;
The cons are that you'll need separate .scss files, and injected values will have to be reworked into class names with the SCSS preprocessor. But there are advantages too: you can have all built-in SCSS features, wich are great.
I can't say much without seeing the proper implementation on this case, but from what I can think of you have a few options.
1 - Instead of having components with display: none you can simply remove them from the DOM with { !immutable && <Component /> }, this way the component won't take space in the VirtualDOM
2 - Second problem could be the Form library you are using, here is a performance overview of some of them. Performance Comparison. Maybe changing the lib you are using also helps.
First and foremost: usability wise, 100 form elements is insane! No user would ever go through the hell of filling this many (or even a quarter) of these elements. I suggest going back to your designer/product people and telling them to come up with a better way to model the business flow.
Really, forget about performance. No user will ever fill this form and it is better to realize and fix it now than in hindsight.
As for performance: 100 form elements is a lot! A desktop browser can surely handle that HTML, but rendering it all in React has its overhead in JS/DOM land. I'd say that half a second is quite good, considering the amount of work the browser has to carry out. And with styled-components in the mix, you're also straining the CSSOM.
There are additional optimizations and techniques which can be applied, though they won't be necessary once you restructure the app to not render so many DOM elements which your users won't be able to handle anyway.