Avoid magic strings when working with Chakra Ui - reactjs

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.

Related

Identify current theme of MS Teams in react bases component

I am working on the MS Teams app where I have a page in which I want to identify current MS Teams theme (default, dark or high contrast).
I have added this https://www.npmjs.com/package/msteams-react-base-component dependency where it explaining how we can extract teams theme. Below is my code snippet which always giving me themeString as default.
export default function FioriTab(): ReactElement {
const [{ themeString }] = useTeams({});
useEffect(() => {
console.log(themeString)
}, [themeString]);
}
Any idea what I am doing wrong here.
I tried adding setThemeHandler which also returning theme as undefined.
With respect to Wictor, I don't know much about that library you're referencing, but according to the docs on this one (https://github.com/OfficeDev/TeamsFx), it's based on his anyway and probably better maintained, considering. It also has a useTeams hook that aims to do the same thing.
If you have a totally new solution anyway, perhaps best is to start with the Teams Toolkit in any case, as it will scaffold this for you.
On a separate but related note, see that useTeams also allows you to send a theme handler - this is important to handle theme changes while a user is using your apps - Teams will let you know the theme has changed so you can respond accordingly.
With regards your main question, the reason you're always seeing the "default" theme, from what I can see in the code for this framework, is because of the asynchronous setting of the theme state over here. It can be easily solved, per my suggestion above, of implementing the theme change event handler that you need to implement anyway. Basically, you should have something like this:
export default function FioriTab(): ReactElement {
const [themeString, setThemeString] = useState<string>("default");
const [{ inTeams }] = useTeams({}, (theme: string | undefined) => {
setThemeString(theme || "default");
});
...

Should I test all component props?

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.

How best to create custom styled components in MUI

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>

Large react app with styled components performance problems

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.

Office UI Fabric - How to apply css styles to existing components

I'm using the provided components and every time I need to change a component style I wonder what's the proper way to do it.
Lets say I need to change the IconButton background color when it's disabled.
https://codepen.io/elsl/pen/KrQQdV
If I provide a theme, how am I supposed to know which palette/semanticColor is used by that component?
const iconsTheme = Fabric.createTheme({
semanticColors: {
disabledBackground: "#ff9933"
}
});
<Fabric.IconButton
iconProps={{iconName:'ChevronRight'}}
disabled
theme={iconsTheme}
/>
If I provide an IButtonStyles, how am I supposed to know that the property name is "rootDisabled.backgroundColor"?
const iconButtonStyles: IButtonStyles = {
rootDisabled: {
backgroundColor: "#ff0000",
}
};
<Fabric.IconButton
iconProps={{iconName:'CalculatorEqualTo'}}
disabled
styles={iconButtonStyles}
/>
For both these options, I had to dig into the component's source code on github to find out.
Is this the expected/correct way?
If so, between creating a Theme or an IStyle which would be the ideal/best practice?
Theme vs IStyles
I would say, use a Theme if you want all Fabric components to have the same customization.
Use the styles property if you just want to customize that specific component (or that one specific instance of the component).
How to discover the styling hooks if using IStyles
There are four ways that comes to mind.
Look at the documentation (e.g. https://developer.microsoft.com/en-us/fabric#/components/dropdown, look at the IDropdownStyles interface)
(screenshot)
Utilize IntelliSense if you're using an editor like Visual Studio Code, which automatically enumerates the IComponentStyles and provides documentation if any.
Inspecting the DOM often provides hints (the hook areas usually look like {area}-{number} so root-33 for instance where the "area" name is root.
Read the source code.
Unfortunately for option 1 and option 2, Fabric React isn't super consistent with the IComponentStyles documentation so not all components have equally descriptive comments and in those cases, you may need to fallback to option 3 and option 4.

Resources