Extend JSS style class instead of overwriting it - javascript

In my React app, I use React JSS for styling. Suppose I have these two files (skipping imports and another non interesting stuff).
This is App.js:
const styles = {
root: {
backgroundColor: '#ffffff',
},
header: {
backgroundColor: '#ff0000',
}
};
class App extends Component {
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<Header classes={{ root: classes.header }}/>
</div>
);
}
}
export default withStyles(styles)(App);
and this is Header.js:
const styles = theme => ({
root: {
backgroundColor: '#0000ff',
padding: '1em',
},
});
class Header extends Component {
render() {
const { classes } = this.props;
return (
<header className={classes.root}>
Hello header
</header>
);
}
}
export default withStyles(styles)(Header);
What I would like to have is "overriding" the style of the root component of Header without overwriting it completely. I can do either of two things:
use <Header className={classes.header}/>, which results in the header element having the class App-root-0-1-2, which means the background is blue with the padding;
use <Header classes={{ root: classes.header }}/> (as above), which results in the header element having the class App-header-0-1-2, which means the background is read without padding.
It seems I can only have either the style defined by the component OR the one that the parent defines to override it. However, I would like to extend the internal style with the one passed by the parent - of course, with the parent taking precedence in conflicts. In this case, I wish to have the red background with the padding.
How can I achieve that? Is it impossible - do I need to pass the editable style as a property?

You can provide an external class name and use classnames (https://github.com/JedWatson/classnames) (or just inline them) to conditionally render this class name if present:
import classNames from "classnames";
const styles = theme => ({
root: {
backgroundColor: '#0000ff',
padding: '1em',
},
});
class Header extends Component {
render() {
const { classes, className } = this.props;
return (
<header
className={classNames({
[classes.root]: true,
[className]: className
})}>
Hello header
</header>
);
}
}
export default withStyles(styles)(Header);
Then use it:
<Header className={classes.myParentClass} />
This will result in a class names, e.g. Header-root-0-1-2 App-myParentClass-0-4-3

Related

Passing props to makeStyles react

I have this component
const MyComponent = (props) => {
const classes = useStyles(props);
return (
<div
className={classes.divBackground}
backgroundImageLink={props.product?.image}
sx={{ position: "relative" }}
></div>
);
};
export default MyComponent;
I am trying to pass backgroundImage link in props and trying to put into makeStyles
export default makeStyles(props => ({
divBackground:{
background:`url("${props.backgroundImageLink}")`,
}
}));
But this does not works
& I am getting this warning in console
index.js:1 Warning: React does not recognize the `backgroundImage` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `backgroundimage` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
You're not supposed to pass arbitrary attributes to the native elements (div in this case) because it doesn't do anything. The prop only works when passed in useStyles:
export default makeStyles({
divBackground: {
background: props => `url("${props.product?.image}")`,
}
});
Usage
const MyComponent = (props) => {
// you only need to pass the props here. useStyles will then use the
// prop.product.image to create the background property, generate a
// stylesheet and return the class name for you.
const classes = useStyles(props);
return (
<div
className={classes.divBackground}
// remove this line -----> backgroundImageLink={props.product?.image}
sx={{ position: "relative" }}
></div>
);
};
const MyComponent = (props) => {
const classes = useStyles(props)();
return (
<div
className={classes.divBackground}
backgroundImageLink={props.product?.image}
sx={{ position: "relative" }}
></div>
);
};
export default MyComponent;
then :
export default useStyles=(props)=>makeStyles(()=> ({
divBackground:{
background:`url("${props.backgroundImageLink}")`,
}
}));

Are props passed from parent to child by default with `this`?

I am learning through an open source project here. I have deployed it and it works. So the below pasted code is valid for sure.
I was looking at a Header component in Header.js:
class Header extends React.Component {
state = {
open: false,
};
render() {
const {
classes,
toggleDrawerOpen,
margin,
turnDarker,
} = this.props;
return (
.... some code ....
)
I see that classes is passed as a prop from the parent. So I looked into the parent component, Dashboard. Here is the code:
import { Header, Sidebar, BreadCrumb } from './../../components';
import { toggleAction, openAction, playTransitionAction } from './../../actions/UiActions';
import styles from './appStyles-jss';
class Dashboard extends React.Component {
state = {
transform: 0,
};
componentDidMount = () => {
// Scroll content to top
const mainContent = document.getElementById('mainContent');
mainContent.addEventListener('scroll', this.handleScroll);
// Set expanded sidebar menu
const currentPath = this.props.history.location.pathname;
this.props.initialOpen(currentPath);
// Play page transition
this.props.loadTransition(true);
// Execute all arguments when page changes
this.unlisten = this.props.history.listen(() => {
mainContent.scrollTo(0, 0);
setTimeout(() => {
this.props.loadTransition(true);
}, 500);
});
}
componentWillUnmount() {
const mainContent = document.getElementById('mainContent');
mainContent.removeEventListener('scroll', this.handleScroll);
}
handleScroll = (event) => {
const scoll = event.target.scrollTop;
this.setState({
transform: scoll
});
}
render() {
const {
classes, // classes is here
route,
toggleDrawer,
sidebarOpen,
loadTransition,
pageLoaded
} = this.props;
const darker = true;
return (
<div className={classes.appFrameInner}>
// NOTE: Header component is here but I don't see how classes is passed to it.
<Header toggleDrawerOpen={toggleDrawer} turnDarker={this.state.transform > 30 && darker} margin={sidebarOpen} />
<Sidebar
open={sidebarOpen}
toggleDrawerOpen={toggleDrawer}
loadTransition={loadTransition}
turnDarker={this.state.transform > 30 && darker}
/>
<main className={classNames(classes.content, !sidebarOpen && classes.contentPadding)} id="mainContent">
<div className={classes.bgbar} />
</main>
</div>
);
}
}
You can see that the classes prop is passed from Dashboard's parent. However, I was expecting some syntax that shows it is passed into the child Header component.
See the "NOTE" line in the code, nothing was said about passing the entire props to Header component or passing the const classes specifically to Header.
How is classes passed from parent (Dashbaord) to child (Header)?
The classes prop is not passed from parent Dashboard to child Header.
The classes prop is made available directly to your Header component using the wrapping withStyles HOC when exporting your component:
export default withStyles(styles)(Header);
This approach is commonly known as CSS-in-JS and you can read more details in the material-ui styles documentation.

React Native - change component style by key

I know that in html and javascript are able to change it own css style by id and class , in react native, how to set / change the component style. I have map a list of component, and each of them have set a key value. When I call a function, I would like to change one of the component style.
eg: change the key is 2 component style
_RenderSpecialItem(){
return this.state.speciallist.map((item, i)=>{
return(
<SpecialItem
key={i}
/>
);
});
}
_ChangeStyle(){
//do something
}
You can use Direct Manipulation but it's not a good practice, for more please read
Direct manipulation will not be a tool that you reach for frequently; you will typically only be using it for creating continuous animations to avoid the overhead of rendering the component ...
in the link. Otherwise, you should you set state in component and change state to update the style
e.g.
first set ref to the component :
<SpecialItem
key={i}
ref={(thisItem) => this[`item-${i}`] = thisItem}
/>
then setNativeProps :
_ChangeStyle() {
this['item-2'].setNativeProps({style: {/* your style here */}});
}
full example
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
speciallist: ['aaa', 'bbb', 'ccc']
}
}
componentDidMount() {
this['text-0'].setNativeProps({style: {fontSize: "10"}});
this['text-1'].setNativeProps({style: {fontSize: "20"}});
this['text-2'].setNativeProps({style: {fontSize: "30"}});
}
render() {
return (
<View style={styles.container}>
{
this.state.speciallist.map((item, i)=>(
<Text
key={`text-${i}`}
ref={(thisItem) => this[`text-${i}`] = thisItem}
>
{item}
</Text>
))
}
</View>
);
}
}

How to extract JSX from class render method?

I want to write something like SCSS for React Native: it'll parse your component jsx and the special SCSS-like styles and return a usual RN component with reworked styles and jsx.
Lets say we have this react code:
class MyClass extends Component {
render() {
return (
<View style={styles.container}>
<Text>I remember syrup sandwiches</Text>
</View>
);
}
}
Also I have SCSS-ish styles where every Text component inside the parent with a container "class" will have the same props that we provided.
const styles = StyleSheet.create(
toRNStyles({
container: {
Text: { color: 'red' },
},
})
);
In the end we need the output of something like this:
...
<View style={styles.container}>
<Text style={styles._Text_container}>
I remember syrup sandwiches
</Text>
</View>
...
So how can I get the jsx that's returning from the render method from outside the class?
You might write a plugin for babel, as react-native uses it to transform JSX to plain javascript.
Have a look to the these packages:
babel-helper-builder-react-jsx
babel-plugin-syntax-jsx
babel-plugin-transform-react-jsx
babel-plugin-transform-react-jsx-source
jsx-ast-utils
There doesn't seem to be a standard way of doing this. However, you could import ReactDOMServer and use its renderToStaticMarkup function.
Like this:
class MyApp extends React.Component {
render() {
var myTestComponent = <Test>bar</Test>;
console.dir(ReactDOMServer.renderToStaticMarkup(myTestComponent));
return myTestComponent;
}
}
const Test = (props) => {
return (
<div>
<p>foo</p>
<span>{props.children}</span>
</div>
);
}
ReactDOM.render(<MyApp />, document.getElementById("myApp"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom-server.js"></script>
<div id="myApp"></div>
I think parsing the returned element is the wrong approach. One challenge will be that the value of style will be an object (styles.container === a hash of style key/values) whereas you need a key which can be mapped to the object.
I think the most reusable approach is to leverage React context (which I'm assuming RN supports!) to build a styleName which can be augmented as you got down the component tree.
Here's an initial approach which makes a few assumptions (e.g. that every component will have styleName provided as a prop; you might want to provide that at design-time rather than run-time). In short, you wrap every component you want to participate with this HOC and the provide styleName as a prop to each component. Those styleName values are concatenated to produce contextualized names which are mapped to styles.
This example produces:
<div style="background-color: green; color: red;">
<div style="color: blue;">Some Text</div>
</div>
const CascadingStyle = (styles, Wrapped) => class extends React.Component {
static displayName = 'CascadingStyle';
static contextTypes = {
styleName: React.PropTypes.string
}
static childContextTypes = {
styleName: React.PropTypes.string
}
// pass the current styleName down the component tree
// to other instances of CascadingStyle
getChildContext () {
return {
styleName: this.getStyleName()
};
}
// generate the current style name by either using the
// value from context, joining the context value with
// the current value, or using the current value (in
// that order).
getStyleName () {
const {styleName: contextStyleName} = this.context;
const {styleName: propsStyleName} = this.props;
let styleName = contextStyleName;
if (propsStyleName && contextStyleName) {
styleName = `${contextStyleName}_${propsStyleName}`;
} else if (propsStyleName) {
styleName = propsStyleName;
}
return styleName;
}
// if the component has styleName, find that style object and merge it with other run-time styles
getStyle () {
if (this.props.styleName) {
return Object.assign({}, styles[this.getStyleName()], this.props.styles);
}
return this.props.styles;
}
render () {
return (
<Wrapped {...this.props} style={this.getStyle()} />
);
}
};
const myStyles = {
container: {backgroundColor: 'green', color: 'red'},
container_text: {color: 'blue'}
};
const Container = CascadingStyle(myStyles, (props) => {
return (
<div {...props} />
);
});
const Text = CascadingStyle(myStyles, (props) => {
return (
<div {...props} />
);
});
const Component = () => {
return (
<Container styleName="container">
<Text styleName="text">Some Text</Text>
</Container>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Adjacent JSX elements wrapping with Fela and React issues with parent component

Adjacent JSX elements must be enclosed by a parent tag, which is causing me some issues with implementing Fela. I am new to all these technologies, and trying to ensure that I am applying them with best practices in mind. Say I have a Page component and a fela component -- pageWrapperCss :
const PageWrapperCss = createComponent(
(props) => (
{
paddingTop: props.navbarHeight + 'px',
display: 'flex',
flexDirection: 'column',
alignContent: 'stretch',
flexGrow: 1,
flexShrink: 0,
}
), 'div'
);
const Page = ({view}, {cssVars}) => {
// some logic here which may set TargetView = Dashboard component
return (
<PageWrapperCss {...cssVars} >
<TargetView />
</PageWrapperCss>
);
};
export default Page;
The TargetView component is composed of sections, which also have their css composed with Fela. Below are the sections wrapped by a parent node -- div. Therein lies the problem. Now I have a div node between my parent component -- PageWrapperCss -- which generates a div node with the attached classNames.
class Dashboard extends Component {
render() {
let { cssVars } = this.context;
return (
<div>
<SectionWrapperCss {...cssVars}>
<HeaderSection>
</SectionWrapperCss>
<SectionWrapperCss {...cssVars}>
<BodySection />
</SectionWrapperCss>
<SectionWrapperCss sectionFlex="1" {...cssVars}>
<AnotherSection />
</SectionWrapperCss>
</div>
);
}
}
Before I had PageWrapperCss component in the Dashboard component like the following, which worked fine; however I want to keep the Fela css logic within the component it relates to, so that I don't have to keep generating PageWrapperCss components in each target view.
class Dashboard extends Component {
render() {
let { cssVars } = this.context;
return (
<PageWrapperCss>
// sections
</PageWrapperCss>
);
}
}
How have others dealt with this? Fela is pretty flexible, I could just render the css in the Page component, and persist the className via props to all the views, and add them to the parent node.
class Dashboard extends Component {
render() {
let { cssVars } = this.context;
return (
<div className={this.props.pageCss} >
// sections
</div>
);
}
}
thanks!

Categories

Resources