I have a series of icon buttons in which most properties are common. The only thing that changes is the icon type and onClick functions. Currently, I am implementing it in the following manner:
const listOfButtons = <div>
<IconButton
flat
icon={<AddIcon />}
onClick={doSomething}
primary
/>
<IconButton
flat
icon={<EditIcon />}
isDisabled
primary
/>
<IconButton
flat
icon={<TrashIcon />}
isDisabled
primary
/>
<IconButton
flat
icon={<RefreshIcon />}
primary
/>
</div>;
I want to try to reduce the redundancy of the code and try to define only one IconButton object and pass different icons and different onClicks to it as in future there might be more icon buttons. I am stuck as to how to assign onclick to a particular icon button. Is there an efficient way of defining multiple icon buttons and assigning icons and onclicks than what I have done above? Please help!
You could create an array of the changing attributes and then use Array.map.
const buttonData = [
{
icon: <AddIcon />,
onClick: doSomething
},
{
icon: <EditIcon />,
isDisabled: true
},
{
icon: <TrashIcon />,
isDisabled: true
},
{
icon: <RefreshIcon />
}
];
const listOfButtons = <div>
{
buttonData.map((data, index) => (
<IconButton
key={index}
flat
icon={data.icon}
onClick={data.onClick}
isDisabled={data.isDisabled}
primary
/>
))
}
</div>;
NOTE: When mapping React components React requires a key attribute on the root component returned from the map. This is so that React can keep track of which component is which (in the case of reordering or removing an item from the array). It is preferred to use a unique identifier from the item being mapped (as this will remain the same if the item changes position), but if you are in full control of the array in use and can ensure that no item ever changes index then using index is fine. If you use index and the items do change position it will result in more DOM changes than necessary.
Step 1: Create a separate component for button :
class IconButtons extends React.Component{
render(){
return(
<IconButton
flat
icon={this.props.icon}
primary
onClick = {this.props.onClick}
/>
)
}
Step 2 :
const buttonList = [
{
icon: <AddIcon />,
onClick: doSomething
},
{
icon: <EditIcon />,
isDisabled: true
},
{
icon: <TrashIcon />,
isDisabled: true
},
{
icon: <RefreshIcon />
}
];
const listOfButtons =
<div>
{
buttonList .map((ele,index)=>(<IconButtons
key=index
icon={ele.icon}
onClick={ele.onClick}
/>))
}
</div>;
Use Array.map to make it better
const iconsList = [<AddIcon />,<EditIcon />,<TrashIcon />,<RefreshIcon />]
const listOfButtons =
<div>
{
iconsList.map((icon,index)=>(<IconButton
key=index
flat
icon={icon}
onClick={doSomething}
primary
/>))
}
</div>;
If I understand your question, you are looking for a DRYer solution. The solutions so far offer mapping from a config, which is certainly a viable option. One alternative option would be to use composition. A quick analysis of your list of buttons show the common props between them all are flat and primary; the remaining props can change with some variability. So you could define a <FlatPrimaryButton /> like so:
const FlatPrimaryButton = ({props}) => (
<IconButton
flat
primary
{...props}
/>
);
This would allow you to write something more like this for your list:
const listOfButtons = <div>
<FlatPrimaryButton
icon={<AddIcon />}
onClick={doSomething}
/>
<FlatPrimaryButton
icon={<EditIcon />}
isDisabled
/>
<FlatPrimaryButton
icon={<TrashIcon />}
isDisabled
/>
<FlatPrimaryButton
icon={<RefreshIcon />}
/>
</div>;
This solution could then be combined with the other .map based solutions.
Related
I have the following code which I'm trying to refactor
const [edit, setEdit] = useState(false);
<ListItemText
primary={edit ?
<TextareaAutosize ... />
: <Typography ..../>}
/>
The TextareaAutosize repeats a lot through the app so I thought I would convert it into a separate component.
export default function Textarea(props) {
const [edit, setEdit] = useState(false);
if(edit){
return(
<TextareaAutosize .../>
)
}
return false
}
As you can see, if edit is false the component returns false. The back at the top I thought I would do this
import Textarea from "../Textarea";
<ListItemText
primary={Textarea ?
<Textarea ... />
: <Typography ..../>}
/>
By my logic, there should be a way to check if component Textarea will return false, right?
A "component" (JSX) is a UI element. You must return JSX or null, and the rendering engine will render the returned JSX or nothing if null.
You can check a flag and short-circuit:
{isFlag && <MyComponent ... />}
or have the component render or not (or if you want to keep state just show/hide):
<MyComponent isVisible={isFlag} />
Or possible return different components depending on flags:
if (isLoading) {
return <MyLoadingSkeletonComponent />
}
<MyMainUIComponent />
EDIT:
You may want to refactor into a compound component where you take the flag and hide the show/hide logic within it
const TextArea = ({edit, editMode, viewMode}) => (
edit ? editMode : viewMode
)
// Call it with the flag and both components:
<TextArea
edit={edit}
editMode={<TextareaAutosize ... />}
viewMode={<Typography ... />}
/>
Have the best of the two worlds, your idea of refactoring is correct, but it is missing something.
try something like this:
export default function Textarea(props) {
props.edit ? <TextareaAutosize .../> : <Typography ..../>
}
and
<ListItemText
primary=<TextareaAutosize {edit} ... />
/>
In this case you have one place to change TextareaAutosize and Typography codes, less messy code in parent listing.
Hope this helps.
Here is a link to a working example of the site: https://codesandbox.io/s/eloquent-kapitsa-kk1ls
The issue I am having is with Buttons.js:
import React, { Component } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import registerIcons from "./FontAwesome";
registerIcons();
const DATA = [
{
href: "https://github.com/",
icon: ["fab", "github"],
label: "Github"
},
{
href: "https://www.linkedin.com/in//",
icon: ["fab", "linkedin"],
label: "LinkedIn"
},
{
href: "...",
icon: ["fas", "file-alt"],
label: "Resume"
},
{
href: "mailto:",
icon: ["fas", "paper-plane"],
label: "Email me"
}
];
const Icon = ({ href, icon, label }) => {
return (
<span className="button">
<a href={href} target="_self" rel="noopener noreferrer">
<FontAwesomeIcon className="icon" icon={icon} size="3x" />
<span className="icon_title">{label}</span>
</a>
</span>
);
};
class Buttons extends Component {
render() {
return (
<div>
{DATA.map(props => (
<Icon {...props} />
))}
</div>
);
}
}
export default Buttons;
I have looked through the other topics related to this issue and none are analogous to my case. I am not passing parameters to the render method - just taking an existing variable and mapping it to produce my output.
I have tried changing the Buttons render method to the code below in addition to a few other permutations of the DATA array without much progress.
<div key={DATA.label}>
{DATA.map(props => (
<Icon {...props} />
))}
</div>
I have also read through the React documentation on keys with no success.
you should change the code with this:
<div>
{DATA.map((props,i) => (
<Icon key={i} {...props} />
))}
</div>
As it is advised not to use index as a key for component. I would say add a random number and do something like below
<div>
{DATA.map((props,i) => (
<Icon key={Math.random()*1000*i} {...props} />
))}
</div>
This is rather a generic approach if you don't have any unique key in your object.
Try
<Icon {...props} key={props.href} />
From react documentation
A good rule of thumb is that elements inside the map() call need keys.
Please take a look to this documentation
https://reactjs.org/docs/lists-and-keys.html#extracting-components-with-keys
The key needs to go on like this:
<div key={DATA.label}>
{DATA.map(props => (
<Icon key={props.label} {...props} />
))}
</div>
As you probably saw in the React documentation on keys you reference, they say
The best way to pick a key is to use a string that uniquely identifies a list item among its siblings.
Each of the objects in your DATA array have unique strings and you can use any of them key={props.href} or key={props.icon[1]}. I used props.label in the answer above but the point is it doesn't have to be totally unique in all the world, it just needs to be unique compared to the other items in the list.
I am new to React and am building a tab component using Material UI's Tabs component. I'd like to place the Material UI badge component within the Tab component's label prop, but I'm not sure how to go about this.
The Tab component looks as such:
<Tab
key={i}
label={label}
{...globalTabProps}
{...tabProps}
classes={{
wrapper: cx('MuiTab-wrapper'),
}}
/>
I'm trying to add the badge as such:
const label = {
<Badge
color="primary"
className={
badgeProps.badgeContent === ''
? classNames(classes.MuiBadge, classes.MuiBadgeDotted)
: classNames(classes.MuiBadge, classes.MuiBadgeNumber)
}
badgeContent={''}
invisible={false}
{...globalBadgeProps}
{...badgeProps}
></Badge>
};
Of course, this errors out (parsing error), but I don't think this is the correct way to handle this anyway.
Would anyone be able to point me in the right direction?
Many thanks!
You should wrap it with (), like so.
const label = (
<Badge
color="primary"
className={
badgeProps.badgeContent === ''
? classNames(classes.MuiBadge, classes.MuiBadgeDotted)
: classNames(classes.MuiBadge, classes.MuiBadgeNumber)
}
badgeContent={''}
invisible={false}
{...globalBadgeProps}
{...badgeProps}
></Badge>
)
Note the () wrapping it.
Then do it like so:
<Tab
key={i}
label={label}
{...globalTabProps}
{...tabProps}
classes={{
wrapper: cx('MuiTab-wrapper'),
}}
/>
What it is done inside:
const WhateverComponent = (props) => (
<div>
...
{props.label}
</div>
);
I have a function componet like example, I know how to use PropTypes to set type of props and give them default value, but my team is using typescript, so I have to know how to use typescript to get this, I tried to search answer, but I didn't get exactly answer, anyone konw how to do this?
example :
const List = ({title,showTitle,buttonText,onClickButton,itemText}) =>
<div>
{ showTitle &&
<Typography
variant="body1"
color="textSecondary"
align="left"
>
{title}
</Typography>
}
<ListItem button={false}>
<ListItemIcon>
<IconVideocam />
</ListItemIcon>
<ListItemText primary={itemText} />
<ListItemIcon>
<Button
id={"Button"}
onClick={onClickButton}
color="primary"
>
{ buttonText }
</Button>
</ListItemIcon>
</ListItem>
</div>
export default List
You can assert the props of stateless React components using the React.SFC type like so:
// Props.
interface Props {
buttonText: string
itemText: string
showTitle: boolean
onClickButton: () => void
title: string
}
// List.
const List: React.SFC<Props> = props => <div> .. </div>
I created a basic component such as:
export default (props) => (
<TouchableOpacity {...props} style={styles.button}>
{props.title && <Text style={styles.text}>{props.title}</Text>}
{props.icon && <Icon name={props.icon} />}
</TouchableOpacity>
);
I can then call it with <Component title="Home" icon="home" /> for instance.
The problem is that passing {...props} to TouchableOpacity generate errors because it does not recognize title nor icon properly.
For instance:
JSON value 'Home' of type NSString cannot be converted to...
Is there a way to filter props so that I only pass valid ones for TouchableOpacity?
Transferring Props
Sometimes it's fragile and tedious to pass every property along. In that case you can use destructuring assignment with rest properties to extract a set of unknown properties.
List out all the properties that you would like to consume, followed by ...other.
var { checked, ...other } = props;
This ensures that you pass down all the props EXCEPT the ones you're
consuming yourself.
function FancyCheckbox(props) {
var { checked, ...other } = props;
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
// `other` contains { onClick: console.log } but not the checked property
return (
<div {...other} className={fancyClass} />
);
}
ReactDOM.render(
<FancyCheckbox checked={true} onClick={console.log.bind(console)}>
Hello world!
</FancyCheckbox>,
document.getElementById('example')
);
Like Paul Mcloughlin, I would recommend using object destructuring along with a rest parameter. You can destructure your props object directly in your function parameters like so:
({title, icon, ...remainingProps}) => (...)
This extracts the title and icon props from your props object and passes the rest as remainingProps.
Your complete component would be:
export default ({title, icon, ...remainingProps}) => (
<TouchableOpacity {...remainingProps} style={styles.button}>
{title && <Text style={styles.text}>{title}</Text>}
{icon && <Icon name={icon} />}
</TouchableOpacity>
);