I have a react component library implemented with styled components. Long story short, the company changed it's design system and they decided they need both styling, old and new, to be available for different projects(no runtime switching). So for now, we have two versions of the library, on two main branches of the same project, the difference being in the styled components files.
The problem is that new components or fixes need to be implemented in both versions, and what we are doing now is manually syncing the branches for the behavioral part of the components, but this task is becoming harder and harder to handle.
Some of the ideas were splitting the library in two, old and new, but doesn't solve the syncing problem; or splitting into three, behavioral, old style, and new style, and the behavioral library should somehow (don' know how) use one or the other; or let the app switch between the styled components at runtime, but that seems to add unnecessary complexity; or switching back to sass instead of styled components and let the user app choose which sass file will load (but I would be really sad to give up on the styled components).
Any ideas, advice, warnings, been-there-done-that-ers? :)
Perhaps you can try something like this: React Theme Management in Less with Javascript? Or perhaps even: Theming in React with Styled Components.
To me it seems like it isn't so much about splitting up the library into different components (which can get super messy over time). Rather it would be more about finding a way to make the styling abstract in such a way that you have different themes (Old Theme vs New Theme) and can choose either one as you use the library for other projects.
I really love theme management in styled-components. I believe this approach would make switching themes a breeze. Let me try to explain with a simple example.
Step 1 - Use a theme-file with e.g. theme_one.js and theme_two.js.
const theme_one = {
primary: "hsl(233, 26%, 24%)",
secondary: "hsl(136, 65%, 51%)",
};
const theme_two = {
primary: "hsl(024, 16%, 4%)",
secondary: "hsl(112, 25%, 81%)",
};
Step 2 - Import the relevant theme into your root App Component and then use the ThemeProvider
import { ThemeProvider } from "styled-components"
as a context provider in your App.js. The following code will make that theme available in every child-component.
<ThemeProvider theme={theme_one}>
<App />
</ThemeProvider>
Step 3 - Access the theme in any downstream component:
const StyledComponent = styled.div`
background-color: ${props => props.theme.primary}`
With this method, you would need to plug in either theme_one or theme_two in your context provider and watch the beauty unfold.
Related
Wondering if anyone can lay out how one avoids a situation wherein a React application that uses material-ui classNames from makeStyles, and which includes a package that ALSO does that, hence two name generators, results in multiple, conflicting .jss1, .jss2, etc. in the production rendered page that includes both the app's components as well as the included package's.
I have control of both, so I can modify either to have a prefix, but I'm not at all clear on where one injects that into a v4 material-ui project.
Now, I know that createGenerateClassName allows one to pass in a prefix, but it's not clear to me how one injects that into the ThemeProvider. I wrapped the whole thing in a StylesProvider and passed the resulting generateClassName to that, but that seemed to be ignored.
Well, allow me to answer my own question for future generations. Turns out that you can indeed wrap the ThemeProvider with a StylesProvider with a generateClassName argument:
const generateClassName = createGenerateClassName({
productionPrefix: 'coreJss'
});
...
<StylesProvider generateClassName={generateClassName}>
<ThemeProvider theme={MyTheme}>
Stuff
</ThemeProvider>
</StylesProvider>
Now, your production classNames will be coreJss1, coreJss2... for that package and jss1, jss2...
Now, you might ask 'why didn't yours work in the first place?' and I would tell you to pay close attention to what you actually committed vs. what you typed and didn't save.
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'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.
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.