Implement dynamic propTypes in ReactJs component - arrays

I have an object with icons available to some button.
const icons = {
check: 'icon-CheckSmall',
chat: 'icon-ChatMedium',
investors: 'icon-InvestorsMedium',
download: 'icon-DownloadMedium',
};
const Button = (props) => {
const {
buttonType,
buttonText,
onClick,
disabled,
} = props;
return (
<button
style={ icons }
type={ buttonType }
onClick={ onClick }
disabled={ disabled && 'disabled' }
>
{ <FormattedMessage id={ buttonText } /> }
</button>
);
};
I want to create prop types for component with keys array
Button.propTypes = {
icon: PropTypes.oneOf(Object.keys(icons)),
};
In this case, Object.keys does not work.
Is there anyone who manage to implement dynamic prop types in the component?

Related

React setState for Font Color with array index

Im having difficulties understanding setState. im able to make the color change but all of the array items turns red with this code. Im new in react so please help me here
{class.map((c, index) => (
<Link style={{color: this.state.fcolor}} key={index} onClick={(e) => {
this.handleChangeColor(index) }}>
{c.classname}<i className="fa fa-check"> </i>
</Link>
))}
handleChangeColor(event, index){
this.setState({
fcolor: "red"
})
}
I want it specifically using index but when i add the index
fcolor[index]:'red' is runs tru an error that needs an ','
That's because all your link may share the same state. A good pattern to show an active link, would be to store an active ID to specify wich link should show a red color.
You can inspire you from this Tab component, found at https://www.digitalocean.com/community/tutorials/react-tabs-component
class Tab extends Component {
static propTypes = {
activeTab: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
};
onClick = () => {
const { label, onClick } = this.props;
onClick(label);
}
render() {
const {
onClick,
props: {
activeTab,
label,
},
} = this;
let className = 'tab-list-item';
if (activeTab === label) {
className += ' tab-list-active';
}
return (
<li
className={className}
onClick={onClick}
>
{label}
</li>
);
}
}
export default Tab;

Change Component property through onClick

I have a ButtonGroup with a few Buttons in it, and when one of the buttons gets clicked, I want to change its color, I kinda want to make them behave like radio buttons:
<ButtonGroup>
<Button
variant={"info"}
onClick={(e) => {
..otherFunctions..
handleClick(e);
}}
>
<img src={square} alt={".."} />
</Button>
</ButtonGroup>
function handleClick(e) {
console.log(e.variant);
}
But that doesnt work, e.variant is undefined.
If it was just a single button I would have used useState and I would be able to make this work, but how do I make it work when there are multiple buttons, how do I know which button is clicked and change the variant prop of that button? And then revert the other buttons to variant="info"
Another approach that I could think of is to create my own Button that wraps the bootstrap Button and that way I can have access to the inner state and use onClick inside to control each buttons state, but I'm not sure if that will work, as then how would I restore the other buttons that werent clicked..?
To further from my comment above, you could create your own button component to handle its own state and remove the need to have lots of state variables in your main component e.g.
const ColourButton = ({ children }) => {
const [colour, setColour] = React.useState(true)
return (
<button
onClick={ () => setColour(!colour) }
style = {{color: colour ? "red" : "blue"} }
>
{ children }
</button>
)
}
That way you can just wrap your image in your new ColourButton:
<ColourButton><img src={square} alt={".."} /></ColourButton>
Edit:
I actually like to use styled-components and pass a prop to them rather than change the style prop directly. e.g. https://styled-components.com/docs/basics#adapting-based-on-props
EDIT: Kitson response is a good way to handle your buttons state locally :)
I like to handle the generation of multiple elements with a function. It allows me to customize handleClick.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [buttons, setButtons] = useState([
{
id: 1,
variant: "info"
},
{
id: 2,
variant: "alert"
}
]);
const handleClick = id => {
setButtons(previous_buttons => {
return previous_buttons.map(b => {
if (b.id !== id) return b;
return {
id,
variant: "other color"
};
});
});
};
const generateButtons = () => {
return buttons.map(button => {
return (
<button key={button.id} onClick={() => handleClick(button.id)}>
Hey {button.id} - {button.variant}
</button>
);
});
};
return <div>{generateButtons()}</div>;
}
https://jrjvv.csb.app/
You can maintain a state variable for your selected button.
export default class ButtonGroup extends Component {
constructor(props) {
super(props);
this.state = {
selected: null
};
}
handleClick = e => {
this.setState({
selected: e.target.name
});
};
render() {
const selected = this.state.selected;
return (
<>
<button
name="1"
style={{ backgroundColor: selected == 1 ? "red" : "blue" }}
onClick={this.handleClick}
/>
<button
name="2"
style={{ backgroundColor: selected == 2 ? "red" : "blue" }}
onClick={this.handleClick}
/>
<button
name="1"
style={{ backgroundColor: selected == 3 ? "red" : "blue" }}
onClick={this.handleClick}
/>
</>
);
}
}
Here is a working demo:
https://codesandbox.io/live/OXm3G

Props are undefined in React

I'm new to js and reactjs. I'm trying to create a ButtonGroup with few Buttons inside, in hope of when I click a particular Button (in ButtonGroup) only that particular button will get highlighted (change colour) and rest will be of normal colour.
Below is the code which does the above behaviour but in setColour method I'm getting an error _this.state.selected.props is undefined. Could someone point out the where I'm getting wrong ? Also, if someone can tell me if this is the correct way to approach this problem.
import React from "react"
import {
ButtonGroup,
Button
} from "reactstrap"
class MainButtonsGroup extends React.Component {
constructor (props) {
super(props)
this.state = {
selected: null
}
}
handleSelection = (e) => {
this.setState({selected: e.target})
}
setColour = (key) => {
if (this.state.selected)
{
// ERROR : _this.state.selected.props is undefined
return (this.state.selected.props.key === key) ? 'primary' : 'secondary'
}
}
render() {
return (
<ButtonGroup>
<Button key={1} onClick={this.handleSelection} color={this.setColour(1)}>MainButtonA</Button>
<Button key={2} onClick={this.handleSelection} color={this.setColour(2)}>MainButtonB</Button>
<Button key={3} onClick={this.handleSelection} color={this.setColour(3)}>MainButtonC</Button>
</ButtonGroup>
)
}
}
export default MainButtonsGroup;
You should not hold on to the e.target reference and you must be getting a React warning in your console about it? You just created a memory leak in your app.
Instead copy what you need from the target and let the reference be garbage collected. Although in your case there's no need to be attaching data to the DOM node:
<Button onClick={() => this.handleSelection(this.setColour(3))}>MainButtonC</Button>
Note you don't need key={3} unless you're mapping the elements in a loop.
handleSelection = (color) => {
this.setState({ selected: color })
}
However this code is a bit strange, just record the index of the clicked button and give it a class to style it e.g.
class MainButtonsGroup extends React.Component {
state = {
selectedIndex: null,
}
handleSelection = (index) => {
this.setState({selectedIndex: index})
}
render() {
const idx = this.state.selectedIndex;
return (
<ButtonGroup>
<Button className={idx === 1 ? 'primary' : 'secondary'} onClick={() => this.handleSelection(1)}>MainButtonA</Button>
<Button className={idx === 2 ? 'primary' : 'secondary'} onClick={() => this.handleSelection(2)}>MainButtonB</Button>
<Button className={idx === 3 ? 'primary' : 'secondary'} onClick={() => this.handleSelection(3)}>MainButtonC</Button>
</ButtonGroup>
);
}
}
You cannot get the props of a component from a DOM node. You could instead keep your button names in an array in your component state and use that to render your buttons in the render method.
You can then pass in the button name to the handleSelection and use that as your selected value. If your button is the selected one it can be given the primary color, otherwise the secondary one.
Example
import React from "react";
import ReactDOM from "react-dom";
import { ButtonGroup, Button } from "reactstrap";
import "bootstrap/dist/css/bootstrap.min.css";
class MainButtonsGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
buttons: ["A", "B", "C"],
selected: null
};
}
handleSelection = button => {
this.setState({ selected: button });
};
render() {
const { buttons, selected } = this.state;
return (
<ButtonGroup>
{buttons.map(button => (
<Button
key={button}
onClick={() => this.handleSelection(button)}
color={selected === button ? "primary" : "secondary"}
>
MainButton{button}
</Button>
))}
</ButtonGroup>
);
}
}

Material-UI - Tooltip on Select/MenuItem component stays displayed after selection

I'm using Material-UI's Select component with a Tooltip surrounding it, like so:
<Tooltip title="Tooltip Test">
<Select value={this.state.age} onChange={this.handleChange}
inputProps={{ name: "age", id: "age-simple" }}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>...</MenuItem>
</Select>
</Tooltip>
My problem is that when I click the Select component, the Tooltip stays displayed, during the use of the Select, and even after an item was selected.
I want it to disappear as soon as the Select is clicked so that it doesn't stay over the MenuItems (Changing the zIndex is not the solution I want) and also still is not displayed even after selecting an item in the menu.
I made a codesandbox with the simple issue I have: https://codesandbox.io/s/yvloqr5qoj
But I am using typescript and this is the actual code I'm working with:
ControlledTooltip
import * as React from 'react';
import PropTypes from 'prop-types';
import withStyles, { WithStyles } from 'material-ui/styles/withStyles';
import { Tooltip } from 'material-ui';
const styles = {
}
type State = {
open: boolean,
};
type Props = {
id: string,
msg: string,
children: PropTypes.node,
};
class ControlledTooltip extends React.PureComponent<Props & WithStyles<keyof typeof styles>, State> {
constructor(props) {
super(props);
this.state = {
open: false,
};
}
private handleTooltipClose(): void {
this.setState({ open: false });
}
private handleTooltipOpen(): void {
this.setState({ open: true });
}
render() {
const { id, msg, children } = this.props;
const { open } = this.state;
return(
<div>
<Tooltip id={id}
title={msg}
open={open}
onClose={this.handleTooltipClose}
onOpen={this.handleTooltipOpen}
>
{children ? children : null}
</Tooltip>
</div>
);
}
}
export default withStyles(styles)(ControlledTooltip);
Component using the Tooltip
<ControlledTooltip msg={'Filter'}>
<Select value={this.state.type} onChange={this.handleChange.bind(this, Filter.Type)}>
{this.docTypeFilters.map(item => {
return (<MenuItem key={item} value={item}>{item}</MenuItem>);
})}
</Select>
</ControlledTooltip>
TL;DR: You have to make your tooltip controlled.
UPDATE:
Based on your actual code, replace the return of your render() with this:
<Tooltip id={id}
title={msg}
open={open}
>
<div onMouseEnter={this.handleTooltipOpen}
onMouseLeave={this.handleTooltipClose}
onClick={this.handleTooltipClose}
>
{children ? children : null}
</div>
</Tooltip>
The problem was that you were using onClose/onOpen from the tooltip, which works as is uncontrolled. Now the div containing the select(or any children) has the control over the tooltip.
ANSWER FOR THE PASTEBINS
You will need to handle the open prop of the Tooltip:
class SimpleSelect ...
constructor(...
state = {..., open: false}; // the variable to control the tooltip
Alter your change handling:
handleChange = event => {
this.setState({ [event.target.name]: event.target.value, open: false });// keeps the tooltip hidding on the select changes
};
handleOpen = (open) => {
this.setState({ open }); // will show/hide tooltip when called
}
And reflect it in your render():
const { open } = this.state; // obtains the current value for the tooltip prop
...
<Tooltip title="Tooltip Test" open={open}>
...
<Select ... onMouseEnter={() => this.handleOpen(true)}
onMouseLeave={() => this.handleOpen(false)}
onClick={() => this.handleOpen(false)} >
The event handlers(onMouseEnter, onMouseLeave, onClick) in Select now control the tooltip show/hide behavior.
Same as David's answer, but for functional components with React hooks.
In your component before the return:
const [tooltipOpen, setTooltipOpen] = useState(false)
const handleTooltip = bool => {
setTooltipOpen(bool)
}
Then in your return:
<Tooltip ... open={tooltipOpen}>
<Select
onMouseEnter={() => {handleTooltip(true)}}
onMouseLeave={() => {handleTooltip(false)}}
onMouseClick={() => {handleTooltip(false)}}
...
>

jsx print text conditional for property without value in styled-component

I have this component:
<Button type="submit" { invalid ? 'primary': null }>
this component is styled component:
import styled from 'styled-components';
export const Button = styled.button`
font-size: 15px;
padding: 0.25em 1em;
border: solid 1px ${(props) => {
let color;
if (props.primary) {
color = 'red';
} else {
color = '#ffffff';
}
return color;
}};
`;
I get this error:
Syntax error: Unexpected token ^invalid, expected ... (64:54)
I need just to send a property 'primary' if invalid is true, to get this:
<Button type="submit" primary/>
I don't want write:
primary = { invalid }
the component calls this button is:
import React from 'react';
import { Button } from './layouts/cssstyled';
const getConditionalProps = ( props) => {
// fill your `invalid` variable in this function or pass it to it
const myprops = {};
myprops.primary = true ;
myprops.secondary = false;
return myprops;
}
const Form = (props) => {
console.log('form props');
console.log(props);
const { handleSubmit, invalid, pristine, reset, submitting, t } = props;
return (
<form onSubmit={handleSubmit}>
<p>Invalid? {JSON.stringify(invalid)}</p>
<Button type="submit" disabled={submitting} primary={invalid ? "" : null} >
Buton styled component does not work
</Button>
<button primary={invalid ? "" : null}> button native works</button>
<div className="formError">
{pristine}
{invalid && t('form.haserrors')}
</div>
</div>
</form>
);
};
export default reduxForm({
form: 'CustomerForm', // a unique identifier for this form
})(Form);
You can use boolean values, so your conditional will become like this:
<Button type="submit" primary={ !!invalid } >
Or if you don't want your button to have a primary value when false, you can do this:
const button = invalid ? <Button type="submit" primary > : <Button type="submit" >
I suggest using the React-JS Spread Attributes. It is great tool to spread props on a component with conditional behavior:
class MyComponent extends React.Component {
// ...
getConditionalProps() {
const props = {};
// fill your `invalid` variable in this function or pass it to it
if (invalid) {
props.primary = true;
}
return props
}
render() {
return (
<Button type="submit"
{ ...this.getConditionalProps() } />
);
}
}
Another solution is conditional rendering:
class MyComponent extends React.Component {
// ...
render() {
return (invalid ?
<Button type="submit" primary />
:
<Button type="submit" />
);
}
}
Please note that there may be a better solution to your problem: you need to handle the behavior of the Button component from inside the component itself instead of trying to decide from the parent component whether to send a specific prop or not. But the solution above does exactly what you are requiring.

Resources