Add elements to Blueprint Select component popover? - reactjs

Blueprint's Select component is exactly what I need for my current React project, with one exception: I need to add some elements to its popover and don't see any way to do it.
Specifically, I'd like to add a title (e.g. an H2 element) above the filter input, and a bar of buttons (e.g., some Button components in a DIV) below the list. Select seems highly configurable but I see no way to add elements inside the popover...what am I missing?

If you want to extend the select's Menu and add custom elements to it, then you have to provide the itemListRenderer prop.
Here's what the docs says:
By default, Select renders the displayed items in a Menu. This
behavior can be overridden by providing the itemListRenderer prop,
giving you full control over the layout of the items. For example, you
can group items under a common heading, or render large data sets
using react-virtualized.
itemListRenderer example:
If provided, the itemListRenderer prop will be called to render the contents of the dropdown menu. It has access to the items, the current query, and a renderItem callback for rendering a single item. A ref handler (itemsParentRef) is given as well; it should be attached to the parent element of the rendered menu items so that the currently selected item can be scrolled into view automatically.
Therefore in the Menu component's body you can place your custom headings and buttons:
import { ItemListRenderer } from "#blueprintjs/select";
const renderMenu: ItemListRenderer<Film> = ({ items, itemsParentRef, query, renderItem }) => {
const renderedItems = items.map(renderItem).filter(item => item != null);
return (
<Menu ulRef={itemsParentRef}>
<h2>Your heading can be styled here</h2>
<MenuItem
disabled={true}
text={`Found ${renderedItems.length} items matching "${query}"`}
/>
{renderedItems}
<div>
<button>Button name</button>
</div>
</Menu>
);
};
<FilmSelect
itemListRenderer={renderMenu}
itemPredicate={filterFilm}
itemRenderer={renderFilm}
items={...}
onItemSelect={...}
/>

Jordan's suggestions above, plus a little experimentation, ultimately yielded a workable answer:
Set filterable to false to hide the built-in filter input.
Use itemListRenderer to render not only the dropdown items, but also an InputGroup to serve as a replacement filter.
Use InputGroup's inputRef prop to capture a ref to the underlying HTML input. Use that to focus the input when it appears, via the onOpening property of Select's popoverProps prop.
Here's a simple component implementing the above:
// Extends Blueprint's Select component with header and footer props that
// can be any arbitrary elements or components
class ExtendedSelect extends Component {
constructor(props) {
super(props);
this.inputRef = null;
this.state = {query: ""};
}
handleInputChanged = event => {
this.setState({query: event.target.value});
}
receiveInputRef = (ref) => {
this.inputRef = ref;
}
handlePopoverOpening = () => {
if (this.inputRef) {
this.inputRef.focus();
}
}
listRenderer = ({filteredItems, renderItem}) => {
// Apply the supplied item renderer to the filtered list of items
const renderedItems = filteredItems.map(renderItem);
return (
<div>
{this.props.header}
<InputGroup inputRef={this.receiveInputRef} value={this.state.query} onChange={this.handleInputChanged} leftIcon="search" />
<Menu>
{renderedItems}
</Menu>
{this.props.footer}
</div>
);
}
render() {
return (
<Select
items={this.props.items}
filterable={false}
query={this.state.query}
itemListRenderer={this.listRenderer}
itemPredicate={this.props.itemPredicate}
itemRenderer={this.props.itemRenderer}
popoverProps={{onOpening:this.handlePopoverOpening}}
onItemSelect={this.props.onItemSelect}
>
{this.props.children}
</Select>
);
}
}
(Note that I'm only passing some of Select's props into the custom component—I suspect I'd know a way to pass them all were I a more experienced React developer.)
This works surprisingly well—e.g., other than a little work to focus the input when it appears, all of Select's other built-in behavior works as expected, like keyboard navigation of the menu while the input is focused.

Related

How to get previous clicked element on any click event in Reactjs?

I am trying to make an single selection in my app. I have a grid and inside it some images. When I clicked an image its parent which is a div background color change to red. I describe this event it is selected. I want to make deselect previous item when click any where. if clicked image is one of my item then new item should be selected and previous should be unselected. if clicked element is not one of my items just previus should be unselected. When I select an item then clicked one of the others it is working. However when I selected an item then click out of my items it is not working. In this stuation my prevClickedElement parameter refers to previous of previous clicked element. And I have no idea why state empty when update it in clickElement in context and in removePrevSelectedSquare in Piece component. Because of my app has multiple components and images I added to here. I am not sure this is the correct way. Because when I select an item component created items count times in this case 15. This is a performance problem?
I believe a good approach would be creating a component global state to save the current element unique id and creating a function to set the element id when it is clicked. After that you can create a function that reset this state whenever it is called and implement this function as an DOM event in a superior div, the parent of the images. Here is the ideia in form of a code snippet:
import React from 'react';
function Stackoverflow(props) {
const [selectedPicture, setSelectedPicture] = React.useState(null);
function resetSelectedPicture() {
setSelectedPicture(null)
}
return (
<div onClick={resetSelectedPicture}>
{pictures.map((picture, index) => {
const {id, src} = picture;
return (
<>
<img
src={src}
key={index}
id={id}
className={
selectedPicture.id === id
? 'selected-picture'
: 'unselected-picture'
}
onClick={setSelectedPicture(
() => picture
)}
/>
</>
);
})}
</div>
);
}
export default Stackoverflow;

React Button Click Hiding and Showing Components

I have a toggle button that show and hides text. When the button is clicked I want it to hide another component and if clicked again it shows it.
I have created a repl here:
https://repl.it/repls/DapperExtrasmallOpposites
I want to keep the original show / hide text but I also want to hide an additional component when the button is clicked.
How to I pass that state or how do I create an if statement / ternary operator to test if it is in show or hide state.
All makes sense in the repl above!
To accomplish this you should take the state a bit higher. It would be possible to propagate the state changes from the toggle component to the parent and then use it in any way, but this would not be the preferred way to go.
If you put the state in the parent component you can use pass it via props to the needed components.
import React from "react";
export default function App() {
// Keep the state at this level and pass it down as needed.
const [isVisible, setIsVisible] = React.useState(false);
const toggleVisibility = () => setIsVisible(!isVisible);
return (
<div className="App">
<Toggle isVisible={isVisible} toggleVisibility={toggleVisibility} />
{isVisible && <NewComponent />}
</div>
);
}
class Toggle extends React.Component {
render() {
return (
<div>
<button onClick={this.props.toggleVisibility}>
{this.props.isVisible ? "Hide details" : "Show details"}
</button>
{this.props.isVisible && (
<div>
<p>
When the button is click I do want this component or text to be
shown - so my question is how do I hide the component
</p>
</div>
)}
</div>
);
}
}
class NewComponent extends React.Component {
render() {
return (
<div>
<p>When the button below (which is in another component) is clicked, I want this component to be hidden - but how do I pass the state to say - this is clicked so hide</p>
</div>
)
}
}
I just looked at your REPL.
You need to have the visibility state in your App component, and then pass down a function to update it to the Toggle component.
Then it would be easy to conditionally render the NewComponent component, like this:
render() {
return (
<div className="App">
{this.state.visibility && <NewComponent />}
<Toggle setVisibility={this.setVisibility.bind(this)} />
</div>
);
}
where the setVisibility function is a function that updates the visibility state.

How to deal with multiple <select> dropdown menus in the same class component that use the same state to pass a value to redux?

This code works fine if the user selects something from each dropdown menu, but if they forget to make a selection, it will just use the value selected from the previous dropdown menu. Also if they don't make any selection at all and submit, it will obviously submit the default value stored in the state which is "0".
Anyone happen to have a workaround for this? Thanks.
export class Content extends Component {
constructor(props){
super(props)
this.state = {
selectedOption: 0
}
}
handleOptionChange = e => {
this.setState({
selectedOption: e.target.value
})
}
handleSubmit = e => {
e.preventDefault()
}
render() {
let snowboardItems = this.props.snowboards.map((board,index) => {
return <div><form onSubmit={this.handleSubmit}>
<li key={index} className="list_item">
<div className="content_div1">
<h3>{board.name}</h3>
<h3>$ {board.price}</h3>
<h4>{board.terrain}</h4>
<h4>Shape: {board.shape}</h4>
<p>Board Length:</p>
<select value={this.state.selectedOption} onChange={this.handleOptionChange}>
{board.length.map((item, index) =>
<option value={item} key={index}>{item}</option>
)}
</select> cm
</div>
<div className="content_div2">
<button className="content_button" type="submit" onClick={() => this.props.addToCart({board}, this.state.selectedOption)}>Add to Cart</button>
<img className="image" src={board.imageurl} />
</div>
</li>
</form>
</div>
})
This is really a case where you should separate this into two components: one to render the list of items (you could do this in the parent passing the props too), and another to render the item and possibly handle its state.
If for some reason you can't though, you'll probably want to separate each board option into its own property on state. Here's an example where state is updated dynamically:
https://codesandbox.io/embed/snowboards-pl9r5
You should always code defensively, so in the example there's a "short circuit" check to make sure that a length was selected before adding it to the cart. Also the select field is marked as required so that you can use HTML5 as another fallback validator.
You can check it by trying to add an item without a length and also selecting different options and adding them to the cart (logging them in the console).
On another note: I changed it to more specific keys because mapping multiple lists and using the index as a key will result in duplicate keys. Keys are how react knows which item is which, and you don't want to confuse react!
P.S. Way to bum me out giving a snowboard example in the summer! lol Happy hackin'

Can't get button component value onClick

I'm sure this is something trivial but I can't seem to figure out how to access the value of my button when the user clicks the button. When the page loads my list of buttons renders correctly with the unique values. When I click one of the buttons the function fires, however, the value returns undefined. Can someone show me what I'm doing wrong here?
Path: TestPage.jsx
import MyList from '../../components/MyList';
export default class TestPage extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleButtonClick(event) {
event.preventDefault();
console.log("button click", event.target.value);
}
render() {
return (
<div>
{this.props.lists.map((list) => (
<div key={list._id}>
<MyList
listCollection={list}
handleButtonClick={this.handleButtonClick}
/>
</div>
))}
</div>
);
}
}
Path: MyListComponent
const MyList = (props) => (
<div>
<Button onClick={props.handleButtonClick} value={props.listCollection._id}>{props.listCollection.title}</Button>
</div>
);
event.target.value is for getting values of HTML elements (like the content of an input box), not getting a React component's props. If would be easier if you just passed that value straight in:
handleButtonClick(value) {
console.log(value);
}
<Button onClick={() => props.handleButtonClick(props.listCollection._id)}>
{props.listCollection.title}
</Button>
It seems that you are not using the default button but instead some sort of customized component from another libray named Button.. if its a customezied component it wont work the same as the internatls might contain a button to render but when you are referencing the event you are doing it throug the Button component

React: Hiding/Showing an element while hiding the rest of the similar elements

I have a recipe creator that creates recipes and also lists their ingredients. I want it to only show the Recipe name and it's div it's inside of, but when you click it, it reveals the ingredients section. If any other sections are open I want it to also close them.
I've tried a couple solutions with state but haven't come up with a solid solution. I could probably do it with jQuery but I've heard it's not good practice to use React and jQuery so I'd rather do it properly.
This is the full app: https://github.com/jeffm64/recipe-box2/tree/master/src/components
The recipe boxes are rendered through .map in the main app render function as shown here:
{Recipes.map(function(item, key) {
return <RecipeBox recipe={Recipes} name={item.name} ingredients={item.ingredients} order={item.order} key={key} generalUpdate={genUpdate} />;
})}
Revealing just the one component's ingredients will be easier. Closing the rest will be a little trickier.
Revealing ingredients - Without Redux, you could add a state property isOpen. When you click on a button in that component it should change the state property isOpen to true. Hide/show the ingredients based on that state property value, which you can do in the markup (shown below) or with a class and a CSS rule.
<div key={1}>
showIngredients = () => { this.setState({isOpen: true}) }
<button onClick={this.showIngredients}>Show ingredients</button>
{this.state.isOpen && <div>Ingredients list</div>}
</div>
Hiding other ingredients - This is a bit trickier and will require a different kind of solution. You'll need to define a method in a parent component that sets the state of all child components. Note the key in the component above. If each child has a unique key, and the parent defines a state property of selectedKey, you could do something like this:
// parent
revealChild = (key) => {this.setState(selectedKey: key)}
...
<div>
{children.map((child) => {
return <Child onReveal={this.revealChild} key={child.key} isSelected={this.state.selectedKey == child.key}/>
})}
</div>
// child
handleReveal = () => this.props.onReveal(this.props.key)
showIngredientsClass = () => this.props.isSelected ? 'visible' : 'hidden'
showButtonClass = () => this.props.isSelected ? 'hidden' : 'visible'
...
<button onClick={handleReveal} className={this.showButtonClass()}>Show ingredients</button>
<div className={this.showIngredientsClass()}>
My ingredients
</div>

Resources