React-Select - clickable values with custom handler - reactjs

Using react-select, I'd like to understand how I can get the events for when selected values (multi) are clicked.
I'm using react-select with the multi select functionality (https://react-select.com/home).
Works as a charm but I'd like to make the values clickable/toggleable to change color/state (not add/remove). In the end showing them in grey/color to indicate a marked state and using the underlying state for subsequent code.
Is there any way to achieve this and if so how?

You could pass in a custom MultiValueLabel or MultiValueContainer and add an onClick handler.
import React from "react";
import ReactDOM from "react-dom";
import Select, { components } from 'react-select';
const options = [
{value: '1', label: 'Item 1', isToggled: true},
{value: '2', label: 'Item 2', isToggled: false},
{value: '3', label: 'Item 3', isToggled: false},
{value: '4', label: 'Item 4', isToggled: false},
{value: '5', label: 'Item 5', isToggled: false},
{value: '6', label: 'Item 6', isToggled: false},
]
const ReactSelectStyles = () => ({
multiValueLabel: (styles, {data: { isToggled }}) => ({
...styles,
backgroundColor: isToggled ? 'hotpink' : null,
color: isToggled ? 'white' : null
}),
});
const handleMultiValueClick = (e, props) => {
e.stopPropagation();
e.preventDefault();
console.log('A multi value has been clicked', props);
const option = options.find(option => option.value === props.data.value);
option.isToggled = !option.isToggled
}
const MultiValueLabel = props => {
return (
<div onClick={(e) => handleMultiValueClick(e, props)}>
<components.MultiValueLabel {...props} />
</div>
);
};
function App() {
return (
<Select
closeMenuOnSelect={false}
components={{ MultiValueLabel }}
defaultValue={[options[0], options[2], options[4]]}
isMulti
options={options}
styles={ReactSelectStyles()}
/>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working Example

Related

Ant Design: Correct type of Menu onClick event parameter

I am trying to use a navigation component from Antd (https://ant.design/components/menu), but it's giving me this error:
Parameter 'e' implicitly has an 'any' type.
I have tried doing e: React.MouseEvent<HTMLElement> but then the error shifts to e.key: Property 'key' does not exist on type 'MouseEvent<HTMLInputElement, MouseEvent>'.
Following is my App.tsx:
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '#ant-design/icons';
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
const items: MenuProps['items'] = [
{
label: 'Navigation One',
key: 'mail',
icon: <MailOutlined />,
},
{
label: 'Navigation Two',
key: 'app',
icon: <AppstoreOutlined />,
disabled: true,
},
{
label: 'Navigation Three - Submenu',
key: 'SubMenu',
icon: <SettingOutlined />,
children: [
{
type: 'group',
label: 'Item 1',
children: [
{
label: 'Option 1',
key: 'setting:1',
},
{
label: 'Option 2',
key: 'setting:2',
},
],
},
{
type: 'group',
label: 'Item 2',
children: [
{
label: 'Option 3',
key: 'setting:3',
},
{
label: 'Option 4',
key: 'setting:4',
},
],
},
],
},
{
label: (
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
Navigation Four - Link
</a>
),
key: 'alipay',
},
];
const App: React.FC = () => {
const [current, setCurrent] = useState('mail');
const onClick: MenuProps['onClick'] = (e) => {
console.log('click ', e);
setCurrent(e.key);
};
return <Menu onClick={(onClick)} selectedKeys={[current]} mode="horizontal" items={items} />;
};
export default App;
I am new to this and have already spent many hours trying to fix this, any help is highly appreciated! Thank you!
The correct type of e is the MenuInfo interface defined in the react-component library (rc-menu). Unfortunately, this type isn't exported. As a workaround, you can use the following code to indirectly reference the MenuInfo interface:
const onClick: MenuProps['onClick'] = (e: Parameters<MenuProps['onClick']>[0]) => {
The Parameters utility type returns an array type containing the parameter types of the specified function, and the [0] indexer returns the type of the first (and only) parameter.

change the value of component with function

I am kinda new to reactjs I am using the react-dropdown library https://github.com/fraserxu/react-dropdown and I am trying to make a dropdown menu so my user can switch between 2 languages. However, I am not sure how to properly update the new value for language.
Here's my code:
const Navbar = () => {
const languages = [
{
code: 'fr',
name: 'Français',
country_code: 'fr',
key : 1
},
{
code: 'en',
name: 'English',
country_code: 'gb',
key: 2
},
]
const defaultLanguages = languages[0].name;
const changeLanguage = (e) => {
console.log(e)
}
return (
<div className={color ? 'header header-bg' : 'header'}>
<ul>
<li>
<DropDown options={languages} value={defaultLanguages} onChange={(e) => changeLanguage} />
</li>
</ul>
</div>
)
}
export default Navbar
as you can see I want to switch between french and english but I am not sure how to pass the value to the dropdown component.
You need to use the same attributes in your options (languages) passed to the Dropdown component. You can see the examples of both flag options and object options on the official repo:
//Options
//Flat Array options
const options = [
'one', 'two', 'three'
];
//Object Array options
const options = [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two', className: 'myOptionClassName' },
{
type: 'group', name: 'group1', items: [
{ value: 'three', label: 'Three', className: 'myOptionClassName' },
{ value: 'four', label: 'Four' }
]
},
{
type: 'group', name: 'group2', items: [
{ value: 'five', label: 'Five' },
{ value: 'six', label: 'Six' }
]
}
];
Below code worked on my side:
import Dropdown from 'react-dropdown';
const Navbar = () => {
const languages = [
{
value: 'fr',
label: 'Français',
},
{
value: 'en',
label: 'English',
country_code: 'gb',
},
];
const defaultLanguages = languages[0].label;
const changeLanguage = (e) => {
console.log(e);
};
return (
<div className={'header'}>
<ul>
<li>
<Dropdown
options={languages}
value={defaultLanguages}
onChange={changeLanguage}
/>
</li>
</ul>
</div>
);
};
export default Navbar;
enter image description here
Create local state for tracking currently selected language. Also move out languages array outside of component. Here is the code:
const languages = [
{
code: 'fr',
name: 'Français',
country_code: 'fr',
key : 1
},
{
code: 'en',
name: 'English',
country_code: 'gb',
key: 2
},
]
const Navbar = () => {
const [selectedLanguage, setSelectedLanguage] = useState(languages[0].name);
const changeLanguage = (option) => {
setSelectedLanguage(option.name)
}
return (
<div className={color ? 'header header-bg' : 'header'}>
<ul>
<li>
<DropDown options={languages} value={selectedLanguage} onChange={changeLanguage} />
</li>
</ul>
</div>
)
}
export default Navbar

Why is my input field losing focus when typing a character?

I have a form that have several input fields and for some reason my component y re-rendering everytime y change the value of my input field which produces to the input to lose focus.
ContactForm.js:
const ContactForm = () => {
const [values, setValues ] = useState({
name: '',
lastname: '',
email: '',
confirmEmail: '',
message: ''
});
const inputs = [
{
id: Math.random(),
name: 'name',
type: 'text',
placeholder: 'Name'
},
{
id: Math.random(),
name: 'lastname',
type: 'text',
placeholder: 'Last Name'
},
{
id: Math.random(),
name: 'email',
type: 'email',
placeholder: 'Email'
},
{
id: Math.random(),
name: 'confirmEmail',
type: 'email',
placeholder: 'Confirm Email'
},
{
id: Math.random(),
name: 'message',
type: 'text',
placeholder: 'Message'
}
]
const handleSubmit = (e) => {
e.preventDefault();
}
MY child component, FormInput.js:
import React from 'react'
import './FormInput.css';
/* import { Input } from '../atoms/Input'; */
const FormInput = (props) => {
const { id, onChange, ...inputProps } = props;
return (
<div className='formInput'>
{/* <label htmlFor="">Username</label> */}
{/* <Input {...inputProps} onChange={onChange}/> */}
<input {...inputProps} onChange={onChange} />
</div>
)
}
export default FormInput
const onChange = (e) => {
setValues({...values, [e.target.name]: e.target.value});
}
console.log(values);
return (
<form className='contactForm' onSubmit={handleSubmit}>
{inputs.map((input) => (
<FormInput
key={input.id}
{...input}
value={values[input.name]}
onChange={onChange}
/>
))}
<SubmitBtn/>
</form>
)
}
So is there a solution for this, so that my input field doesn´t lose focus after re-rendering? Or should i prevent re-rendering?
you have 3 options here.
move the input array outside of the component so that it is always the same on every iteration. But if you are fetching this from the server, that is not possible.
you can use a useMemo hook on the input array and make sure to pass an empty array as a dependency array.
remove the Math.random function and maybe use a unique id from the server or for the time being you can use the array index (even though it is not advisable).
I have created a small POC. if you remove the useMemo, the input(s) will lose their focus on every re-render.
Following is the code:
import * as React from 'react';
import './style.css';
export default function App() {
const inputs = React.useMemo(
() => [
{
id: Math.random(),
name: 'name',
type: 'text',
placeholder: 'Name',
},
{
id: Math.random(),
name: 'lastname',
type: 'text',
placeholder: 'Last Name',
},
{
id: Math.random(),
name: 'email',
type: 'email',
placeholder: 'Email',
},
{
id: Math.random(),
name: 'confirmEmail',
type: 'email',
placeholder: 'Confirm Email',
},
{
id: Math.random(),
name: 'message',
type: 'text',
placeholder: 'Message',
},
],
[]
);
const [state, setState] = React.useState({
name: '',
email: '',
message: '',
confirmEmail: '',
lastname: '',
});
const handleChange = (e: any) => {
const value = (e.target as HTMLInputElement).value;
const name = (e.target as HTMLInputElement).name;
setState({
...state,
[name]: value,
});
};
const handleSubmit = () => {
console.log('state', state);
};
return (
<div>
{inputs.map((item) => (
<div key={item.id}>
<label>{item.name}: </label>
<input
name={item.name}
onChange={handleChange}
placeholder={item.placeholder}
/>
</div>
))}
<button onClick={handleSubmit}>Submit</button>
</div>
);
}
It's probably because you are calling Math.random in the body of the ContactForm component. You should never call Math.random() during rendering.
In your case, you can probably move the const inputs to outside the component.

how can we have different functions on ANt design menu items in the new version?

I'm sure if you're using ant design menu component you've got the same error:
Warning: [antd: Menu] children will be removed in next major version. Please use items instead.
the new way to make this is:
const items = [
{ label: 'item 1', key: 'item-1' },
{ label: 'item 2', key: 'item-2' },
{
label: 'sub menu',
key: 'submenu',
children: [{ label: 'item 3', key: 'submenu-item-1' }],
},
];
return <Menu items={items} />
and you can define your function and use it like this:
return <Menu onClick={onClick} selectedKeys={[current]} mode="horizontal" items={items} />;
how can i have different functions on my items?
i'll be glad if someone help me with this
in the old way i could easily define any functions i want on any items
Actually, we can add functions to the items array as well
const [current, setCurrent] = useState();
const func = () => {
setCurrent(e.key);
}
const func2 = () => {
setCurrent(e.key);
}
const items = [
{
label: 'item 1',
key: 'item-1'
onClick: func,
className: class1,
},
{
label: 'item 1',
key: 'item-1'
onClick: func2,
className: class2,
},,
{
label: 'sub menu',
key: 'submenu',
children: [{ label: 'item 3', key: 'submenu-item-1' }],
},
];
return <Menu selectedKeys={[current]} mode="horizontal" items={items} />;

Ant design 4.20 new Menu item style

Ant Design deprecated <Menu.Item> in 4.20.0, so I am converting it to a new format.
When I have the following code, how do I convert it into the new format?
<Menu>
<Menu.Item key="1" className={css.first} onClick={onFirst}>item 1</Menu.Item>
<Menu.Item key="2" className={css.second} onClick={onSecond}>item 2</Menu.Item>
</Menu>;
Ant Design v4.21.7 you can convert it like that
import React, { useState } from 'react';
import 'antd/dist/antd.css';
import './index.css';
import { Menu } from 'antd';
const App = () => {
const [current, setCurrent] = useState();
const onFirst = (e) => {
console.log('click 1', e);
setCurrent(e.key);
};
const onSecond = (e) => {
console.log('click 2', e);
setCurrent(e.key);
};
const items = [
{
label: 'Navigation One',
children: [
{
label: 'item 1',
onClick: onFirst,
key: '1',
className: 'first',
},
{
label: 'item 2',
onClick: onSecond,
key: '2',
className: 'second',
},
],
},
];
return <Menu selectedKeys={[current]} mode="horizontal" items={items} />;
};
export default App;

Resources