How to add a button using Higher Order Component in reactjs - javascript

How can I add a button to a component using higher order component? I tried this but its not adding the button inside the component. Its adding it before the original component.
const withButton = WrappedComponent => {
return class extends Component {
render() {
return (
<Fragment>
<button>BUTTON ADDED USING HOC</button>
<WrappedComponent {...this.props} />
</Fragment>
)
}
}
}
export default withButton
When I call the HOF like this
const ComponentWithButton = withButton(WrappedComponent)
ComponentWithButton has button added but its adding before WrappedComponent whereas I want to add button inside as a child of the WrappedComponent.
Lets say that WrappedComponent is rendering something like
<div className="baseClass">{other divs and layout}</div>
const ComponentWithButton = withButton(WrappedComponent)
ComponentWithButton should render the following
<div className="baseClass">
<button>BUTTON ADDED USING HOC</button>
{other divs and layout}
</div>

Try using props.children, also refer to React.Children API
function ComponentWithButton({ children }) {
return (
<>
<button>BUTTON ADDED USING HOC</button>
{children}
</>
);
}
And then render:
<ComponentWithButton>
<WrappedComponent />
</ComponentWithButton>
With classes:
class ComponentWithButton extends Component {
render() {
const { children } = this.props;
return (
<>
<button>BUTTON ADDED USING HOC</button>
{children}
</>
);
}
}
export default ComponentWithButton;

If you want to dynamically place the button somewhere inside the WrappedComponent, you can try something like this.
const withButton = WrappedComponent => {
return class extends Component {
render() {
return (
<Fragment>
<WrappedComponent {...this.props}>
<button>BUTTON ADDED USING HOC</button>
</WrappedCompnent>
</Fragment>
)
}
}
}
export default withButton
Now in your wrapped component, you can place the button any where you want as the button would be accessible as a property children to WrappedComponent.
const WrappedComponent = ({ children, ...otherProps }) => (
<div className="baseClass">
{children}
{renderOtherDivsAndProps(otherProps)}
</div>
);
Hope this helps you

I tried this and I am getting what I am looking for.
const withButton = WrappedComponent => {
return class extends Component {
render() {
return (
<WrappedComponent {...this.props}>
<button>BUTTON ADDED USING HOC</button>
{this.props.children}
</Wrappedcomponent>
)
}
}
}
export default withButton

Related

React | ForwardedRef using React.Component

I'm creating a custom component in React, and I need to export it using forwardedRef. But when I try, this error occurs:
error
my code:
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>{
ref?: React.RefObject<HTMLButtonElement>;
}
class Button extends React.Component<ButtonProps> {
render() {
const {
ref,
children,
...otherProps
} = this.props;
return (
<button
{...otherProps}
ref={ref}
>
{children}
</button>
)
}
}
const ButtonForwarded = React.forwardRef<ButtonProps>((props, ref) =>
<Button {...props} ref={ref} /> );
ButtonForwarded.displayName = 'Button';
export default ButtonForwarded;
Create the ButtonForwarded component like this:
const ButtonForwarded = React.forwardRef((props: ButtonProps, ref: LegacyRef<Button>) => <Button {...props} ref={ref} /> );

how do you pass down an object containing props

after getting all mixed up with state, i am now trying to restructure my app in a way that might be more reflective of best practices (not sure if this is the way, advice is welcome.)
so, i have my main page, which holds 3 states: viewer,buyside,sellside
there are also three different components, one for each of those states.
i want to be able to pass the props down from the main page, through those components, to their children (i've read this is the best approach??)
main page:
//we have 3 states for the website: viewer,buyside customer, sellside customer
const [visitorType, setVisitorType] = useState('viewer');
if (visitorType == 'viewer') {
return(
<div>
<Viewer visitortype='viewer' setvisitor={()=>setVisitorType()}/>
</div>
)}
else if (visitorType =='buyside') {
return(
<div>
<Buyside visitortype='buyside' setvisitor={()=>setVisitorType()}/>
</div>
)}
else if (visitorType =='sellside') {
return(
<div>
<Sellside visitortype='sellside' setvisitor={()=>setVisitorType()}/>
</div>
)}
};
what is the best way to pass down the main page props, so that i can bring them down to any grandchildren, along with the child props?
the viewer component -UPDATED-:
const MainView = (props) => {
return(
<>
<Navbar mainprops={{props}}/>
</>
)
};
export default MainView
i was previously just passing them individually, but realized it might be better to do so as one object...
UPDATE: point taken on the syntax, but i'm wondering how i can best pass the objects
nav component (grandchild)
const Navbar = (props) => {
const {mainprops} = props.mainprops;
if (mainprops.visitortype == 'viewer') {
return(
<>
<h1>viewer navbar</h1>
</>
)}
else if (mainprops.visitortype =='buyside') {
return(
<>
<h1>buyside navbar</h1>
</>
)}
else if (mainprops.visitortype =='sellside') {
return(
<>
<h1>sellside navbar</h1>
</>
)}
};
export default Navbar;
UPDATE 2 - this works, but not sure if it is the correct way, are these still considered object literals??
viewer component:
const MainView = (props) => {
const mainprops = {...props}
return(
<>
<Navbar mainprops={mainprops}/>
</>
)
};
export default MainView
navbar component
const Navbar = (props) => {
const mainprops = {...props.mainprops};
if (mainprops.visitortype == 'viewer') {
return(
<>
<h1>viewer navbar</h1>
</>
)}
else if (mainprops.visitortype =='buyside') {
return(
<>
<h1>buyside navbar</h1>
</>
)}
else if (mainprops.visitortype =='sellside') {
return(
<>
<h1>sellside navbar</h1>
</>
)}
};
export default Navbar;
if this is correct, then is this what #amir meant?
First there are certain rules for passing props:
You never ever pass literal object as props since it will not be the same every re-render and will cause the child component to re-render too (without any new info)
You don't need to do that
<Viewer visitortype='viewer' setvisitor={()=>setVisitorType()}/>
You can:
<Viewer visitortype='viewer' setvisitor={setVisitorType}/>
since it comes from useState react make sure the setVisitorType keeps the same reference
And now for you error, you almost correct you just did a js syntax error
you should write it like this:
const MainView = (props) => {
return(
<>
<Navbar mainobj={{
visitortype:props.visitortype,
setvisitor:props.setvisitor
}}
/>
</>
)
};
export default MainView
But again you never send literal object as props
I would keep it inside a ref or state (depend if the visitor state will be change)

How to pass props props from Parent to this.props.children in React

I have difficulties trying to pass props to this.props.children. I know there's a few similar posts, however, I believe I have tried most of the accepted solutions, and it's still not behaving and expected. So, I guess I'm missing something vital.
The general idea is this: I have a <NavBar> component that I would like to wrap around my pages as shown below. I'd like for the wrapped page to accept props passed down from the <NavBar> component.
<NavBar>
<Container>
<Grid container>
<Grid item>
...
</Grid>
</Grid>
</Container>
</NavBar>
Currently my <NavBar> is defined as such:
class NavBar extends React.Component<React.PropsWithChildren<NavBarProps>, NavBarState>
So, my component has a prop children?: React.ReactNode. In my render() method I am rendering an <AppBar> (from Material UI library) underneath which I display the children similar as such:
render() {
const {children} = this.props;
return(
<>
<AppBar>...</AppBar>
{children}
</>
)
}
Some attempts I've had:
render() {
const children = React.cloneElement(this.props.children as React.ReactElement, {
test: "test"
});
return(
<>
<AppBar>...</AppBar>
{children}
</>
)
}
What I expect: In this case, I would like to be able to access the test props from any page wrapped within <NavBar> like this.props.test
I also tried:
const children = React.Children.map(this.props.children as React.ReactElement, (child) =>
React.cloneElement(child, { test: "test" })
);
&
const children = React.Children.map<ReactNode, ReactNode>(this.props.children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { test: "test" });
}
});
Result so far: I've been unsuccessful and trying to access this.props.test from my page returns undefined.
I don't see anything wrong with your third attempt. Here is a working example using that method. Notice unlike your second attempt, you do need to return from the map.
function Test() {
return (
<Parent>
<Child />
</Parent>
);
}
class Parent extends React.Component {
render() {
const children = React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {test: 'test'});
});
return (
<div>
<h3>Parent</h3>
{children}
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
<h3>Child</h3>
Test Prop: {this.props.test}
</div>
);
}
}
ReactDOM.render(<Test/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

ReactDOM.render doesn't update component on props change

I have create a very small app to demonstrate my query.
Below shown code has the functionality where the component is dynamically added to DOM using ReactDOM.render and this component carries a prop called title, but when I update the title of the parent component ( in state ) the DynamicComponent doesn't update.
import React from 'react';
import ReactDOM from 'react-dom';
const DynamicComponent = (props) => {
return (
<div style={{ 'border': '2px dotted green' }} >Dynamic Component : {props.title}</div>
)
}
class App extends React.Component {
state = {
title: 'Iam Title'
}
addBlock = () => {
return ReactDOM.render(<DynamicComponent title={this.state.title} />, document.getElementById('dynamiccomponents'))
}
render() {
return (
<div>
<div>Value in state: <b>{this.state.title}</b></div>
<p><b><DynamicComponent /></b> Added Initially</p>
<DynamicComponent title={this.state.title} />
<br />
<p><b><DynamicComponent /></b> Added By ReactDOM.render will be shown below: </p>
<div id="dynamiccomponents"></div>
<button onClick={this.addBlock} >Click to Dynamic Component</button>
<button onClick={() => this.setState({ title: `Update Title` })} >Update Title</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
The first button is used to added the DynamicComponent, works fine as expected.
The Second button is used to update the title in state, now the title got changed but still DynamicComponent doesn't update.
am I missing anything, how do I solve this issue, any help would be appreciated
Thanks
You could re-render the component after state change using a LifeCycle method componentDidUpdate()
import React from "react";
import ReactDOM from "react-dom";
const DynamicComponent = props => {
return (
<div style={{ border: "2px dotted green" }}>
Dynamic Component : {props.title}
</div>
);
};
class App extends React.Component {
state = {
title: "Iam Title"
};
addBlock = () => {
return ReactDOM.render(
<DynamicComponent title={this.state.title} />,
document.getElementById("dynamiccomponents")
);
};
componentDidUpdate() {
return ReactDOM.render(
<DynamicComponent title={this.state.title} />,
document.getElementById("dynamiccomponents")
);
}
render() {
return (
<div>
<div>
Value in state: <b>{this.state.title}</b>
</div>
<p>
<b><DynamicComponent /></b> Added Initially
</p>
<DynamicComponent title={this.state.title} />
<br />
<p>
<b><DynamicComponent /></b> Added By ReactDOM.render will be
shown below:{" "}
</p>
<div id='dynamiccomponents'></div>
<button onClick={this.addBlock}>Click to Dynamic Component</button>
<button onClick={() => this.setState({ title: `Update Title` })}>
Update Title
</button>
</div>
);
}
}
export default App;
This is because when you call addBlock, you are only rendering <DynamicComponent title={this.state.title} /> once to the <div id="dynamiccomopnents"></div>.
When you update the state of title by clicking the button, it re-runs your App's render function, but this.addBlock does not get run again in your render function and therefore your title does not get updated. You can verify this by clicking the button that calls this.addBlock again. It will render your component again, with the updated title.
I'd suggest you introduce some state to conditionally render your component instead of using ReactDOM.render. That way, your component gets re-rendered everytime your render method is run. Here's an example:
import React from 'react';
import ReactDOM from 'react-dom';
const DynamicComponent = (props) => {
return (
<div style={{ 'border': '2px dotted green' }} >Dynamic Component : {props.title}</div>
)
}
class App extends React.Component {
state = {
title: 'Iam Title',
showBlock: false,
}
addBlock = () => {
// this method now sets `this.state.showBlock` to true
this.setState({ showBlock: true });
}
renderBlock = () => {
// return any component you want here, you can introduce some conditional
// logic or even return nested elements, for example:
return (
<div>
<p>Dynamic Component!</p>
<DynamicComponent title={this.state.title} />
</div>
);
}
render() {
return (
<div>
<div>Value in state: <b>{this.state.title}</b></div>
<p><b><DynamicComponent /></b> Added Initially</p>
<DynamicComponent title={this.state.title} />
<br />
<p><b><DynamicComponent /></b> Added By ReactDOM.render will be shown below: </p>
{/* This will run `this.renderBlock` only if `this.state.showBlock` is true */}
{this.state.showBlock && this.renderBlock()}
<button onClick={this.addBlock} >Click to Dynamic Component</button>
<button onClick={() => this.setState({ title: `Update Title` })} >Update Title</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM.render renders element only once. It creates a different tree that is not connected to your first tree. That is, React doesn't keep track of all ReactDOM.renders you might have ever called and doesn't update them with data that was used to create them
If you need to render element somewhere in the DOM tree outside of your App component but you want it to be connected with your App component (so that it reacts to state changes), use ReactDOM.createPortal

React DnD drags whole list of cards instead of single card

I am trying to use react DnD in my react Project. In my render method I define a variable named Populate like show below, which returns a list of cards like this
render() {
var isDragging = this.props.isDragging;
var connectDragSource = this.props.connectDragSource;
var Populate = this.props.mediaFiles.map((value) => {
return(
<div>
<MuiThemeProvider>
<Card style= {{marginBottom: 2, opacity: isDragging ? 0 : 1}} id={value.id} key={value.id}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
//onTouchTap={() => {this.handleClick(value.id)}}
zDepth={this.state.shadow}>
<CardHeader
title={value.Episode_Name}
//subtitle={value.description}
actAsExpander={false}
showExpandableButton={false}
/>
</Card>
</MuiThemeProvider>
</div>
)
});
And my return of render method looks like this
return connectDragSource (
<div>
<MuiThemeProvider>
<div className="mediaFilesComponent2">
{Populate}
</div>
</MuiThemeProvider>
</div>
)
Problem is when I try using drag, then the whole list of cards gets selected for drag. I want all the cards having individual drag functionality.
If you want each card to have drag functionality than you'll have to wrap each card in a DragSource, and not the entire list. I would split out the Card into it's own component, wrapped in a DragSource, like this:
import React, { Component, PropTypes } from 'react';
import { ItemTypes } from './Constants';
import { DragSource } from 'react-dnd';
const CardSource = {
beginDrag: function (props) {
return {};
}
};
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}
}
class CardDragContainer extends React.Component {
render() {
return this.props.connectDragSource(
<div>
<Card style= {{marginBottom: 2, opacity: this.props.isDragging ? 0 : 1}} id={value.id} key={value.id}
onMouseOver={this.props.onMouseOver}
onMouseOut={this.props.onMouseOut}
zDepth={this.props.shadow}>
<CardHeader
title={props.title}
actAsExpander={false}
showExpandableButton={false}
/>
</Card>
</div>
)
}
}
export default DragSource(ItemTypes.<Your Item Type>, CardSource, collect)(CardDragContainer);
Then you would use this DragContainer in render of the higher level component like this:
render() {
var Populate = this.props.mediaFiles.map((value) => {
return(
<div>
<MuiThemeProvider>
<CardDragContainer
value={value}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
shadow={this.state.shadow}
/>
</MuiThemeProvider>
</div>
)
});
return (
<div>
<MuiThemeProvider>
<div className="mediaFilesComponent2">
{Populate}
</div>
</MuiThemeProvider>
</div>
);
}
That should give you a list of Cards, each of which will be individually draggable.

Categories

Resources