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.
Related
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.
I am using react and came across a situation where I needed a fallback CSS property.
Normal CSS code:
.grid{
position:sticky;
position: -webkit-sticky;
}
Currently, I am getting it done by using two different classes and adding both the classes to the component (using classnames library).
Like this:
<Grid className={classnames(c1,c2)}/>
where c1 and c2 both have position property but with different values.
My question being that am I doing it in the right manner? If not, Is there any workaround?
.c1{
position:sticky;
}
.c2{
position:-webkit-sticky;
}
The browser will apply what it suports. I never worked with React, but I'm pretty sure there'd a better way to write the fall-back properties.
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.
I've got a component that's only ever going to be rendered as part of a list. Let's call it MyListItem. The easiest thing from a coding perspective is to localize the const useStyles = makeStyles(...) right inside MyListItem. But in that case, if there are 100 list items, then I'm potentially invoking useStyles 100 times.
Normally, I'd just say screw it and put the useStyles in the parent (e.g. MyList) and then pass classes down to MyListItem. That way, useStyles is only computed once, rather than 100 times.
However, MyListItem could get invoked by more than 1 parent (e.g. MyListOne, MyListTwo). That means that any parent that wants to invoke MyListItem needs to contain the styles for MyListItem, which is less than ideal.
So here's the question: is Material-UI smart at all about detecting that the same useStyles is being invoked again and again? Will it really write out 100 versions of those styles, or will it collapse all those down at runtime? My guess is the answer is "no", but I figured it was worth asking.
Thanks!
Material-UI is definitely smart in this regard. If it wasn't, you would have the same problem with Material-UI's components such as ListItem. So long as makeStyles is being called at the top-level (which is the typical pattern) such that you are always using the exact same useStyles function, you are fine. A component that uses styles has more overhead than one that doesn't, but it won't duplicate the styles in the DOM.
The smarts in the library can be found here: https://github.com/mui-org/material-ui/blob/v4.9.13/packages/material-ui-styles/src/makeStyles/makeStyles.js#L56
let sheetManager = multiKeyStore.get(stylesOptions.sheetsManager, stylesCreator, theme);
This is the cache lookup for a particular style sheet. stylesOptions.sheetsManager will always be the same unless you are customizing it (very uncommon) for part of your component hierarchy using StylesProvider. stylesCreator is the argument to makeStyles -- i.e. the declaration of your styles. So for the same makeStyles call and the same theme, it will find the same styles and avoid re-generating them.
You can easily verify this is all working as intended by looking at the <style> elements in the <head> of your document via developer tools. The image below is from this page which is a sandbox for this other StackOverflow answer.
You can see in the image style elements for the different Material-UI components used in the page (e.g. MuiSvgIcon, MuiListItem, etc.). You can also see one generated from makeStyles and one generated from withStyles. The withStyles case is similar to what you are describing -- it is styles for a ListItem component that is used multiple times in the page, but the styles only occur once in the document.
To better explain my question, I'll use an example:
Let's say I have two react elements, which contain one another. Lets call the container Box, and the child Stuff. Box just renders a div with className="box" which surrounds the children it is given. Stuff most of the time renders something, but its render function can return null when it decides there's nothing to render.
Here's the twist: if <Box> is empty, I don't want to show it at all. So I decided to use css3 selectors, and write something like
.box:empty {
display: none;
}
... which should work, except react renders a <noscript> tag, which prevents the browser from treating the parent .box element as empty...
Is there an elegant way around this? I'd like to keep the logic of determining emptiness inside of Stuff, and have Box just "look" at its contents and decide whether it wants to show anything or not.
UPDATE
This fiddle https://jsfiddle.net/69z2wepo/67543/ has an example of what I'm trying to do. Strangely, in that fiddle, it works, and react doesn't render <noscript> tags... why does it render <noscript>s in my code? In what cases does react choose to render <noscript>s?
Found the problem!
I was using an old version of react-css-modules (3.7.7). Upgrading to 4.1.0 fixed it.
Looks like this was a relatively recent fix, too:
https://github.com/gajus/react-css-modules/commit/a9c8de252d5464037090e155c431abfe9f671531