React: Pass component as prop without using this.props.children - javascript

I have this a component Foo.js:
// a svg component with a star.svg icon
import { IconStar } from 'react-svg-icons';
// call a button with a custom icon
<Button icon={ IconStar }> Click to trigger </Button>
And then on button.js I have:
render() {
const theIcon = this.props.icon;
return (
<button>
{this.props children}
{icon() /*this works but i can't pass props inside it*/}
<theIcon className={ Styles.buttonIcon } />
</button>
)
}
I want to render <IconStar> inside <Button>, how do I do that?.
I can't pass this as this.prop.children because it has more logic around the icon prop, but in this case I'm showing only a demo.
Thanks

The only thing you have missed is that for JSX to transpile to JS custom components should be named starting with a capital letter, e.g. <TheIcon />, while lowercase letter signifies native DOM elements, e.g. <button />.:
render() {
const TheIcon = this.props.icon; // note the capital first letter!
return (
<button>
{this.props.children}
<TheIcon className={ Styles.buttonIcon } />
</button>
)
}
https://jsfiddle.net/03h2swkc/3/

If your using es6 then you can use a spread operator to pass the attributes to a below component, I think that might solve your problem:
var MyIcon = (props) => {
return <i {...props}> {
props.className
} < /i>
};
var MyButton = (props) => {
return ( < button > {
props.children
}<MyIcon {...props} />< /
button > );
}
ReactDOM.render( <
MyButton className = 'my-icon'>My Button Text</MyButton> ,
document.getElementById('myComponent')
)
<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>
<div id='myComponent'></div>
so the ...props will pass myProps into IconStar. Obviously you can pass anything you want through.

Related

Can we pass setState directly to the button element?

Generally, i would pass setVisFalse as a prop to Modal and then define a button inside Modal component that calls it, but i want to make Modal dynamic such that, instead of a button it could be anything (another component) defining the onClick event listener.
The following code works fine, but i want to know is it correct approach?
const Parent = () => {
const [vis, setVis] = useState(false);
return (
<>
{vis && (
<Modal> // generally, i pass here a setVisFalse as a prop.
<h1>Hello Modal</h1>
<button onCLick={setVisFalse}>Close Modal</button> // directly defining onCLick here only.
</Modal>
)}
</>
);
};
export default class Modal extends React.Component {
render() { // instead of definig a button here,
return <div className="modal">{this.props.children}</div> it should be already inside children
}
}
You obviously want to pass the boolean to the the vis state so the way you are doing it won't achieve that
You need to do this instead
const Parent = () => {
const [vis, setVis] = useState(false);
const setVisState = () => {
setVis(value => !value);
}
return (
<>
{vis && (
<Modal> // generally, i pass here a setVisFalse as a prop.
<h1>Hello Modal</h1>
<button onCLick={setVisState }>Close Modal</button> // directly defining onCLick here only.
</Modal>
)}
</>
);
};
use useContext API .
pros - can be used in any nested level of children.

What is the React way of inserting an icon into another component?

I'm trying to create an WithIcon wrapper component which would insert a child (icon) into a wrapped component.
Let's say I have a button:
<Button>Add item</Button>
I want to create a component WithIcon which will be used like this:
<WithIcon i="plus"><Button>Add item</Button></WithIcon>
Ultimately what I want to achieve is this:
<Button className="with-icon"><i className="me-2 bi bi-{icon}"></i>Add item</Button>
Notice the added className and the tag within the Button's body.
I'm trying to figure out how the WithIcon component's code should look like. What is the React way of achieving this result?
The hardest part was the rules of using the WithIcon Will we only have one ?
Will we have only it at the leftmost ? Something like that.
But if we follow your example. We can relatively write something like this for the WithIcon
const WithIcon = ({ i, children }) => {
return React.Children.map(children, (child) => {
return (
<>
<i className={`me-2 bi bi-${i}`}></i>
{React.cloneElement(child, { className: "with-icon" })}
</>
);
});
};
Then we can just use it the way you want it
<WithIcon i="plus"><Button>Add item</Button></WithIcon>
What we do is just looping through the children which in react is any nested jsx you throw in it (Button in our case)
You can find my fiddle here : https://codesandbox.io/s/react-font-awesome-forked-321tz?file=/src/index.js
UPDATE
So my previous answer does not fully meet the end result we want. The will need to be the main parent
The idea is still quite the same as before but here we are infering the type of the component we passed inside the WithIcon This also adds a safeguard when we passed a nested component inside the WithIcon
const WithIcon = ({ i, children }) => {
return React.Children.map(children, (child) => {
const MyType = child.type; // So we can get the Button
return (
<MyType className="with-icon">
<i className={`me-2 bi bi-${i}`}></i>
{(React.cloneElement(child, {}), [child.props.children])}
</MyType>
);
});
};
I think I'll go to sleep I'll update the rest of the explanation at later date.
See the fiddle here :
https://codesandbox.io/s/react-font-awesome-forked-y43fx?file=/src/components/WithIcon.js
Note that this code does not preserved the other props of the passed component, but you can relatively add that by adding {...child.props} at the MyComponent which is just (reflection like?) of infering the component.
Of course also have another option like HOC Enhancers to do this but that adds a bit of complexity to your how to declare your component api. So Pick whats best for ya buddy
Maybe try using a higher order component?
const withIcon = (icon, Component) => ({children, ...props}) => {
return (
<Component className="with-icon" {...props}>
<i className=`me-2 bi bi-${icon}` />
{children}
</Component>
);
}
Then the usage is
const ButtonWithIcon = withIcon("your-icon", Button);
<ButtonWithIcon>Add Item</ButtonWithIcon>
From my experience with react it usually comes down to either using a property inside the component like here (https://material-ui.com/api/button/) or higher order component like what I described.
There are two common patterns used in React for achieving this kind of composition:
Higher-Order Components
Start by defining a component for your button:
const Button = ({ className, children }) => (
<button className={className}>{children}</button>
);
Then the higher-order component can be implemented like this:
const withIcon = (Component) => ({ i, className = '', children, ...props }) => (
<Component {...props} className={`${className} with-icon`}>
<i className={`me-2 bi bi-${i}`} />
{children}
</Component>
);
Usage:
const ButtonWithIcon = withIcon(Button);
<ButtonWithIcon i="plus">Add Item</ButtonWithIcon>
Context
Start by defining the context provider for the icon:
import { createContext } from 'react';
const Icon = createContext('');
const IconProvider = ({ i, children }) => (
<Icon.Provider value={i}>{children}</Icon.Provider>
);
and then your component:
import { useContext } from 'react';
const Button = ({ className = '', children }) => {
const i = useContext(Icon);
if (i) {
className += ' with-icon';
children = (
<>
<i className={`me-2 bi bi-${i}`} />
{children}
</>
);
}
return (
<button className={className}>{children}</button>
);
};
Usage:
<IconProvider i="plus"><Button>Add Item</Button></IconProvider>

having difficulty in extracting out id in react component

I can see item and item id as it loops to render on the screen but i don't see the value of id when i click on any of the Tile where Tile is a div and react styled component.
class CategoryOffers extends React.Component {
passidtopointscreen =(id)=>{
console.log("id is", id);
localStorage.setItem('points_id',id);
this.props.history.push('/marketplacepoints')
debugger
}
render() {
debugger
return (
<Wrapper>
{this.props &&
this.props.cards_data &&
this.props.cards_data.map(item => {
return (
<Tile onClick={(item)=>this.passidtopointscreen(item.id)}>
<ImageWrapper>
<Image src={item.logo} height={'24px'} width={'73px'} />
</ImageWrapper>
<CardString>{item.offer_summary}</CardString>
</Tile>
)
})}
</Wrapper>
)
}
}
onClick={()=>this.passidtopointscreen(item.id)}
while adding item there you create new instance for this keyword for no reason
By having the same argument-name as your already decleared argument (item), you overwrite the outer argument. There should be no reason for you here to use the event-argument, if I have understood your question correctly.
I would also suggest avoiding localstorage and instead make use of the state.
I made the component into functional one here:
import React from "react";
const CategoryOffers = ({history,cards_data}) => {
const passidtopointscreen =(id)=>{
localStorage.setItem('points_id',id);
history.push('/marketplacepoints')
}
return (
<Wrapper>
{
cards_data?.map(item => {
return (
<Tile onClick={(event)=>passidtopointscreen(item.id)}>
<ImageWrapper>
<Image src={item.logo} height={'24px'} width={'73px'} />
</ImageWrapper>
<CardString>{item.offer_summary}</CardString>
</Tile>
)
})}
</Wrapper>
)
}
}

How to use MDCRipple.attachTo on multiple buttons in React Component

I have a simple React component that renders multiple buttons from an array in my props. I'm applying the ripple on DidMount, however, it's only attaching on the first button, the rest are being ignored. It looks like the attachTo only takes the first element. Is there another way to attach to all the buttons on didmount?
class NavBar extends Component {
constructor(props) {
super(props);
this.state = {
links
};
}
componentDidMount() {
MDCRipple.attachTo(document.querySelector('.mdc-button'));
}
render() {
return (
<section>
{this.state.links.map((link, i) => {
return (
<StyledLink key={i} to={link.url}>
<StyledButton className="mdc-button">
<StyledIcon className="material-icons">{link.icon}</StyledIcon>
<StyledTypography className="mdc-typography--caption">
{link.title}
</StyledTypography>
</StyledButton>
</StyledLink>
);
})}
</section>
);
}
}
Final markup
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj mdc-ripple-upgraded" style="--mdc-ripple-fg-size:57.599999999999994px; --mdc-ripple-fg-scale:2.1766951530355496; --mdc-ripple-fg-translate-start:-7.799999999999997px, 19.200000000000003px; --mdc-ripple-fg-translate-end:3.200000000000003px, 19.200000000000003px;">
...content
</button>
</a>
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj">
...content
</button>
</a>
Updated
I was able to find a way to use the attachTo with each button, but it still seems like there's a better way.
I changed by componentDidMount() to:
componentDidMount() {
this.state.links.forEach((link) => {
MDCRipple.attachTo(document.getElementById(`button-navbar-${link.id}`));
});
}
and then changed my render to
<StyledButton id={`button-navbar-${link.id}`} className="mdc-button">
Is there a way to do this without having to iterate through the array?
The react way to do this is to write component that injects the necessary logic.
class RippleButton extends Component {
const handleRef = elem => MDCRipple.attachTo(elem);
render() {
return (
<StyledButton {...this.props} ref={this.handleRef} />
);
}
}
Then render that component instead of your original StyledButton component and it will call the MDCRipple.attachTo() itself with its ref.
Depending on how the StyledButton is implemented you may need to use another prop to get the ref to the underlying DOM element. You did not provide enough of your code to exactly know this.

Toggle component in react on button click

I have 4 components. I only want to render one at a time. I have buttons in my nav, when i click one it should render that component and then hide the other 3 (i.e. set them to null)
This is easy with 2 components. I just have a toggle function like so:
toggle() {
this.setState(prevState => ({
showTable: !prevState.showTable
}));
}
I have tried to adapt this for now where I have this:
showComponent(component) {
this.setState(prevState => ({
[component]: !prevState.component
}));
}
This currently shows the component when i click the corresponding button. However, it wont hide the component once the same button is clicked again.
I have all my buttons calling this method like so:
<button onClick={() => this.showComponent('AddPlayer')}>Add</button>
<button onClick={() => this.showComponent('ShowPlayers')}>Players</button>
<button onClick={() => this.showComponent()}>Table</button>
<button onClick={() => this.showComponent()}>Matches</button>
any ideas?
EDIT:
{this.state.AddPlayer ?
<div className="add-container">
<AddPlayer />
</div>
:
null
}
{this.state.ShowPlayers ?
<div className="players-container">
<Players />
</div>
:
null
}
You can do this in multiple ways,
One way is, create a const with all state values and components like
const components = {
"AddPlayer": <AddPlayer />,
"ShowPlayers": <Players />,
"Something1": <Something1 />,
"Something2": <Something2 />
}
set value to state like
showComponent(componentName) {
this.setState({displayedTable: componentName});
}
and inside render simply
render(){
return(
<div>
{components[this.state.displayedTable]}
</div>
)
}
Using Switch case
renderComponent(){
switch(this.state.displayedTable) {
case "AddPlayer":
return <AddPlayer />
case "ShowPlayers":
return <Players />
}
}
render () {
return (
<div>
{ this.renderComponent() }
</div>
)
}

Categories

Resources