Change selected value component in react-select - reactjs

Is there any way to change the selected value component design, At my option menu, I show CscId and CscDesc but when I select the option, I only want to show CscId only. Is there any way to change the selected value component? I google for this one and it already took 1 day. Please Help me.
Here is my react-select
import React from "react";
import Select from 'react-select';
const costcenterselect = ({ value, onChange, id, datasource }) => {
const formatOptionLabel = ({ CscID, CscDesc }) => (
<div style={{ display: "flex"}}>
<div style={{width:'40%'}}>{CscID}</div>
<div>{CscDesc}</div>
</div>
);
return (
<div>
<Select
id={id}
menuIsOpen={true}
formatOptionLabel={formatOptionLabel}
getOptionValue={option => `${option.CscID}`}
options={datasource}
onChange={onChange}
defaultValue={value}
/>
</div>
)
}
export default costcenterselect;

You can do it using formatOptionLabel itself. It has a second argument which provides you with meta information like context which you can use to conditionally render. Here is a working demo.
You can see that context === value allows you to render for selected value while context === menu renders for the options.
const formatOptionLabel = ({ CscID, CscDesc }, { context }) => {
if (context === "value") {
return <div>{CscID}</div>;
} else if (context === "menu") {
return (
<div style={{ display: "flex" }}>
<div style={{ width: "40%" }}>{CscID}</div>
<div>{CscDesc}</div>
</div>
);
}
};

Related

React.js toggle faq type style

Just wanted to implement toggling faq type style. I have tried couple different ways but I can't seem to get it right. Basically i want to be able to expand the paragraph when the button is click. The paragraphs should expand one at a time. The code below is able to expand and close the paragraph one at a time as well but if you click on different button and there is an open paragraph, it just closes the first paragraph instead of opening the other paragraph.
Here is a link to sandbox as well: https://codesandbox.io/s/pensive-lichterman-nmjpy?file=/src/App.js:0-886
import { useState } from "react";
import "./styles.css";
import { info } from "./data";
export default function App() {
const [itemId, setItemId] = useState(null);
const [expand, setExpand] = useState(false);
const handleClick = (id) => {
setItemId(id);
setExpand((preState) => !preState);
};
return (
<div className="details">
{info.map(({ title, details, id }, i) => {
return (
<div key={id} className="details-wrapper">
<div>
<h3 className="title">{title}</h3>
<button onClick={() => handleClick(id)}>+</button>
</div>
<p
className="text"
style={{
display: itemId === id && expand ? "block" : "none"
}}
>
{details}
</p>
</div>
);
})}
</div>
);
}
You are only keeping a single id and expand state. So no matter what you do, when the page is re-renedered onClick it sees a single id and only sets the display style for that id.
If you wish to control each element separately then they each need their own expand state saved. There are several ways to do this, but it's probably best to create an "Expandable" component that saves its own state. Something like this:
https://codesandbox.io/s/polished-rain-xnez0?file=/src/App.js
Pretty much, I create a state for each paragraph. That’s what you need to create individual components with their own state.
export default function App() {
return (
<div className="details">
{info.map(({ title, details, id }, i) => {
return <ItemParagragh key={id} title={title} details={details} />;
})}
</div>
);
}
const ItemParagragh = ({ title, details }) => {
const [expand, setExpand] = useState(false);
const handleClick = () => {
setExpand((preState) => !preState);
};
return (
<div className="details-wrapper">
<div>
<h3 className="title">{title}</h3>
<button onClick={() => handleClick()}>+</button>
</div>
<p
className="text"
style={{
display: expand ? "block" : "none"
}}
>
{details}
</p>
</div>
);
};

How to make a React dropdown to render the array the user clicks on AND show "nothing found here!" if array is empty?

I'm trying to figure out a better way to render the array of objects of this Dropdown.
I can currently see all arrays correctly but, for example, if captured or released arrays are empty, I get an empty screen. I tried setting up the ternary operator such as "value === capturedPkm && capturedPkm === '' ? Nothing found here : capturedPkm.map() but then I would need another : to show 'released' or 'all' and that's not how the ternary operator works. How can I make it so to show Nothing to be found here if the array are empty AND show another component when the user clicks on it? I hope I made myself clear enough. If not, please tell me so!
import React, { useState } from "react";
import PokemonCard from "./PokemonCard";
const Dropdown = ({
pokemons,
capturedPkm,
releasedPkm,
setCapturedPkm,
setReleasedPkm,
}) => {
const [value, setValue] = useState([pokemons]);
const handleChange = (e) => setValue(e.target.value);
return (
<div className="select">
<select name={JSON.stringify(value)} onChange={handleChange}>
<option value={JSON.stringify(pokemons)}>All</option>
<option value={JSON.stringify(capturedPkm)}>Captured</option>
<option value={JSON.stringify(releasedPkm)}>Released</option>
</select>
<div
style={{
display: "flex",
flexWrap: "wrap",
justifyContent: "space-evenly",
}}
>
{value === JSON.stringify(capturedPkm)
? capturedPkm.map((el, i) => {
return (
<div>
<img src={el.img} alt={"pokemon"} />
{el.name}
</div>
);
})
: value === JSON.stringify(releasedPkm)
? releasedPkm.map((el, i) => {
return <div>{el.name}</div>;
})
: pokemons.map((pokemon, index) => {
return (
<div style={{ width: "235px" }} key={index}>
<PokemonCard
pokemon={pokemon}
index={index}
name={pokemon.name}
capturedPkm={capturedPkm}
setCapturedPkm={setCapturedPkm}
notCapturedPkm={releasedPkm}
setNotCapturedPkm={setReleasedPkm}
/>
</div>
);
})}
</div>
</div>
);
};
export default Dropdown;
Just in case this turns out to be useful for anybody, I've just added a new condition in case of empty array, and it works perfectly. I will upload my own question (LOL) with edited and refactored code.

react-select focus() doesn't show cursor after change

As described in the official documentation for react-select, I'm trying to use ref and focus() to manually set the focus into the control input field. In most instances it works, but not immediately after selecting an Option from the dropdown.
After selecting an option from the dropdown, the control gets the focus but the cursor doesn't appear. It only appears if you start typing (including hitting the Esc key). On subsequent openings of the menu, the cursor appears along with the focus of the entire control field. Any ideas how to get this working?
I've created a sample code in codesandbox.io here
This is the code:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
import styled from "styled-components";
import { stateOptions } from "./data.js";
class PopoutExample extends Component {
selectRef = React.createRef();
state = {
isOpen: false,
option: undefined,
};
toggleOpen = () => {
const isOpening = !this.state.isOpen;
this.setState(
{
isOpen: isOpening,
},
() => isOpening && setTimeout(() => this.selectRef.focus(), 400),
);
};
onSelectChange = option => {
this.toggleOpen();
this.setState({ option });
};
render() {
const { isOpen, option } = this.state;
return (
<Dropdown
target={
<MainButton onClick={this.toggleOpen}>
{option ? option.label : "Select a State"}
</MainButton>
}
>
<Select
menuIsOpen
ref={ref => {
this.selectRef = ref;
}}
styles={{
container: provided => ({
...provided,
display: isOpen ? "block" : "none",
}),
}}
onChange={this.onSelectChange}
options={stateOptions}
value={option}
controlShouldRenderValue={false}
/>
</Dropdown>
);
}
}
const MainButton = styled.button`
padding: 10px;
background-color: aqua;
width: 100%;
`;
const Dropdown = ({ children, target }) => (
<div>
{target}
{children}
</div>
);
ReactDOM.render(<PopoutExample />, document.getElementById("root"));
You can notice that the bug also exists in the official react-select examples. Even clicking on the blur button after the selection is not solving the problem.
There's probably a small different in the code when user closes the menu and saves + automatically closes action.
I saw you've opened an issue on github. Let's keep an eye on it.
If I can offer an alternative to the behaviour you're trying to achieve, instead of hiding the Select with css why don't just mount / unmount it ?
class PopoutExample extends Component {
state = {
isOpen: false,
option: undefined
};
toggleOpen = () => {
this.setState({
isOpen: !this.state.isOpen
});
};
onSelectChange = option => {
this.setState({ option, isOpen: !this.state.isOpen });
};
render() {
const { isOpen, option } = this.state;
return (
<Dropdown
target={
<MainButton onClick={this.toggleOpen}>
{option ? option.label : "Select a State"}
</MainButton>
}
>
{isOpen && (
<Select
autoFocus
menuIsOpen
onChange={this.onSelectChange}
options={stateOptions}
value={option}
controlShouldRenderValue={false}
/>
)}
</Dropdown>
);
}
}
Here a live example of my solution.

How to create fully controlled dropdown in semantic-ui-react

I want to create a fully controlled dropdown in order to use react-window to show really long list of items in it.
I've checked docs, and there is no any example of controlled dropdown with Dropdown.Item specified.
This is how my component looks like:
<Dropdown
placeholder="Filter Posts"
clearable={true}
search={true}
onChange={this.handleChange}
text={tagOptions[1].value}
value={tagOptions[1].value}
onSearchChange={this.handleChange}
>
<Dropdown.Menu>
{tagOptions.map(option => (
<Dropdown.Item key={option.value} {...option} onClick={this.handleItemClick} />
))}
</Dropdown.Menu>
</Dropdown>;
I've encounter with 2 issues:
Initial value is not appears, I dig into the code, and saw that if i don't pass options property it won't find the given value, therefore, it will not be shown. I can use the text property, but it seems like a hack.
I need to implement handleItemClick by myself, and I see that there is logic in the original handleItemClick.
Any suggestions? did I missed something here?
I've able to hack it around with using ref on the dropdown and passing the original handleItemClick method.
The only downside for now is that keyboard nav is not works :\
Seem like it was not designed to be fully controlled.
https://codesandbox.io/s/ql3q086l5q
The dropdown module simply doesn't have support for controlling it's inner components, that being said this is the closest I've gotten to a controlled dropdown with react-window support. I'm posting it here for anyone in the future that wants a select dropdown with virtualisation without a headache.
VirtualisedDropdown.js
import React, { forwardRef, useCallback, useRef, useState } from "react"
import { Dropdown, Ref } from "semantic-ui-react"
import { FixedSizeList } from "react-window"
import "./VirtualisedDropdown.scss"
const SUI_DROPDOWN_MENU_HEIGHT = 300
const SUI_DROPDOWN_MENU_ITEM_HEIGHT = 37
const VirtualisedDropdown = ({
options, value,
...restProps
}) => {
const dropdownRef = useRef()
const listRef = useRef()
const [open, setOpen] = useState(false)
const OuterDiv = useCallback(({ style, ...props }, ref) => {
const { position, overflow, ...restStyle } = style
return (
<Ref innerRef={ref}>
<Dropdown.Menu open={open} {...props} style={restStyle}>
{props.children}
</Dropdown.Menu>
</Ref>
)
}, [open])
const InnerDiv = useCallback(props => {
return (
<Dropdown.Menu className="inner" open={open} style={{ ...props.style, maxHeight: props.style.height }}>
{props.children}
</Dropdown.Menu>
)
}, [open])
return (
<Dropdown
className="virtualised selection"
onClose={() => setOpen(false)}
onOpen={() => {
setOpen(true)
listRef.current.scrollToItem(options.findIndex(i => i.value === value))
}}
// This causes "Warning: Failed prop type: Prop `children` in `Dropdown` conflicts with props: `options`. They cannot be defined together, choose one or the other."
// but is necessary for some logic to work e.g. the selected item text.
options={options}
ref={dropdownRef}
selectOnNavigation={false}
value={value}
{...restProps}
>
<FixedSizeList
height={options.length * SUI_DROPDOWN_MENU_ITEM_HEIGHT < SUI_DROPDOWN_MENU_HEIGHT ? options.length * SUI_DROPDOWN_MENU_ITEM_HEIGHT + 1 : SUI_DROPDOWN_MENU_HEIGHT}
innerElementType={InnerDiv}
itemCount={options.length}
itemData={{
options,
handleClick: (_e, x) => dropdownRef.current.handleItemClick(_e, x),
selectedIndex: options.findIndex(i => i.value === value),
}}
itemSize={SUI_DROPDOWN_MENU_ITEM_HEIGHT}
outerElementType={forwardRef(OuterDiv)}
ref={listRef}
>
{Row}
</FixedSizeList>
</Dropdown>
)
}
const Row = ({ index, style, data }) => {
const { options, handleClick, selectedIndex } = data
const item = options[index]
return (
<Dropdown.Item
active={index === selectedIndex}
className="ellipsis"
key={item.value}
onClick={handleClick}
selected={index === selectedIndex}
style={style}
title={item.text}
{...item}
/>
)
}
export default VirtualisedDropdown
VirtualisedDropdown.scss
.ui.dropdown.virtualised .menu {
&.inner {
margin: 0 -1px !important;
left: 0;
overflow: initial;
border-radius: 0 !important;
border: 0;
}
> .item {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
To solve first problem remove clearable={true} and text={tagOptions[1].value}
What handleItemClick function should do?

How to pass value from custom component class to redux-form?

I'm calling a custom component in my redux-form.
<Field name="myField" component={SiteProjectSelect}/>
This component is a combination of two combo boxes. The second box is dependant on the value of the first on - i.e. depending on what site you select, you can choose from a list of projects. What I'd like to do is get the form to receive the selected site and the selected projects. However, I'm not sure how to pass the values to the redux-form.
class SiteProjectSelect extends Component {
constructor() {
super();
this.state = {
selectedSite: null,
selectedProject: null,
};
}
handleSiteSelection = selectedSite => {
console.log(selectedSite)
this.setState({ selectedSite, selectedProject: null });
};
handleProjectSelection = selectedProject => {
this.setState({ selectedProject });
this.props.input.onChange(selectedProject.value);
};
render() {
const selectedRow = this.state.selectedSite ? projects.find((node) => node.site === this.state.selectedSite.value) : "";
const filteredProjectOptions = selectedRow ? selectedRow.projects.map(project => ({ value: project, label: project })) : []
return (
<div {...this.props} >
<label>Site</label>
<div style={{ marginBottom: '20px' }} >
<Select
name="site"
value={this.state.selectedSite}
onChange={this.handleSiteSelection}
options={siteOptions}
isSearchable
/>
</div>
<div style={{ marginBottom: '20px' }} >
<label>Project</label>
<Select
name="project"
value={this.state.selectedProject}
onChange={this.handleProjectSelection}
options={filteredProjectOptions}
isMulti
isSearchable
closeMenuOnSelect={false}
/>
</div>
</div>
);
}
}
I did finally figure it out. For anyone else who stumbles across this, here's what I needed to know. To use a custom component,
Use the onChange prop to set the new value of the Field. You do this by calling the onChange function, this.props.input.onChange(your-components-new-value-here) when you need to change the value of the component and passing it the new value.
This new value will now be stored in the value prop: this.props.input.value. So, wherever in the render function for your component you need to pass/display the current value of your component, use the value prop. It has to be the value prop and not another variable such as what you passed to your onChange function. What this does is give control of what's displayed to the state of your redux-form which the value prop is tied to. Why is this useful? For example, you could take the user to a form review page when they're done and then back to the form if the user wants to make some more changes. How would redux-form know how to repopulate all of what's displayed without getting the user to fill in the form again? Because the display is dependant on the state, not user input! Took me a while to make sense of all this!!
In my example, where I was using two react-select components, one of which was dependant on the other, I ended up having to use the Fields component which allowed me to have two Fields in my component rather than just the one. Once I implemented this, it also became evident that I didn't need to have a separate state within my component as the value of both Fields is always accessible via the value prop for each of them. So, yes, I could have just used a stateless function after all!
I call my component with:
<Fields names={["site", "projects"]} component={SiteProjectSelect} />
My final working component:
class SiteProjectSelect extends Component {
handleSiteSelection = selectedSite => {
this.props.site.input.onChange(selectedSite);
this.props.projects.input.onChange(null);
};
handleProjectSelection = selectedProjects => {
this.props.projects.input.onChange(selectedProjects);
};
renderSite = () => {
const {
input: { value },
meta: { error, touched }
} = this.props.site;
return (
<div>
<label>Site</label>
<div style={{ marginBottom: '20px' }}>
<Select
name="site"
value={value}
onChange={this.handleSiteSelection}
options={siteOptions}
isSearchable
/>
</div>
<div className="red-text" style={{ marginBottom: '20px' }}>
{touched && error}
</div>
</div>
);
};
renderProjects = () => {
var {
input: { value },
meta: { error, touched }
} = this.props.projects;
const selectedSite = this.props.site.input.value;
const selectedRow = selectedSite
? projects.find(node => node.site === selectedSite.value)
: '';
const filteredProjectOptions = selectedRow
? selectedRow.projects.map(project => ({
value: project,
label: project
}))
: [];
return (
<div>
<div style={{ marginBottom: '20px' }}>
<label>Projects</label>
<Select
name="projects"
value={value}
onChange={this.handleProjectSelection}
options={filteredProjectOptions}
isMulti
isSearchable
closeMenuOnSelect={false}
/>
</div>
<div className="red-text" style={{ marginBottom: '20px' }}>
{touched && error}
</div>
</div>
);
};
render() {
return (
<div>
{this.renderSite()}
{this.renderProjects()}
</div>
);
}
}

Resources