Imagine a Container component that renders a div with the specified height, e.g.:
<Container height="80">
Hello World
</Container>
and MyHeader component that renders a Container with a certain height, e.g.:
function MyHeader() {
return (
<Container height="100">
Header content goes here
</Container>
);
}
Now, I'd like to implement a Fixed component that looks like this:
<Fixed>
<Fixed.Item>
<MyHeader />
</Fixed.Item>
<Fixed.Content>
Some content goes here
</Fixed.Content>
</Fixed>
When rendering Fixed.Content I'd like to automatically set its offset to 100px (since MyHeader is 100px high).
Is there a way for the Fixed component to get MyHeader's height so it could pass it to Fixed.Content?
Is there a better way to automate this?
Note: Using useEffect (or componentDidMount) is not an option because I'd like it to work in server rendered environments.
Generally you want data flowing down from parents to children. If that is not an option for you, you can use Contexts: https://reactjs.org/docs/context.html. Especially check out https://reactjs.org/docs/context.html#updating-context-from-a-nested-component. In your case, MyHeader could be a Consumer of your context and update it with its height, and Fixed.Content would also be a consumer that uses the value for its offset. But in general, I'd say what you're trying to do is a bit unnatural.
You can use refs for that.
To solve your specific problem, first turn your <Container> component into a class component to be able to set a ref to it.
Then use React.forwardRef to forward the ref from the MyHeader component to the <Container> component:
const MyHeader = React.forwardRef((props, ref) => {
return (
<Container ref={ref} height={100}>
Header content goes here
</Container>
);
});
Finally, create a ref hook in your component that renders the <Fixed> component and pass the ref to the <MyHeader> component. You can then also use the ref to set the height of the <Fixed.Content> component (or whatever you want to set), as follows:
function App () {
const headerRef = React.useRef(null)
return (
<Fixed>
<Fixed.Item>
<MyHeader ref={headerRef} />
</Fixed.Item>
<Fixed.Content height={headerRef.current && headerRef.current.props.height}>
Some content goes here
</Fixed.Content>
</Fixed>
)
}
This seems to only render the <App> component once, so it should also work for server-side rendering. See the following code snippet as an example: https://codesandbox.io/s/get-height-from-header-gvvlo
You can use React Test Renderer which renders React Node to an inspectable Object tree.
import TestRenderer from 'react-test-renderer';
const height = TestRenderer.create(<MyHeader />).toTree().rendered.props.height
This way, you can get its height without second render.
function Container(props) {
return (
<div style={{height:props.height}}>
your content
</div>
);
}
you need to pass height to Container as props which can varied dynamically.
function MyHeader() {
return (
<Container height="100px">
Header content goes here
</Container>
);
}
for fixed also you need to pass it as props by having the height in a variable,e.g.:
function TopBar() {
let height = '100px'
return (
<>
<MyHeader height={height} />
<FixedComponent height={height} />
</>
);
}
I think you need to pass height prop from MyHeader to Fixed. And then Fixed to Fixed.Content. For example you can do similar to the following:
MyHeader.jsx
class MyHeader extends React.Component {
constructor(props) {
super(props)
this.state = {
height: 100
}
this.props.setHeaderOffset(this.state.height)
}
render() {
return (
<Container height={this.state.height}>
</Container>
)
}
}
Fixed.jsx
class Fixed extends React.Component {
constructor(props) {
super(props)
this.state = {
headerOffset: 0
}
this.setHeaderOffset = this.setHeaderOffset.bind(this)
}
setHeaderOffset(headerOffset) {
this.setState({
headerOffset
})
}
render() {
return (
<>
<Item>
<MyHeader setHeaderOffset={this.setHeaderOffset} />
</Item>
<Content headerOffset={this.state.headerOffset}>
Some content goes here
</Content>
</>
)
}
}
Related
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'm new to React and I'm building something like Card component from bootstrap. So what I want to achieve is create some component with Children components like , etc ...
Is there any way to tell React where to render component of specific type (according name of component itself for example) to be able to do something like:
<Card>
<CardHeader />
<CardBody />
...
</Card>
Thank you
You need to maintain state to render component on condition,
state={
cardHeader: true,
cardBody: true
}
Now you can render component on condition,
<Card>
{this.state.cardHeader && <CardHeader />}
{this.state.cardBody && <CardBody />}
...
</Card>
If you have button and onclick of that button you want card header to get disabled, you can do so,
<button onClick={this.hideCardHeader}>Hide Header</button>
hideCardHeader = () => {
this.setState({cardHeader:false})
}
Doing this you can tell react you don't want to display card header.
Note: You can render any component at any place. Where you place component at that place the component will get render.
Thank you for your answer :)
I was trying different approach and I came up with something like this. It's probably not final and It requires some "fine-tuning" but It seems to be what I was looking for.
class Card extends Component {
getParsedChildren = () => {
const children = this.props.children;
let header, body;
children.forEach((child) => {
switch(child.type) {
case CardHeader:
header = child
break;
case CardBody:
body = child
break;
default:
break;
}
})
return {header, body};
}
render() {
const {header, body} = this.getParsedChildren();
return (
<div className="card">
{header}
{body}
</div>
);
}
}
export default Card;
Is there anything wrong with the "design" ?
Thank you
I have 3 components which is my site. Each component js-file is loaded and all 3 shows on one page like this:
Topmenu
SectionOne
SectionTwo
In the Topmenu component I have a menu only. I’ve tried to setup the scrollToComponent onClick at a menu field (SectionOne). But I cannot figure out how to get it to scroll to SectionOne when clicked?
I know this.sectionOne is a ref to an internal ref, right? But how to direct it to a ref inside the “sectionOne.js” file?
I have the following inside my TopMenu.js file
onClick={() => scrollToComponent(this.sectionOne , { offset: 0, align: 'top', duration: 1500})}
To forward the ref to somewhere inside the component, you can use the logic of forwardRef.
This means that we create the ref in the parent component and pass the same to the component which passes it down to DOM element inside it.
class App extends React.Component {
constructor(props) {
super(props);
this.section1 = React.createRef();
this.section2 = React.createRef();
this.scrollToContent = this.scrollToContent.bind(this);
}
scrollToContent(content) {
switch(content) {
case 1:
this.section1.current.scrollIntoView({behavior: 'smooth'});
break;
case 2:
this.section2.current.scrollIntoView({behavior: 'smooth'});
}
}
render() {
return (
<main>
<Menu goTo={this.scrollToContent} />
<div className='main'>
<Section1 ref={this.section1} />
<Section2 ref={this.section2} />
</div>
</main>
);
}
}
and then, inside Section1,
const Section1 = React.forwardRef((props, ref)=>{
return (
<section ref={ref} className='section'>
<p>Section 1</p>
</section>
);
});
You can see a working sample here
I'm not using scroll-to-component package, but the same thing applies.
Forward Refs are supported only in React 16.3 now, you can use this alternative approach if you need the feature in lower versions.
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)
})
wonder if it is possible to pass a component a property as following
ReactDOM.render(
<ContainerBox anotherComponent={<AnotherComponent />} />, document.body);
And then insider the ContainerBox I want to pass AnotherComponent a property in following way.
class ContainerBox extends React.Component {
clickHandler() {
//does something fun
}
render () {
return (
this.props.anotherComponent(this.clickHandler) //<----- is it possible to pass properties from here?
);
}
}
Is it possible to pass things from ContainerBox to AnotherComponent from that position?
ContainerBox has a clickHandler function which I want to pass to AnotherComponent. It is possible to do so if I move <AnotherComponent /> to inside of render() instead. But then I cannot reuse ContainerBox for other components without first copying the whole ContainerBox.
Does it make sense? Hope you can understand.
UPDATED code example
Yes, that is possible. However, it's more common to do it like this
ReactDOM.render(
<ContainerBox><AnotherComponent /></ContainerBox>, document.body);
And in ContainerBox
class ContainerBox extends React.Component {
render () {
return (
this.props.children
);
}
}
Read more about reacts this.props.children here: https://facebook.github.io/react/docs/multiple-components.html#children
Edit:
I just want to point out that in this example, we are not passing a component, but an element (the result of rendering the component).
It's also possible to pass components, like this:
<Foo buttonComponent={FancyButtonComponent} />
and in Foo:
render() {
Button = this.props.buttonComponent;
return (
<div>
...
<Button />
</div>
);
}