I'd like to pass additional classNames into a child component, and also pass down any other props.
For example:
class Parent extends Component {
render() {
<Child
className="parent-class"
flavor="chocolate"
/>
}
}
class Child extends Component {
render() {
<div className="child-class" {...props}>
</div>
}
}
In this case, I would like the Child component div to have the "flavor" prop, and also have both classes "parent-class" and "child-class". However as it is, the className="child-class" will be overwritten by {...props}.
The only workaround I can think of is putting the {...props} before the className in the Child component:
<div {...props} className={`child-class ${props.className}`}>
Is this the only workaround? Or is there a cleaner solution?
I typically use the classnames package and the rest operator for things like this.
import classNames from 'classnames';
class Parent extends Component {
render() {
<Child
className="parent-class"
flavor="chocolate"
/>
}
}
class Child extends Component {
render() {
const { className, ...rest } = this.props;
const childClassNames = classNames('child-class', className);
return (
<div className={childClassNames} {...rest}>
</div>
);
}
}
You can call rest whatever you like, e.g. ...props will create an object variable called props which would contain everything from this.props except for className.
Also, the classnames package is very widely used, and lets you do other cool things like conditionally include class names.
If you are using functional components, the approach is almost identical to Bryan Downings answer. For simplicities sake I am only posting the implemented child component.
import { Tabs as AntdTabs } from 'antd'
/**
* simplified exmaple, based on our custom Antd Tabs*
* AntD Tabs with custom styling pre-applied.
* #param props The default TabsProps.
* #returns Antd Tabs.
*/
const Tabs = (props: TabsProps) => {
const {
className,
...rest
} = props
const cls = classnames(className, 'custom-tabs')
return (
<>
<AntdTabs
className={cls}
{...rest} >
</AntdTabs>
</>
)
}
export { Tabs }
Related
I have my code setup working like this....
_app.js
<ThemeProvider theme={clientGroupTheme}>
<GlobalStyles />
<Layout>
<Component {...this.props} />
</Layout>
</ThemeProvider>
someComponent.js
import { createGlobalStyle } from 'styled-components'
export const LoginMenuStyles = createGlobalStyle`
.exampleClass {
color:${(props) => props.theme.secondaryColor};
}`
class LoginMenu extends React.Component {
render() {
return ( <>
<div className="exampleClass">Test</div>
<PassColorComponent myColor={${(props) => props.theme.secondaryColor}} /> //This
</>
)
}
}
Basically I want to pass that same color value as a prop (//This). It works fine in the styled-component class but I can get it to work in the render method and pass to that component.
Is it possible? am I close?
Thanks
I think 'withTheme' higher-order component is what you are looking for.
import { withTheme } from 'styled-components'
class LoginMenu extends React.Component {
render() {
return ( <>
<div className="exampleClass">Test</div>
<PassColorComponent myColor={this.props.theme.secondaryColor} />
</>
)
}
}
export default withTheme(LoginMenu);
don't forget to wrap the component at export inside the withTheme component (see bottom line). That's how the styled theme props will be passed to LoginMenu component.
The theme is available only within styled function when creating a styled component. If you want to access in React component, you could e.g. directly pass the theme down as a prop.
<Component {...this.props} theme={clientGroupTheme} />
Then, you will be able to access the theme object.
<PassColorComponent myColor={props.theme.secondaryColor} />
Using ReactJS, I am trying to create a common workspace component that will have toolbar buttons and a navigation menu. The idea I have is to re-use this component to wrap all other dynamic components that I render in the app.
Currently, I've created a Toolbar and MenuBar components that I then add to each component in the app as such:
<Toolbar/>
<MenuBar/>
<Vendors/>
This does not feel right, since my aim is to have just one component which would be something like:
<Workspace>
<Vendor/>
</Workspace>
However, I am not sure of how to achieve this and whether this is the right approach.
As to whether or not it is the right approach is subjective, but I can provide insight into one way to make a "wrapper" type component:
// Your workspace wrapper component
class Workspace {
render() {
return (
<div className="workspace">
<div className="workspace__toolbar">
Toolbar goes here
</div>
<div className="workspace__nav">
Navgoes here
</div>
<div className="workspace__content">
{this.props.children}
</div>
</div>
)
}
}
// Using the component to define another one
class MyComponent {
render() {
return (
<Workspace>
This is my workspace content.
</Workspace>
)
}
}
You can also look at HOC's or Higher Order Components to wrap things.
React offer two traditional ways to make your component re useable
1- High-order Components
you can separate the logic in withWorkspace and then give it a component to apply that logic into it.
function withWorkSpace(WrappedComponent, selectData) {
// ...and returns another component...
return class extends React.Component {
render() {
// ... and renders the wrapped component with the fresh data!
// Notice that we pass through any additional props
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
const Component = () => {
const Content = withWorkSpace(<SomeOtherComponent />)
return <Content />
}
2- Render Props
or you can use function props then give the parent state as arguments, just in case you need the parent state in child component.
const Workspace = () => {
state = {}
render() {
return (
<div className="workspace">
<div className="workspace__toolbar">
{this.props.renderTollbar(this.state)}
</div>
<div className="workspace__nav">
{this.props.renderNavigation(this.state)}
</div>
<div className="workspace__content">
{this.props.children(this.state)}
</div>
</div>
)
}
}
const Toolbar = (props) => {
return <div>Toolbar</div>
}
const Navigation = (props) => {
return <div>Toolbar</div>
}
class Component = () => {
return (
<Workspace
renderNavigation={(WorkspaceState) => <Navigation WorkspaceState={WorkspaceState} />}
renderTollbar={(WorkspaceState) => <Toolbar {...WorkspaceState} />}
>
{(WorkspaceState) => <SomeComponentForContent />}
</Workspace>
)
}
I have a parent ButtonGroup component and the child buttonItem component:
//ButtonGroup Component (Parent)
clicky(){
//capture the props of the clicked button, ie, caption and disabled here.
}
render() {
return (
<div onClick={this.clicky}>
{this.props.children}
</div>
)
}
//buttonItem component:
render() {
return (
<button disabled={this.props.disabled}>{this.props.caption}</button>
)
}
//final render
<ButtonGroupComponent>
<buttonItem caption="Nothing"/>
<buttonItem caption="Something" disabled={true}/>
<buttonItem caption="Refresh"/>
</ButtonGroupComponent>
from the above code is there any way i can capture the props of the clicked child buttonItem?
In your case, you need to merge this.props.children with your custom prop. So, I suggest you use React.Children to operate with it.
By the way, after adding new prop you need to return this child, so cloneElement will help you with this.
Inside import section of ButtonGroupComponent:
import React, { Children, Component, cloneElement } from 'react';
Its render function will look like this:
render() {
const childrenWithCustomHandler = Children.map(this.props.children, itemChild => {
// Arguments of cloneElement (component, [customProps], [customChildren])
return cloneElement(itemChild, { onClickItem: this.clicky })
}
);
return <div>{childrenWithCustomHandler}</div>;
}
And the code of buttonItem component will look like:
return (
<button
disabled={this.props.disabled}
onClick={() => {
this.props.onClickItem({ ...this.props });
}}
>
{this.props.caption}
</button>
)
I used Spread operator to clone the object, so if you will want to change props in your clicky function, the child won't be rendered.
I may be over thinking this, but I am curious if importing a child component directly is bad practice with regards to coupling and testing.
Below is a simple example:
import Header from './header.jsx';
class Widget extends React.Component {
render() {
return (
<div>
<Header></Header>
<div>{this.props.importantContent}</div>
</div>
)
}
}
To me it looks like there is now coupling between Widget and Header. With regards to testing, I don't see an easy way to mock the Header component when testing the Widget component.
How do other larger React apps handle cases like this? Should I pass Header in as a prop? If using react-redux, I can inject header with the Connect method like below to reduce boilerplate. Is that sound?
import { connect } from 'react-redux';
import Header from './header.jsx';
class Widget extends React.Component {
render() {
return (
<div>
{this.props.header}
<div>{this.props.importantContent}</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
header: Header
}
}
export default connect(mapStateToProps)(Widget)
I am interested is simple doing what the community is generally doing. I see that one solution is doing shallow rendering to test on the main part of the component and not the child components using something like Enzyme.
Thoughts or other ideas?
Passing elements / components as props is a good idea. Having default props is a good idea too:
const Widget = ({
header = <div>Default Header.. </div>,
content = <div>Default Content.. </div>
}) =>
<div>
{header}
{content}
</div>
Then elsewhere in your app:
<Widget header={<Header title="Foo" />} content="content from props" />
No need to inject using connect
You can also pass a component, not just an element if you want to interact with props / send data back to parent:
const Widget = ({
Header = props => <div>Default Header.. </div>,
Content = props => <div>Default Content.. </div>
}) =>
<div>
<Header />
<Content />
</div>
Elsewhere:
<Widget Header={Header} Content={props => <Content />} />
As long as the component always renders the same thing it can be directly rendered as a child rather than the parent.
If all other portions of the Component remain constant and only the Header can be different across pages then you could actually implement it as an HOC instead of passing it as a props
const MyCompFactory = ({CustomHeader = DefaultHeader}) => {
return class Widget extends React.Component {
render() {
return (
<div>
<CustomHeader/>
<div>{this.props.importantContent}</div>
</div>
)
}
}
}
and use it like
const CustomComponent = MyCompFactory({CustomComponent: Header})
as long as testing is concerned in your case, you could just shallow render your component and then Search if the Header component is rendered something like
import Header from 'path/to/header'
const component = shallow(
<Widget {...customProps}/>
)
test('test' , () => {
expect(component.find(Header).exists()).toBe(true)
})
I have a higher order component, that provides the complete website layout, including a sidebar. This sidebar contains some static elements (e.g. a logo, the home button, imprint and disclaimer) and some dynamic content, provided by the composed component.
const HtmlSkeleton = (ComposedComponent) => {
class Wrapper extends Component {
[...]
render() {
return (
<div>
<Sidebar content={ComposedComponent.getSidebarContent()} />
<Header />
<Content>
<ComposedComponent {...this.props} />
</Content>
</Footer />
</div>
)
}
}
return connect(...)(Wrapper)
}
Then I have some views/containers, that will be composed by that HOC:
class View extends Component {
static getSidebarContent = () => {
// how to access someFunction?!?
return [...some stuff depending on props. How do I have access?]
}
constructor(props) {
[....]
}
[...]
someFunction = () => { [....] }
render() {
this.someFunction()
[...]
}
}
export default connect(....)(HtmlSkeleton(View))
(connect is from react-redux, but not that important in that case)
Or is there any other possibility to keep the layout in some kind of component, that I don´t see? Where do you keep the basic HTML?
I´m using react-router, react-redux and redux-saga.
You can pass the props directly to your getSidebarContent:
<Sidebar content={ComposedComponent.getSidebarContent(this.props)} />
And retrieve them in your function:
class View extends Component {
static getSidebarContent = (props) => {
return ...
}
[...]
}