Is there a way to make React Context type-safe with flow type?
For example :
Button.contextTypes = {
color: React.PropTypes.string
};
Unfortunately, it is inherently not possible because Context is not known at compile time (so I was told).
A bit of a workaround I use is pulling the the context from the consumer at the parent level, and then calling proptypes at the child...
Parent
//parent
class Parent extends component {
render(){
return (
<Consumer>{(context)=>{
const { color } = context
return(
<div>
<Button color={color} />
</div>
)}}</Consumer>
}
Child
//Button
...
Button.contextTypes = {
color: React.PropTypes.string
};
...
Related
I have two components using Stencil (TypeScript), a parent and a child.
In the parent I accept props like textColor, fontFamily, etc. and use them in a function to call them in the return statement as a style. Also, I use another function to declare the props that need to be passed down.
What I'm now failing is to pass the function down to the child which is in a slot. Also I have the feeling that there is a smarter way to handle it with one instead of two functions.
Any help is highly appreciated.
Parent
import { Component, h, Prop } from '#stencil/core';
interface Styles {
[key: string]: string;
}
#Component({
tag: 'my-parent',
shadow: true,
})
export class MyParent {
#Prop() textColor: string;
#Prop() fontFamily: string;
getAccordionProps(): Styles {
return {
color: this.textColor
fontFamily: this.fontFamily
};
}
props(): Styles {
return {
fontColor: this.textColor,
fontWeight: this.fontWeight,
fontFamily: this.fontFamily,
};
}
render() {
return (
<Host>
<div class="some-content" style={this.getAccordionProps()}>
</div>
<slot {...this.props()}/>
</Host>
);
}
}
Child
import { Component, h, Prop, Method } from '#stencil/core';
#Component({
tag: 'my-child',
shadow: true,
})
export class MyChild {
#Prop() textColor: string;
#Prop() fontFamily: string;
render() {
return (
<Host style={this.getAccordionProps()>
{/* Use the <slot> element to insert the text */}
<slot />
</Host>
);
}
}
You may be misunderstanding what a slots are and how they work. A slot in a component is a place where any DOM can be added inside the light DOM of a component. A <slot> element uses only one property/attribute - "name", so applying other properties to the slot element does nothing.
Slotted components and their parent components do not have any special access to each other - only standard DOM just like how a <span> within a <div> would have no special access to each other. So for example a child component does not inherit functions from its parent component. However, it can find its parent in the DOM and call the parent's functions. As in:
export class MyChild {
#Element() hostElement;
...
render() {
return (
<Host style={this.hostElement.parent.getAccordionProps()}>
<slot />
</Host>
);
}
}
The problem with this approach is that my-child is completely dependent on my-parent in order to work properly. It's better to keep separate components separate - don't assume a certain parent or child, instead design the components so they can be used independently. For example, to apply attributes to a slotted element like <my-child> (not the <slot> element itself), you would do that in the DOM not in the parent. For example:
<my-parent text-color="..." font-family="...">
<my-child font-color="..." font-family="...">
...
</my-child>
</my-parent>
If you want to apply properties to a slotted component from the parent, you need to find the component as an element and manipulate it as standard DOM. For example:
private myChildElement;
#Element() hostElement;
export class MyParent {
componentWillLoad() {
const child = this.hostElement.querySelector('my-child');
if (child) {
child.setAttribute('font-color', this.textColor);
...
}
}
...
}
Of course, the problem with this approach is that you may not know how the <my-parent> component is being used - it could have none or more than one <my-child> elements. This is why the DOM example above is preferred.
An alternative is to include <my-child> in the <my-parent> template instead of the slot, as mentioned in comments above:
render() {
return (
<Host>
<div class="some-content" style={this.getAccordionProps()}>
</div>
<my-child {...this.props()}></my-child>
</Host>
);
}
The problem with this approach is that it's not flexible to other usages (other content inside the parent), and may require updating both components if <my-child> is updated. Also, if <my-child> is only ever used this way, then whether it needs to be a separate component is questionable, because you could easily include it in <my-parent>:
render() {
return (
<Host>
<div class="some-content" style={this.getAccordionProps()}>
</div>
{/* formerly my-child */}
<div ...>
<slot />
</div>
</Host>
);
}
Parent
export class MyParent {
private children;
#Element() host: HTMLElement;
#Prop() prop1;
#Prop() prop2;
componentDidLoad() {
this.passPropsToChildren()
}
private passPropsToChildren = () => {
this.children = this.host.querySelectorAll('child');
this.children.forEach(child => {
child['prop1'] = this.prop1;
child['prop2'] = this.prop2;
});
}
render() {
return (
<Host>
<div class="some-content" style={this.getAccordionProps()}></div>
<slot />
</Host>
);
}
}
Child
export class Child {
#Prop() prop1;
#Prop() prop2;
render() {
return (
<Host style={this.getAccordionProps()>
{/* Use the <slot> element to insert the text */}
<slot />
</Host>
);
}
}
Consider a component that renders a button and says this button should have a red background and a yellow text color. Also there exists a Parent component that uses this child but says, the yellow color is fine, but I want the background color to be green.
withStyles
No problem using the old withStyles.
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import { Button } from "#material-ui/core";
const parentStyles = {
root: {
background: "green"
}
};
const childStyles = {
root: {
background: "red"
},
label: {
color: "yellow"
}
};
const ChildWithStyles = withStyles(childStyles)(({ classes }) => {
return <Button classes={classes}>Button in Child withStyles</Button>;
});
const ParentWithStyles = withStyles(parentStyles)(({ classes }) => {
return <ChildWithStyles classes={classes} />;
});
export default ParentWithStyles;
https://codesandbox.io/s/passing-classes-using-withstyles-w17xs?file=/demo.tsx
makeStyles/useStyles
Let's try the makeStyles/useStyles instead and follow the guide Overriding styles - classes prop on material-ui.com.
import React from "react";
import { makeStyles } from "#material-ui/styles";
import { Button } from "#material-ui/core";
const parentStyles = {
root: {
background: "green"
}
};
const childStyles = {
root: {
background: "red"
},
label: {
color: "yellow"
}
};
// useStyles variant does NOT let me override classes
const useParentStyles = makeStyles(parentStyles);
const useChildStyles = makeStyles(childStyles);
const ChildUseStyles = ({ classes: classesOverride }) => {
const classes = useChildStyles({ classes: classesOverride });
return (
<>
<Button classes={classes}>Button1 in Child useStyles</Button>
<Button classes={classesOverride}>Button2 in Child useStyles</Button>
</>
);
};
const AnotherChildUseStyles = props => {
const classes = useChildStyles(props);
return (
<>
<Button classes={classes}>Button3 in Child useStyles</Button>
</>
);
};
const ParentUseStyles = () => {
const classes = useParentStyles();
return <>
<ChildUseStyles classes={classes} />
<AnotherChildUseStyles classes={classes} />
</>
};
export default ParentUseStyles;
https://codesandbox.io/s/passing-classes-using-usestyles-6x5hf?file=/demo.tsx
There seems no way to get the desired effect that I got using withStyles. A few questions, considering I still want the same effect (green button yellow text) using some method of classes overriding (which seemed to make sense to me before).
How is my understanding wrong about how to pass classes as means to override parts of them using useStyles?
How should I approach it alternatively?
And if I'm using the wrong approach, why is material-ui still giving me a warning when the parent has something in the styles that the child doesn't have?
the key something provided to the classes prop is not implemented in [Child]
Is the migration from the old approach (withStyles) vs the new approach documented somewhere?
Btw, I'm aware of this solution but that seems cumbersome when you have too much you want to override.
const useStyles = makeStyles({
root: {
backgroundColor: 'red',
color: props => props.color, // <-- this
},
});
function MyComponent(props) {
const classes = useStyles(props);
return <div className={classes.root} />;
}
withStyles has very little functionality in it. It is almost solely a wrapper to provide an HOC interface to makeStyles / useStyles. So all of the functionality from withStyles is still available with makeStyles.
The reason you aren't getting the desired effect is simply because of order of execution.
Instead of:
const useParentStyles = makeStyles(parentStyles);
const useChildStyles = makeStyles(childStyles);
you should have:
const useChildStyles = makeStyles(childStyles);
const useParentStyles = makeStyles(parentStyles);
The order in which makeStyles is called determines the order of the corresponding style sheets in the <head> and when specificity is otherwise the same, that order determines which styles win (later styles win over earlier styles). It is harder to get that order wrong using withStyles since the wrapper that you are using to override something else will generally be defined after the thing it wraps. With multiple calls to makeStyles it is easier to do an arbitrary order that doesn't necessarily put the overrides after the base styles they should impact.
The key to understanding this is to recognize that you aren't really passing in overrides, but rather a set of classes to be merged with the new classes. If childClasses.root === 'child_root_1' and parentClasses.root === 'parent_root_1', then the merged result is mergedClasses.root === 'child_root_1 parent_root_1' meaning any elements that have their className set to mergedClasses.root are receiving both CSS classes. The end result (as far as what overrides what) is fully determined by CSS specificity of the styles in the two classes.
Related answers:
Material UI v4 makeStyles exported from a single file doesn't retain the styles on refresh
Internal implementation of "makeStyles" in React Material-UI?
In Material-ui 4.11.x while creating styles using makeStyles wrap the enclosing styles with createStyles, and this style will have highest priority than the default one.
const useStyles = makeStyles((theme: Theme) =>
createStyles({
backdrop: {
zIndex: theme.zIndex.drawer + 1,
color: '#fff',
},
}),
);
You could try removing the createStyles and see the difference.
code source from https://material-ui.com/components/backdrop/
One way to achieve this using withStyles is the following and can be helpful to override css classes.
Supposing that you want to override a class called ".myclass" which contains "position: absolute;":
import { withStyles } from '#material-ui/styles';
const styles = {
"#global": {
".myClass": {
position: "relative",
}
}
};
const TestComponent = (props) => (
<>
<SomeComponent {...props}>
</>
);
export default withStyles(styles)(TestComponent);
After doing this, you override the definition of .myClass defined on <SomeComponent/> to be "position: relative;".
I have two custom components called Grid and FieldValue and I use the FieldValue component multiple times on a particular page. I am using a class name called .black for all the FieldValue components. Now, I want to use a different class name called .blue-pointer where the data in FieldValue says view2. please help me understand how to do it.
Components on the page look like below
<Grid>
<FieldValue data={'view1'}/>
<FieldValue data={'view2'}/>
<FieldValue data={'view3'}/>
</Grid>
And the FieldValue is defined as below,
class FieldValue extends React.Component<>{
render(){
<div className="black">
{'testView'}
</div>
}
}
And the CSS is defined as below
.black{
color:#4d546d;
}
.blue-pointer {
color: #0070d2;
cursor: pointer;
}
Use props from your component :
class FieldValue extends React.Component{
render() {
return (
<div className={this.props.data === 'view2' ? 'blue-pointer' : 'black'}>
{'testView'}
</div>
);
}
}
You can define className as a prop, and give it a default value of 'black'.
class FieldValue extends React.Component<> {
static defaultProps = {
className: 'black'
}
render() {
const { className } = this.props
<div className={className}>
{'testView'}
</div>}
}
For the default case, you don't need to change anything:
<FieldValue data={'view1'} /> // Still uses the `black` style.
When you want to change the class name, use:
<FieldValue data={'view2'} className='blue-pointer' />
I have React parent component A which has its own scss file a-style.scss. Component B is child of A. A is passing styleInfo object as props which is applied on B.
My question is - is there any way we can define styleObj in a-style.scss instead of defining it inline. I want all styling related info should be in external scss file.
Component A
import "./a-style.scss";
import B from "./B.js";
class A extends Component {
constructor(props) {
super(props);
}
const styleObj = {
backgroundColor: "#F9F9F9",
borderRadius: '2px',
color: "#686868",
};
render() {
return (<B styleInfo={this.styleObj}></B>);
}
}
Component B
class B extends Component {
constructor(props) {
super(props);
}
render() {
return (<div style={this.props.styleInfo}></div>);
}
}
The standard way is to define CSS properties based on class in your scss/css. And then pass className from props in your React component:
class A extends Component {
theme = "themeA";
render() {
return (<B styleInfo={this.theme} />);
}
}
class B extends Component {
styleClass = ["B"];
render() {
const className = styleClass.push(this.props.styleInfo).join(' ');
return (<div className={className} />);
}
}
.themeA {
background-color: #F9F9F9;
border-radius: 2px;
color: #686868;
}
.B {
/* Some style for B component */
}
Why not just import that one file directly into B.js?
Is there any benefit of having it go through a parent, seems like necessary routing to me!
If you do need this, then I would just keep it in JS, as this is what JS is good at, or at least, have JS just do the className switching and, again, just have one css file that is a main style lookup hash!
Best of luck!
At which point in a React components life cycle can I get the components css properties which are set in a css file?
I've tried it in the render method and the componentDidMount method and neither assigned the css properties to the component.
export default class HomeArtist extends React.Component {
constructor(props){
super(props);
}
componentDidMount(){
let ImageStore = document.getElementsByClassName('home-artist-display');
console.log("ComponentDidMount: ", ImageStore);
}
render(){
var ImageStyle = {
backgroundImage: "url("+this.props.info.image+")"
};
return (
<div className="home-artist-display" style={ImageStyle}>
<Link to={"artist/" + this.props.info.id}>
<h3 className="home-artist-name">{this.props.info.name}</h3>
</Link>
</div>
)
}
}
I wrote a React library that exposes a size object (with width and height props) to components.
For your use case you could use it like so:
import SizeMe from 'react-sizeme'; // import me!
class HomeArtist extends React.Component {
...
render(){
// Size gets passed in as props!
const { width, height } = this.props.size;
var ImageStyle = {
backgroundImage: "url("+this.props.info.image+")"
};
return (
<div className="home-artist-display" style={ImageStyle}>
<Link to={"artist/" + this.props.info.id}>
<h3 className="home-artist-name">{this.props.info.name}</h3>
</Link>
</div>
)
}
}
// wrap your component export!
export default SizeMe()(HomeArtist);
--
You can find out full details at https://github.com/ctrlplusb/react-sizeme