React styled-components style a dynamic component - javascript

I have a following problem:
I have defined some icons in my project. Each icon is defined by a component type (an icon from Material-UI) plus some additional props. I did it that way, so I can then render those icons in differing ways.
Anyway, let's say that I want to re-use the same icon in 2 places, but in one place I want it to be red, in the other I want it to be green. MUI icons don't accept a color prop (I mean not an RGB one, just a few predefined colors). So the way to do it is to style the icon component with css (color:).
I use styled-components so the way I would usually do this is:
const StyledIcon = styled(AssignmentIcon)`
color: red
`
But the thing is, in some places I don't know which icon I'll be styling. For example some component may receive a list with AssignmentIcons and BugReportIcons. As it is with styled-components, I cannot define the styled components inside another component - so I cannot create a style like the one above, and just pass icons from the props to it.
I solved it like this: https://codesandbox.io/s/nostalgic-hofstadter-ww2ly. Basically, I have a styleIcon function, which takes a React.ComponentType (dynamically of course), puts in in styled() and returns it. This way I've applied some style to the icon and now I can render it.
My question is: this seems kinda sketchy, having some helper functions to do the job. Is there a simpler way to do it? (and by the way, I cannot use style={{}} props when rendering the icon - it may collide with other styles that I would apply via prop-spreading)

Your component should always be able to switch to different color.
const color = props => props.color || 'red'
const StyledIcon = styled.div`
color: ${color};
`
If the color is given, then use color, ex.
return <StyledIcon color="blue" />
I don't know if this solves your problem. Sometimes we want to use a theme mode, for example StyledComponent does support a theme.
https://styled-components.com/docs/advanced
Even when you don't want to always use one theme, you can apply different theme at run time.
<ThemeProvider theme={theme1}>
// A
<ThemeProvider theme={theme2}>
// B
<Button>Themed</Button>
</ThemeProvider>
// C
</ThemeProvider>
// D
The locations A, B, C, D gets different themes:
A. theme1
B. theme2
C. theme1
D. defaultTheme
I believe the base solution should give u some ideas, but if not, try this sophisticated theme options offered by StyledComponent.

Based on your problem, I assume that you want to change the color from Material UI without explicitly styling. So I came up with a wrapper that takes Material UI icons as one child node.
First, we need to create the styling for that wrapper:
const StyledIcon = styled.span<{color: string}>`
svg, .icon {
color: ${props => props.color}; // You can even pass a hex color
}
`
Secondly, create a separate React component .You use can the styled one alone but this will help you with handling states and effects in the future:
interface ColorfulIconProps {
color: string;
[k: string]: any;
}
const ColorfulIcon: React.FC<ColorfulIconProps> = ({ color, children, ...rest }) => {
// States and effects
return (
<StyledIcon color={color} {...rest}>
{children}
</StyledIcon>
);
};
Finally, to use it, just simply pass any Material UI icons you want, or your own icon set as long as they're formatted in svg
const App = () => {
return (
<ColorfulIcon color="red">
<AMaterialUIIcon />
</ColorfulIcon>
);
};
Live demo:
https://codesandbox.io/s/modern-brook-oecgn?file=/src/StyledIcon.tsx

Related

How do Styled Components forward props?

I have been working with Styled Components for a while now but I'll be honest I never really understood how it works. That is what my question is about.
So, I understand styled components can adapt based on props. What I don't understand is how does the forwarding of props work.
For Example in my case, Im working with React Native. There are some default props which I have given to my input field. Now, styled component wrapper automatically picks up the default height prop but If I explicitly pass the prop it does not pick it up and I have to manually get it from props. What is that about?
import React from "react";
import styled from "styled-components/native";
const StyledTextInput = styled.TextInput`
/* Why do I have to do this for custom heights.
* Isn't height automatically get forwarded?
*/
/* if I comment this height style out, It takes defaultProp height but doesn't take custom height
* I have to uncomment it for the custom height
*/
height: ${({ height }) => height};
...other styles
`;
const TextInput = ({ height }) => {
return <StyledTextInput height={height} />;
};
TextInput.defaultProps = {
height: 50
};
export default TextInput;
// Then In some Smart Component
class App extends Component {
render() {
return (
<View style={styles.app}>
<TextInput height={200} /> // ???
...other stuff
</View>
);
}
}
Here are my Questions:
What are the cases in which styled component automatically picks up the prop?
Which props are automatically forwarded.
How does this prop forwarding work?
Documentation doesn't talk much about that or maybe I have missed it.
Any help would be much appreciated.
All standard attributes are automatically forwarded props. Standard attributes like defaultValue and type. Take note of the camel case notation for attributes in React.
If it's a custom attribute like someCustomAttribute prop, it's not passed directly to DOM.
If there are props that are true to all instances of the styled component, you can make use of .attrs.
const StyledTextInput = styled.TextInput.attrs(props => ({
height: props.height
}))`
// other styles
`

Style a styled-components within a wrapper

I would like to add an animation and a specific height to a Button that is styled. The thing is, my StyledButton is a wrapper that can render one of multiple pre-styled buttons according to a type prop which are styled React Semantic UI Buttons.
See the CodeSandbox with reproduction here :
https://codesandbox.io/embed/practical-haibt-oz9sl
The thing is it does get the styles from the ActionButton but it does not apply whatever style I put on the const AnimatedButton = styled(StyledButton).
But, if I try the same thing without the wrapper, directly by importing the BaseButton, and creating a AnimatedBaseButton, this one works but
removes the modularity of having a type prop that returns a pre-styled button.
I searched here and on google / github, but there's no issue that reflects this one. I know I could add an animation property on the StyledButton and pass it, but with the real codebase, it's not possible.
Thanks in advance !
EDIT : Added a Codesandbox instead of code example.
Quick fix:
In StyledButton.js:
render() {
const {
content,
icon,
iconPosition,
onClick,
type,
...otherProps // take base props passed through wrapper
} = this.props;
// ...
return (
<ButtonToDisplay
{...otherProps} // spread it firstly here so below props can override
onClick={onClick}
content={content}
/>
);
}
Why it works:
As you can see, styled(comp)'' syntax we use to style our component is actually a HOC component under the hood, which takes in a component and returns another component.
So when you make a wrapper that intercepts between a styled component and the real component, you need to allow props that generated by the library go through that wrapper.
You forgot the ... (spread operator) while destructing this.props
export default class StyledButton extends React.Component {
render() {
// added ... (spread operator)
const {type, ...additionalProps} = this.props
if (type === 'normal') return <NormalButton {...aditionalProps} />
else if (type === 'action') return <ActionButton {...aditionalProps} />
}
}
What is happening here is that styled-component pass the styles in the style prop, but with out the spread operator, you aren't passing it, you are just getting a prop that is called additionalProps.

Can I make styles apply to classes other than the first defined in a file?

I have inherited some code and I've been manipulating it, but I came across something that makes me scratch my head.
I am not sure whether the issue I am having relates to specifically to react.js or more generally to CSS / javascript...
In this code, we make use of react.js withStyles.
I have set up a sandbox to demonstrate.
First thing in the file, we define some styles that can then be applied to page elements, e.g.
const styles = theme => ({
buttonGroup: {
width: "250px",
display: "flex",
flexWrap: "wrap",
}, ...
then, when we define a class, we can get access to these styles by doing a const { classes } = this.props , e.g.
class MyButtons extends React.Component {
render() {
const { classes } = this.props;
return (
<div className={classes.buttonGroup}>
{three_buttons.map(e => (
<Button className={classes.a_button}>{e}</Button>
))}
</div>
);
}
}
That is all fine and works.
What I've tried is to, in the same file, define a second class, which I then call from the first (to make a component within a component).
However, the const { classes } = this.props does not seem to gain me access to the styles defined at the top, and when I try to set className={classes.buttonGroup} in the second class, I get an error
TypeError: read property 'buttonGroup' of undefined
I am pretty sure I can overcome this by simply defining the second class in a separate file like I usually do, but I would like to understand why this does not work.
You are not passing the styles as props to MyOtherButtons Component and hence you are getting this issue. Pass the classes as props and things would work as expected. It works for MyButtons component since you are passing the styles using withStyles syntax.
Check the working link https://codesandbox.io/s/m3rl6o2qyj

Positioning a component in .jsx-file and not via a .css-file

I'm looking for a solution how to position a component in a .jsx-file instead of a .css-file because I have multiple files of the same component, but each one is responsible for different tasks so I need them in different positions of the page.
I don't want to have multiple copies of the same file with only a minor change of the css class - I would rather like to add changes to the .jsx-file, or if you know how to achieve that using a .css-file, please let me know.
Example:
I have a 'Fish'-file which gives the basic structure of what the fish will look like.
I have to make multiple fish files(i.e fish.jsx, fish1.jsx, fish2.jsx) because they each use a different css class for their positioning. How can I reduce the amount of Fish.jsx to one either by adding to the original fish.jsx or .css-file used?
The way I would approach your problem would be something like this
import Fish from './Fish';
import './Fish.css';
// other stuff
const FishContainer => (
{[...Array(10)].map( (_, i) => <Fish className=`fish-${i}` /> )}
);
export default FishContainer;
And in your css file
.fish {
&-1 {}
&-2 {}
// etc
}

Why are higher-order components a lot more useful than the regular component?

I can't seem to grasp the understanding of why higher order components are highly valued over the regular components? I read a tutorial on them and that higher order components are good because they: 1) Allow code re-use, logic and bootstrap abstraction. 2) Able to do render highjacking. 3) Able to abstract state and manipulate them. 4) Able to manipulate props. Source: link
An example of a higher order component in code was shown there as:
function refsHOC(WrappedComponent) {
return class RefsHOC extends React.Component {
proc(wrappedComponentInstance) {
wrappedComponentInstance.method()
}
render() {
const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
return <WrappedComponent {...props}/>
}
}
}
These look almost the same exact code as a regular class definition that receives props and you are still able to "manipulate props" and "manipulate state" inside that class
class Something extends React.Component {
constructor(props) {
super(props);
this.state = { food: 'no_food_received_yet' }
}
componentDidMount() {
this.setState({ food: 'apple' });
}
render() {
return (
<div>
<p>{ this.state.food }</p>
<h2>{ this.props.food }</h2>
</div>
);
}
}
Actually I didn't get to manipulate or mutate the props but I can just receive them, apply them to state and output that state.
This is not to bash on higher order components---this is actually the complete opposite. I need to understand where I am wrong and why I should integrate higher order components into my react app.
HOCs are absolutely useful, but they're useful in the same way any "higher order" function is.
Consider the following component:
let Button = props => <button style={{ color: props.color }} />
You could make another component called BlueButton:
let BlueButton = props => <Button color="blue" />
There's nothing wrong with that, but maybe you want any component to be able to be blue, not just a button. Instead we can make a generic HOC that "blueifies" the passed component:
let blueify = Component => props => <Component {...props} style={{ color: 'blue' }} />
Then you can make blue buttons, blue divs, blue anything!
let BlueButton = blueify(Button)
let BlueDiv = blueify(props => <div {...props} />)
Imagine you had an app where certain screens were private and only available to authenticated users. Imagine you had 3 such screens. Now on each screen's container component you can have the logic to check if the user is authenticated and let them through if they are, or send them back to login when they are not. This means having the exact same logic happen 3 times. With a HOC you can code that logic just once and then wrap each private screen in with the HOC thus giving you the same logic over and over, but the code only needs to exist in one place. This is an example of great code reuse that HOCs offer.
Higher order components are more generalized, and so in theory can be applied to a broader number of cases. In the more general sense, higher-order components (and higher-level languages) tend to be more expressive.
In your case, the difference is readily apparent. The non-generalized (lower-order) component has hard-coded HTML in it. This is programming 101; constant declarations like PI are preferred over hard-coded declarations like 3.14159 because they give meaning to magic numbers and allow one point of modification.
A simple example of a higher order "component" (in this case a higher order function) is a sort function that takes as one of its arguments an ordering function. By allowing the ordering to be controlled by an outboard function, you can modify the behavior of the sort without modifying the sort function itself.

Categories