It would be really helpful if we could compose multiple Cx components into one functional component that accepts multiple parameters as JSX attributes and can have its own nested (child) components.
<LoadingOverlay loading={bind('$page.loading')} >
<Grid />
</LoadingOverlay/>
When creating a custom Cx component, normally we need to create a class that extends some of the base components and implements certain predefined methods, which adds complexity to the process.
Here is one possible implementation of LoadingOverlay:
class LoadingOverlay extends PureContainer {
declareData() {
super.declareData(...arguments, {
loading: undefined
});
}
render (context, instance, key) {
let {data} = instance;
return <div key={key} className="cxb-loading-overlay-container">
{this.renderChildren(context, instance)}
{data.loading && <div className="cxe-loading-overlay">
<div className="cxe-loading-indicator">
{Icon.render('loading', {
style: {
width: '24px',
height: '24px',
position: 'relative',
top: '6px'
}
})
}
Loading...
</div>
</div>}
</div>;
}
}
For the example above, LoadingOverlay had to inherit from PureContainer and implement declareData and render methods.
And I would like to be able to use something simmilar to React's stateless functional components, like this:
const LoadingOverlay = (props) => {
return <div className="cxb-loading-overlay-container">
{props.children}
{data.loading && <div className="cxe-loading-overlay">
<div className="cxe-loading-indicator">
{Icon.render('loading', {
style: {
width: '24px',
height: '24px',
position: 'relative',
top: '6px'
}
})
}
Loading...
</div>
</div>}
</div>;
}
Is this possible in CxJS?
CxJS allows mixing React components with CxJS components, so your second example should work, except that you should use props.loading instead of data.loading.
React components can be defined as functional stateless components, or as ES6 classes extending the base React.Component class (or VDOM.Component in CxJS).
CxJS recently got support for CxJS functional components, and that would probably be the best choice for this example:
const LoadingOverlay = ({ loading, children }) => (
<cx>
<div className="cxb-loading-overlay-container">
{children}
<div className="cxe-loading-overlay" visible={loading}>
<div className="cxe-loading-indicator" ws>
<Icon
name="loading"
style="width:24px;height:24px;position:relative;top:6px"
/>
Loading...
</div>
</div>
</div>;
</cx>
);
Related
I tried to create a dropdown menu with createPortal() because of visibility problems due to the stacking context, but the child components are not working as they should. The OperationButton has an onConfirm event.
createPortal(
<div
className={`widgetActionsMenu`}
style={{ position: 'alternative', top: '27px' }}>
{allowedOperations.map((pseudoOperation) => {
const [operation, disabled] = pseudoOperation.split(':');
return (
<OperationButton
disabled={!!disabled}
key={operation}
operation={operation}
itemId={props.itemId}
/>
);
})}
</div>
,
document.querySelector(`.flowChartActionLevel`)
)
Actuall assigning it to a ref solved the problem, it now finds the child elements and their properties.
const portalRef = React.useRef(null);
I'm very new to react. And I'm trying to learn some new stuff. So what I want to do is to add CSS within my Header.js file, And I don't know how to do that. Because I don't want to import external or inline CSS. But rather use it like in Html with tag on the header. But not just that, I want to use that CSS specifically for the file, in this case, Header.js.
This might help
Header.js
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
// alignSelf: 'center',
},
textStyle: {
marginTop: Metrics.ratio(0),
marginHorizontal: Metrics.ratio(70),
fontFamily: Fonts.type.base,
fontSize: Fonts.size.normal,
color: Colors.black,
},
});
class Header extends React.Component {
render() {
return (
<div style={ styles.color } />
);
}
}
You have several possibilites.
The simplest is to use css code directly in the element like
<div style={{color:'#000',backgroundColor:'#fff'}}>
...
</div>
Or you can use the libraries for that, like styled-components (https://styled-components.com/) for this.
You need to import this:
import styled from 'styled-components';
Then you can define your element css on the top of the page, i.e. SomeCSSStyling
const SomeCSSStyling = styled.div`
color:#000;
background-color:#fff;
`
Then you can use this constant in the class code of the react component:
class MyReactComponent extends Component {
constructor(props) {
...
}
render() {
return (
<SomeCSSStyling>
...
</SomeCSSStyling>
);
}
}
export default MyReactComponent;
UPDATE:
You can also define :hover or ::before etc. with style-components:
const SomeCSSStyling = styled.div`
color:#000;
background-color:#fff;
&:hover{
font-weight:bold;
}
`
You can create style object inside react component like this:
const myDivStyles = {
color: "red",
fontSize: "32px"
}
All propertis are the same like in CSS but this one with "-" sign change in to camelCase nams, e.g. font-size change to fontSize, background-color change to backgroundColor.
Then you can add this style to elements in your components by style attribute.
render() {
return (
<div style={ myDivStles } />
)
}
You can also describe style without creating style object like this:
<div style={{ color: "red", backgroundColor: "#fff" }} />
Be sure you are using double closure {{ }}.
EDIT
With :hover selector
You have two prosibilites. First you can use component state to determinate if component is hovered and then prepare correct style, e.g:
class Component extends React.Component {
constructor(props) {
super(props)
this.state = {
hovered: false
}
this.toggleHover = this.toggleHover.bind(this)
}
toggleHover(state) {
this.setState({
hovered: state
})
}
render() {
const styles = {
color: this.state.hovered ? "red" : "blue"
}
return (
<div style={styles} onMouseEnter={ () => this.toggleHover( true ) } onMouseLeave={ () => this.toggleHover( false ) }>
Text
</div>
);
}
}
Second you can use js styled components syntax and refer to other component, you can read more about this here: https://styled-components.com/docs/advanced#referring-to-other-components
But to be honest when I dealing with :hover or other selectors I prefer using default css files or much more often scss files prepared for components. So when I have e.g Button component in same location I have Button.css ( or Button.scss ) file where I can work with standard css. After this I have css files connected with components which should handled them.
Using the docs/examples for overriding Material UI styling with styled-components, I've managed to style the root and "deeper elements" within an ExpansionPanel and ExpansionPanelDetails.
However, when I use the same technique to return an overridden ExpansionPanelSummary from a function passed to styled(), the ExpansionPanelSummary moves in the DOM and the whole ExpansionPanel no longer renders correctly.
The technique in question, as applied to ExpansionPanel (this works as expected, on the container ExpansionPanel):
import MUIExpansionPanel from '#material-ui/core/ExpansionPanel';
export const ExpansionPanel = styled(props => (
<MUIExpansionPanel
classes={{expanded: 'expanded'}}
{...props}
/>
))`
&& {
...root style overrides
}
&&.expanded {
...expanded style overrides
}
`;
The typical DOM (with class names abbreviated) for ExpansionPanel and friends:
<div class="MuiExpansionPanel...">
<div class="MuiExpansionPanelSummary..." />
<div class="MuiCollapse-container...>
<div class="MuiCollapse-wrapper...>
<div class="MuiCollapse-wrapperInner...>
<div class="MuiExpansionPanelDetails..." />
</div>
</div>
</div>
</div>
The DOM when I apply the above technique to ExpansionPanelSummary:
<div class="MuiExpansionPanel...">
<div class="MuiCollapse-container...>
<div class="MuiCollapse-wrapper...>
<div class="MuiCollapse-wrapperInner...>
<div class="MuiExpansionPanelSummary..." />
<div class="MuiExpansionPanelDetails..." />
</div>
</div>
</div>
</div>
For completeness, here's a minimal repro of what I'm doing to ExpansionPanelSummary, that triggers the DOM switch:
export const ExpansionPanelSummary = styled(props => (
<MUIExpansionPanelSummary
{...props}
/>
))``;
And my JSX is standard ExpansionPanel setup:
<ExpansionPanel>
<ExpansionPanelSummary>
Summary Label
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<div>Some Content</div>
</ExpansionPanelDetails>
</ExpansionPanel>
This difficulty is independent of using styled-components and just has to do with wrapping ExpansionPanelSummary in another component.
You can similarly reproduce this with the following wrapping of ExpansionPanelSummary:
const MyCustomSummary = props => {
return (
<ExpansionPanelSummary {...props} expandIcon={<ExpandMoreIcon />}>
<Typography>{props.text}</Typography>
</ExpansionPanelSummary>
);
};
There are several component groups like this where a Material-UI parent component looks for a particular type of child component and treats that child specially. For instance, you can find the following block in ExpansionPanel
if (isMuiElement(child, ['ExpansionPanelSummary'])) {
summary = React.cloneElement(child, {
disabled,
expanded,
onChange: this.handleChange,
});
return null;
}
Fortunately, Material-UI has a straightforward way to tell it that your custom component should be treated the same as a particular Material-UI component via the muiName property:
MyCustomSummary.muiName = "ExpansionPanelSummary";
or in your case it would look like:
export const ExpansionPanelSummary = styled(props => (
<MUIExpansionPanelSummary
{...props}
/>
))``;
ExpansionPanelSummary.muiName = "ExpansionPanelSummary";
I have a Playground here: https://codesandbox.io/s/736v9vjzw1
const Something = ({ classes, children, variant }) => {
return (
<div className={classes.someThing}>
<p> I'm some thing </p>
<SomeOtherThing />
<SomeOtherThing> I have some children </SomeOtherThing>
<SomeOtherThing> I have some children </SomeOtherThing>
<SomeOtherThing> I have some children </SomeOtherThing>
</div>
);
};
const styles = {
someThing: {
color: "green",
border: "solid 2px black",
margin: 30,
"& $someOtherThing": {
backgroundColor: "pink" // Doesn't work
},
"& p": {
fontWeight: "bold" //This works but is too broad
}
}
};
I have a situation here, where I want to style all the SomeOtherThings inside my SomeThing.
I can use & p selector to select the p element - but I don't like this. It would style any random ps I have around - and I don't want to have to look inside the component definition to find what it's top level element is.
How can I do this? Something like & SomeOtherElement.
The real world application of this, is that in some places I want have SomeOtherElement be displayed block and other places inline-block.
I would extend the SomeOtherThing component to accept a className and add it to the div if present. This will also work on a production setup, where the class names is minified to e.g. .t-0-root.
Here is a forked playground: https://codesandbox.io/s/zlzx277zzm which shows how to use it.
const SomeOtherThing = ({ classes, children, className }) => {
return (
<p className={`${classes.someOtherThing} ${className && className}`}>
I'm another thing {children}
</p>
);
};
I would most likely use the package classnames to conditionally render the class name instead of string interpolation.
const Something = ({ classes, children, variant }) => {
return (
<div className={classes.someThing}>
<p> I'm some thing </p>
<SomeOtherThing />
<SomeOtherThing className={classes.someOtherThing}>
I have some children
</SomeOtherThing>
<SomeOtherThing className={classes.someOtherThing}>
I have some children
</SomeOtherThing>
<SomeOtherThing className={classes.someOtherThing}>
I have some children
</SomeOtherThing>
</div>
);
};
const styles = {
someThing: {
color: "green",
border: "solid 2px black",
margin: 30
},
someOtherThing: {
backgroundColor: "pink"
}
};
It's so simple, in your someThing CSS codes select the p elements with class someOtherThing class name and use not() CSS operation for p in top level, see following code:
const styles = {
someThing: {
color: "green",
border: "solid 2px black",
margin: 30,
"& [class*='SomeOtherThing']": {
backgroundColor: "pink" //
},
"& :not([class*='SomeOtherThing'])": {
fontWeight: "bold" // Just for top level p
}
}
};
and
const SomeOtherThing = ({ classes, children }) => {
return (
<p className={classes.root}> I'm another thing {children} </p>
);
};
CodeSandBox
The way that this works, is that by giving SomeOtherThing any jss class, it's going to render the dom element as something like:
<p class="SomeOtherThing-root-0-1-2"> I'm another thing I have some children </p>
which the [class*='SomeOtherThing'] attribute selector will match on.
You should note that this selector will apply to any deeper nested SomeOtherThings as well.
One problem with the "cascading" aspect of CSS is that it sort of breaks React's component model. But in React you can always create a wrapper or higher-order component that returns another with some predefined props, kind of like a factory function:
const Something = props => {
return (
<div className={props.className}>
// ...
</div>
)
}
const SomethingInline = props => {
return <Something className='display-inline' {...props} />
}
const SomethingBlock = props => {
return <Something className='display-block' {...props} />
}
const App = () => {
return (
<div>
<SomethingInline />
<SomethingBlock />
<SomethingBlock> I have children </SomethingBlock>
</div>
)
}
Instead of using the & selector to define your style, create a class that only applies to these specific versions of your component. In this way, the global scope of CSS is avoided and you can create these sort of declarative components that describe their own style, but don't require you to explicitly pass a class name.
I'm trying to create a div that sits underneath the main app (lexically) but is styled to only show up after a timed delay. I feel like this is most likely a very simple failure on my part to grasp some of the react concepts I'm working with.
Here's my code: (The CSS is pseudo code)
import React, {Component} from 'React'; //eslint-disable-line
import styled from 'styled-components';
import ReactTimeout from 'react-timeout';
const Icon = styled.div.attrs({
dataRight: props => props.dataRight || '1em',
dataLeft: props => props.dataLeft || '1em',
displayIcons: props => props.displayIcons|| 'none'
})`
display: ${props => props.displayIcons};
font-size: 1.5rem;
background-color: rebeccapurple;
position: absolute;
top: 1rem;
right: ${props => props.dataRight};
left: ${props => props.dataLeft};
`;
class Iconset extends Component {
constructor(props) {
super(props);
this.state = {
displayIcons: 'none'
};
}
componentDidMount () {
this.props.setTimeout(this.showIcons, 4000);
alert('Display Icons = ' + this.state.displayIcons);
}
showIcons() {
this.setState({displayIcons: 'Block'});
alert('Display Icons = ' + this.state.displayIcons);
}
render () {
return (
<div id='iconset'>
<Icon dataLeft="auto" dataRight="1em" display={this.props.displayIcons}>First Icon</Icon> {/*eslint-disable-line */}
<Icon dataLeft="1em" dataRight="auto" display={this.props.displayIcons}>Second Icon</Icon> {/*eslint-disable-line */}
</div>
);
}
}
export default ReactTimeout(Iconset);
So, my current understanding is that when the timeout fires the container state change should populate down to the children and override the display: none with display: block. That change never seems to happen although the state-change itself does happen.
What concept am I missing here?
When you use setState, you're setting the displayIcons variable in component's internal state which would be accessed by this.state.displayIcons.
If you look at your render, in the display prop, you're targeting this.props.displayIcons
You would only use props here if you were changing the displayIcons property in a parent component.
Change that to this.state.displayIcons and it should work as you expect.
I'm not sure what makes sense or not to your code.
this.props.setTimeout(this.showIcons, 4000);
Is setTimeout really a props function? It looks like that what you really want was to just call setTimeOut:
setTimeout(this.showIcons, 4000);
Why are you rendering props.displayIcons ?
<Icon dataLeft="auto" dataRight="1em" display={this.props.displayIcons}>First Icon</Icon> {/*eslint-disable-line */}
<Icon dataLeft="1em" dataRight="auto" display={this.props.displayIcons}>Second Icon</Icon> {/*eslint-disable-line */}
I believe what you really want is to render the state that you changed on timeOut:
<Icon dataLeft="auto" dataRight="1em" display={this.state.displayIcons}>First Icon</Icon> {/*eslint-disable-line */}
<Icon dataLeft="1em" dataRight="auto" display={this.state.displayIcons}>Second Icon</Icon> {/*eslint-disable-line */}
Hope that helps!