Semantic UI react - implement popup on dropdown select - javascript

I have a dropdown with a few options using semantic-ui-react. I want to be able to show the user a brief description on the choice they made after selection from the dropdown. Semantic also has a <Popup/> module that I've been trying to use along with <Dropdown/> to make this work.
I'm looking through the prop list of the dropdown module and don't see anything in particular that fits my case. I've tried using dropdown inside of popup, but with no luck.
Sandbox with the example: https://codesandbox.io/s/5zo52qyrxk
import React from "react";
import ReactDOM from "react-dom";
import { Dropdown, Popup, Input } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import "./styles.css";
const offsetOptions = [
{
text: "fromEarliest",
value: "fromEarliest"
},
{
text: "fromLatest",
value: "fromLatest"
}
];
const DropdownExample = () => (
<Dropdown
placeholder="Select offset position"
clearable
fluid
selection
options={offsetOptions}
header=" Something about offset "
/>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
offset: ""
};
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div>
<fieldset>
<h1> Implement Popup on this Dropdown - semantic ui </h1>
<DropdownExample />
</fieldset>
</div>
</form>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

If you're trying to show a popup on each dropdown option, then you can use the subcomponent API to create the dropdown options rather than using the options prop.
<Dropdown text='File'>
<Dropdown.Menu>
<Popup trigger={<Dropdown.Item text='Close' description="Close" value="Close" />} content="Hello there" />
<Popup trigger={<Dropdown.Item text='Open' description='Open' value="Open" />} content="Open me"/>
{/* more options would go here... */}
</Dropdown.Menu>
</Dropdown>
There is a warning on the Semantic-UI-React site that states
Dropdown state is not fully managed when using the subcomponent API. The shorthand props API fully manages state but needs to be extended to support the markup shown here.
So I would take this suggestion with a grain of salt, but it does seem to accomplish what you're looking for.

Here is a solution that allows you to use a <Popup/> inside your <Dropdown.Item/> while still using the shorthand props like options, value and onChange:
// Main component
const DropdownWithPopups = ({ className, options, value, onChange, ...props }) => {
return (
<Dropdown
className='dropdown-with-popups'
selection
options={options}
value={value}
onChange={onChange}
{...props}
/>
);
};
// Popup that will be inside the default <Item/>
const ItemPopup = ({ text, popupContent }) => {
return (
<Popup
basic
position='left center'
hoverable
pinned
trigger={(
<div className='item-popup-trigger'>
{text}
</div>
)}
content={(
<div className='item-popup-content'>
{popupContent}
</div>
)}
popperModifiers={{
preventOverflow: {
boundariesElement: 'window',
},
}}
/>
);
};
// What your options should look like
const getOptions = (optionsData) => {
return _.map(optionsData, (option) => {
return {
value: option.value,
// `text` so the selected option is displayed
text: option.text,
// `children`is incompatible with `text` prop, so Semantic UI React will throw a warning,
// but it works as expected
children: (
<ItemPopup
text={option.text}
// whatever you need to render inside your Popup
popupContent={...}
/>
),
};
});
};
However I only tested this on an old version of semantic-ui-react (v0.88.2)

Actually there is another way to render items of dropdown with popups with using additional parameter content during data mapping
For example you have some received data to place in dropdown, then map of options would be:
const options = data.map(elem => ({
key: elem.id,
text: elem.name,
value: elem.name,
content: (
<Popup
content={ elem.example }
trigger={ <div>{ elem.name }</div> } />
),
})
this.setState({
dropdown: {
options: options
}
})
Then pass options as <Dropdown /> parameter:
<Dropdown
fluid
selection
placeholder="Data"
options={ this.state.dropdown.options }
value={ this.state.dropdown.selected } />

Related

Hide Child Component Element After onClick using ReactJS

I'm new to React, learning by coding, here i have component A, which has select element with menuItems (all material ui), when user clicks select element and chooses from drop down, right after user has chosen whole component should go display:none, is this possible ? i mean user should not be able to see select element anymore on the page
English is not my mother language, so there might be mistakes.
suggestions/help is appreciated.
component A:
const A: React.FC<AProps> = (props) => {
const handleChange = (e: React.ChangeEvent<{ value: unknown }>) => {
const site = e.target.value as string;
dispatch(changeActiveSite(site));
if (site) {
dispatch(getAnalysers(site));
} else {
dispatch(clearSiteData(site));
}
};
const sites = [
{
ident: "",
name: "None",
},
].concat(sitess);
return (
<React.Fragment>
<FormControl className={classes.formControl}>
<InputLabel id="site-select-input-label">site</InputLabel>
<Select
id="site-select"
value={currentSiteId}
labelId="site-select-input-label"
onChange={(e) => handleChange(e)}
>
{sites.map((site) => {
return (
<MenuItem key={site.ident} value={site.ident}>
{site.name}
</MenuItem>
);
})}
</Select>
</FormControl>
</React.Fragment>
);
};
that component is in component B like this: <div > <A site={site} /> </div>
Let's imagine your MenuItem.js, specifically, its render() and constructor(). You'll want it to be able to be hidden/not-displayed, or visible/displayed. Use a state attribute for hidden to control this, your render will probably look like...
constructor(props) {
super(props);
this.state = {
'hidden':false,
};
}
render () {
if(this.state.hidden) {
return '';
}
return (
<div
onClick={(e) => this.handleOnClick(e)}
>
{this.props.value}
</div>
);
}
Notice I also added a handleOnClick(e) handler up above! That will simply call this.setState({'hidden':true}), so like...
handleOnClick(e) {
this.setState({'hidden':true});
}
I have an answer to a similar question elsewhere, if it might also help: How to set one component's state from another component in React

Input ReactJS with function component

I work on a reactJS code coded by a freelance (i'm new in reactJS), and I need to make the inputs functional.
But when I look at the tutorials and solutions on the internet, you have to use controlled components, they are always built with a class and not a function.
However all the components present in the project are built with functions.
Is it possible to make the inputs functional anyway? In order to have the output in a console.log see keep the data to use it on other pages?
See my Input component :
import React from 'react'
// Import styles
import './style.scss'
const BaseTextInput = ({
className = '',
id = '',
name = '',
size = 'normal',
type = 'text',
value = '',
placeholder = '',
disabled = false,
top = false,
onChange,
children,
...rest
}) => {
return (
<form>
<div
className={`text-input text-input__size-${size} ${top ? 'text-input__top' : ''} ${className}`}
{...rest}
>
{children && <label>{children}</label>}
<input
className="text-input__input"
id={id}
name={name}
type={type}
value={value}
placeholder={placeholder}
disabled={disabled}
onChange={onChange}
/>
</div>
</form>
)
}
export default BaseTextInput
Thank you, I hope I explained my problem clearly
EDIT 26/02/201 10h23:
Thank you for all, I understand how I can do it !!
I use your sample component to make a CodeSandbox for you, but I'm not sure if this is what you want.
import InputComponent from "./inputComponent";
import { useState } from "react";
export default function App() {
const [inputData, setInputData] = useState("");
const handleChange = (e) => {
const value = e.target.value;
setInputData(value);
};
return (
<div className="App">
Your input component
<InputComponent onChange={handleChange} value={inputData} />
<br />
Input value: <b>{inputData}</b>
</div>
);
}
Hope to help you !
I think onChange method of parent component is passed as a prop to this functional component , if you want you could make your onChange method for this component

React render list only when data source changes

Basically I have a modal with a state in the parent component and I have a component that renders a list. When I open the modal, I dont want the list to re render every time because there can be hundreds of items in the list its too expensive. I only want the list to render when the dataSource prop changes.
I also want to try to avoid using useMemo if possible. Im thinking maybe move the modal to a different container, im not sure.
If someone can please help it would be much appreciated. Here is the link to sandbox: https://codesandbox.io/s/rerender-reactmemo-rz6ss?file=/src/App.js
Since you said you want to avoid React.memo, I think the best approach would be to move the <Modal /> component to another "module"
export default function App() {
return (
<>
<Another list={list} />
<List dataSource={list} />
</>
);
}
And inside <Another /> component you would have you <Modal />:
import React, { useState } from "react";
import { Modal } from "antd";
const Another = ({ list }) => {
const [showModal, setShowModal] = useState(false);
return (
<div>
<Modal
visible={showModal}
onCancel={() => setShowModal(false)}
onOk={() => {
list.push({ name: "drink" });
setShowModal(false);
}}
/>
<button onClick={() => setShowModal(true)}>Show Modal</button>
</div>
)
}
export default Another
Now the list don't rerender when you open the Modal
You can use React.memo, for more information about it please check reactmemo
const List = React.memo(({ dataSource, loading }) => {
console.log("render list");
return (
<div>
{dataSource.map((i) => {
return <div>{i.name}</div>;
})}
</div>
);
});
sandbox here

Send searchParam data from one Component to another component in reactjs

I have one Component which shows a list of data in a dropdown and there is an option to search these data which works as a filter. Here is my code:
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Popover from '../../Popover';
import Input from '../../Input';
import Icon from '../../Icon';
import IconButton from '../../IconButton';
const DropDownFilter = props => {
const { label, options, onChange, isSearchEnabled } = props;
const [activeOption, setActiveOption] = useState({});
const [filter, setfilter] = useState('');
const searchFilter = event => {
setfilter(event.target.value);
};
const removeFilter = () => {
setfilter('');
};
const lowercasedFilter = filter.toLowerCase();
const filteredData = options.filter(item => {
return Object.keys(item).some(
key => typeof item[key] === 'string' && item[key].toLowerCase().includes(lowercasedFilter)
);
});
const labelText = activeOption.label ? activeOption.label : label;
const handleSelectedOption = option => {
setActiveOption(option);
onChange(option);
};
return (
<div className="filter">
<Popover linkText={labelText} size="small" direction="bottom-left">
{isSearchEnabled && (
<div className="filter__search">
<Input
value={filter}
onChange={searchFilter}
preIcon={
<div role="presentation">
<Icon name="search" />
</div>
}
placeholder="Search"
postIcon={
filter.length > 0 && (
<IconButton
icon={<Icon name="close" />}
size="tiny"
onClick={removeFilter}
standalone={true}
isIconOnly={true}
/>
)
}
/>
</div>
)}
<ul className="filter__options filter__options--scrollbar">
{filteredData.map(option => (
<li
key={option.value}
role="presentation"
className={classNames('filter__options-option', {
'filter__options-option--active': option.value === activeOption.value,
})}
onClick={() => handleSelectedOption(option)}
>
{option.label}
</li>
))}
</ul>
</Popover>
</div>
);
};
DropDownFilter.defaultProps = {
label: 'Filter Menu',
options: [],
isSearchEnabled: true,
};
DropDownFilter.propTypes = {
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
options: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
})
),
onChange: PropTypes.func.isRequired,
isSearchEnabled: PropTypes.bool,
};
export default DropDownFilter;
Here is a gif of it: https://recordit.co/HtalUtuPsj
Now during searching I want to send the value of the search param to another component, the value will be used to search from a DB or any other external data source which is being handled in that new component. Such as, if I am searching for Ratings, this component should search for it in the existing options list it has in its own component, as well as the same time it will search for Ratings in any other external data source or DB. This external network call, search or any other functionality will be processed in the other component. So this component will only send the search param; for example Ratings to the other component in real time.
I can think of an idea like I will get the searchParam in a state and pass the setState value to a new props which will be called through an onSearchParamChange function, this new function will pass the data through a callback and the other component will get the data through calling that props of this component. I am not sure if this is the correct way and also I am not able to implement this thought in the code either. Is there any better way to do it? if so what would be that coding implementation?
If you need to pass to a parent component you should be able to use for example the onChange prop which is passed to your component, like you are doing in the handleSelectedOption function. That function is in fact passing the chosen option to the parent component. If you want to pass to the parent component when the user is typing, then you should call the onChange function also in searchFilter:
const searchFilter = event => {
const option = event.target.value);
setfilter(option);
onChange(option);
};
If you want to pass it to a child component, the you can just pass it as prop:
<ChildComponent filter={ filter } />

Material UI + React Form Hook + multiple checkboxes + default selected

I am trying to build a form that accommodates multiple 'grouped' checkboxes using react-form-hook Material UI.
The checkboxes are created async from an HTTP Request.
I want to provide an array of the objects IDs as the default values:
defaultValues: { boat_ids: trip?.boats.map(boat => boat.id.toString()) || [] }
Also, when I select or deselect a checkbox, I want to add/remove the ID of the object to the values of react-hook-form.
ie. (boat_ids: [25, 29, 4])
How can I achieve that?
Here is a sample that I am trying to reproduce the issue.
Bonus point, validation of minimum selected checkboxes using Yup
boat_ids: Yup.array() .min(2, "")
I've been struggling with this as well, here is what worked for me.
Updated solution for react-hook-form v6, it can also be done without useState(sandbox link below):
import React, { useState } from "react";
import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Checkbox from "#material-ui/core/Checkbox";
export default function CheckboxesGroup() {
const defaultNames = ["bill", "Manos"];
const { control, handleSubmit } = useForm({
defaultValues: { names: defaultNames }
});
const [checkedValues, setCheckedValues] = useState(defaultNames);
function handleSelect(checkedName) {
const newNames = checkedValues?.includes(checkedName)
? checkedValues?.filter(name => name !== checkedName)
: [...(checkedValues ?? []), checkedName];
setCheckedValues(newNames);
return newNames;
}
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
{["bill", "luo", "Manos", "user120242"].map(name => (
<FormControlLabel
control={
<Controller
name="names"
render={({ onChange: onCheckChange }) => {
return (
<Checkbox
checked={checkedValues.includes(name)}
onChange={() => onCheckChange(handleSelect(name))}
/>
);
}}
control={control}
/>
}
key={name}
label={name}
/>
))}
<button>Submit</button>
</form>
);
}
Codesandbox link: https://codesandbox.io/s/material-demo-54nvi?file=/demo.js
Another solution with default selected items done without useState:
https://codesandbox.io/s/material-demo-bzj4i?file=/demo.js
Breaking API changes made in 6.X:
validation option has been changed to use a resolver function wrapper and a different configuration property name
Note: Docs were just fixed for validationResolver->resolver, and code examples for validation in repo haven't been updated yet (still uses validationSchema for tests). It feels as if they aren't sure what they want to do with the code there, and it is in a state of limbo. I would avoid their Controller entirely until it settles down, or use Controller as a thin wrapper for your own form Controller HOC, which appears to be the direction they want to go in.
see official sandbox demo and the unexpected behavior of "false" value as a string of the Checkbox for reference
import { yupResolver } from "#hookform/resolvers";
const { register, handleSubmit, control, getValues, setValue } = useForm({
resolver: yupResolver(schema),
defaultValues: Object.fromEntries(
boats.map((boat, i) => [
`boat_ids[${i}]`,
preselectedBoats.some(p => p.id === boats[i].id)
])
)
});
Controller no longer handles Checkbox natively (type="checkbox"), or to better put it, handles values incorrectly. It does not detect boolean values for checkboxes, and tries to cast it to a string value. You have a few choices:
Don't use Controller. Use uncontrolled inputs
Use the new render prop to use a custom render function for your Checkbox and add a setValue hook
Use Controller like a form controller HOC and control all the inputs manually
Examples avoiding the use of Controller:
https://codesandbox.io/s/optimistic-paper-h39lq
https://codesandbox.io/s/silent-mountain-wdiov
Same as first original example but using yupResolver wrapper
Description for 5.X:
Here is a simplified example that doesn't require Controller. Uncontrolled is the recommendation in the docs. It is still recommended that you give each input its own name and transform/filter on the data to remove unchecked values, such as with yup and validatorSchema in the latter example, but for the purpose of your example, using the same name causes the values to be added to an array that fits your requirements.
https://codesandbox.io/s/practical-dijkstra-f1yox
Anyways, the problem is that your defaultValues doesn't match the structure of your checkboxes. It should be {[name]: boolean}, where names as generated is the literal string boat_ids[${boat.id}], until it passes through the uncontrolled form inputs which bunch up the values into one array. eg: form_input1[0] form_input1[1] emits form_input1 == [value1, value2]
https://codesandbox.io/s/determined-paper-qb0lf
Builds defaultValues: { "boat_ids[0]": false, "boat_ids[1]": true ... }
Controller expects boolean values for toggling checkbox values and as the default values it will feed to the checkboxes.
const { register, handleSubmit, control, getValues, setValue } = useForm({
validationSchema: schema,
defaultValues: Object.fromEntries(
preselectedBoats.map(boat => [`boat_ids[${boat.id}]`, true])
)
});
Schema used for the validationSchema, that verifies there are at least 2 chosen as well as transforms the data to the desired schema before sending it to onSubmit. It filters out false values, so you get an array of string ids:
const schema = Yup.object().shape({
boat_ids: Yup.array()
.transform(function(o, obj) {
return Object.keys(obj).filter(k => obj[k]);
})
.min(2, "")
});
Here is a working version:
import React from "react";
import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Checkbox from "#material-ui/core/Checkbox";
export default function CheckboxesGroup() {
const { control, handleSubmit } = useForm({
defaultValues: {
bill: "bill",
luo: ""
}
});
return (
<form onSubmit={handleSubmit(e => console.log(e))}>
{["bill", "luo"].map(name => (
<Controller
key={name}
name={name}
as={
<FormControlLabel
control={<Checkbox value={name} />}
label={name}
/>
}
valueName="checked"
type="checkbox"
onChange={([e]) => {
return e.target.checked ? e.target.value : "";
}}
control={control}
/>
))}
<button>Submit</button>
</form>
);
}
codesandbox link: https://codesandbox.io/s/material-demo-65rjy?file=/demo.js:0-932
However, I do not recommend doing so, because Checkbox in material UI probably should return checked (boolean) instead of (value).
Here's my solution, which is not using all the default components from Material UI cause at my interface each radio will have an icon and text, besides the default bullet point not be showed:
const COMPANY = "company";
const INDIVIDUAL = "individual";
const [scope, setScope] = useState(context.scope || COMPANY);
const handleChange = (event) => {
event.preventDefault();
setScope(event.target.value);
};
<Controller
as={
<FormControl component="fieldset">
<RadioGroup
aria-label="scope"
name="scope"
value={scope}
onChange={handleChange}
>
<FormLabel>
{/* Icon from MUI */}
<Business />
<Radio value={COMPANY} />
<Typography variant="body1">Company</Typography>
</FormLabel>
<FormLabel>
{/* Icon from MUI */}
<Personal />
<Radio value={INDIVIDUAL} />
<Typography variant="body1">Individual</Typography>
</FormLabel>
</RadioGroup>
</FormControl>
}
name="scope"
control={methods.control}
/>;
Observation: At this example I use React Hook Form without destruct:
const methods = useForm({...})
This is my solution with react hook form 7, the other solutions don't work with reset or setValue.
<Controller
name={"test"}
control={control}
render={({ field }) => (
<FormControl>
<FormLabel id={"test"}>{"label"}</FormLabel>
<FormGroup>
{items.map((item, index) => {
const value = Object.values(item);
return (
<FormControlLabel
key={index}
control={
<Checkbox
checked={field.value.includes(value[0])}
onChange={() =>
field.onChange(handleSelect(value[0],field.value))
}
size="small"
/>
}
label={value[1]}
/>
);
})}
</FormGroup>
</FormControl>
)}
/>
link to codesandbox: Mui multiple checkbox

Categories

Resources