How to use checkbox inside select options? React hook - javascript

Currently I'm trying to create this design.
It doesn't open and close as expected.
I also created a codesandbox
Three things:
1. I have a onClick, but I'm not sure if my logic is correct to open and close the button.Also should there be a useEffect here to listen to changes?
const showPlatformOptions =()=> {
// let checkboxes = el;
// console.log("ref:",myRef.current)
// if (!expanded) {
// //checkboxes.style.display = "block";
// setExpanded(true);
// } else {
// // checkboxes.style.display = "none";
// setExpanded(false);
// }
}
I have a onChange called selectionOptions that should let me know which platforms are selected, but it currently only shows one platform at a time, why?
Is there another way to create this dropdown and checkbox. Maybe a library using hooks?
Any help is appreciated.
import React, { useState,useEffect, useRef} from "react";
const SearchBar =()=>{
const [query, setQuery] = useState("");
const [expanded,setExpanded] = useState(false);
const [selectionOptions, setSelectionOptions] = useState(["Instagram","LinkedIn","Twitter"]);
const myRef = useRef(null);
const showPlatformOptions =()=> {
// let checkboxes = el;
// console.log("ref:",myRef.current)
// if (!expanded) {
// //checkboxes.style.display = "block";
// setExpanded(true);
// } else {
// // checkboxes.style.display = "none";
// setExpanded(false);
// }
}
const handleQueryChange = event => {
console.log("Event:",event.target.value)
setQuery(event.target.value);
};
return (
<form onSubmit={showPlatformOptions}>
<div className="w-64">
<div className="relative" onClick={showPlatformOptions}>
<h6>PLATFORMS </h6>
<select className="w-full font-semibold" onChange={handleQueryChange}>
{selectionOptions.map((platform,x) => (
<option key={x} value={platform}>
{platform}
</option>
))}
</select>
<div className="absolute left-0 right-0 top-0 bottom-0"></div>
</div>
<div
ref={myRef}
className="checkboxes border-gray-200 border border-solid">
<label htmlFor="one" className="block ">
<input type="checkbox" id="one" onChange={handleQueryChange} className="m-3"/>
Instagram</label>
<label htmlFor="two" className="block">
<input type="checkbox" id="two" onChange={handleQueryChange} className="m-3"/>
LinkedIn</label>
<label htmlFor="three" className="block">
<input type="checkbox" id="three" onChange={handleQueryChange} className="m-3"/>
Twitter</label>
</div>
</div>
</form>
);
}

You're really close with your logic and code so far so well done! I'll answer your questions in order:
I have a onClick, but I'm not sure if my logic is correct to open and close the button.Also should there be a useEffect here to listen to changes?
Yep your logic to toggle the hide and show of the dropdown is spot on. The only thing is you don't have to worry about CSS there. You can use this boolean to display or hide the dropdown when the state changes.
I have a onChange called selectionOptions that should let me know which platforms are selected, but it currently only shows one platform at a time, why?
Good question, the reason for this is because its firing on the change of "each" checkbox. As you change checkboxes you'll see the event fire and just show the value for the new checkbox. The checkbox itself is what the event.target refers to. Not the whole form
Is there another way to create this dropdown and checkbox. Maybe a library using hooks?
I don't think you'll need to in this case. I took your code sandbox and enhanced it with just a couple additional bits of code to show how close you were!
Here it is
I'll point out a couple of changes i made:
Instead of dealing with CSS I used your expanded variable to determine if I should render the dropdown. This conditional logic is on line 50.
Because you want to keep track of all the different queries I changed the query variable to keep track of which queries were selected in an array. The logic for this is on line 26, but basically if the user unticks a checkbox I remove it from the array (if its there) and if they check it I add it to the array (if its not there).
I hope this helps you out!

The answer given by Code Novitiate is really good here to solve the bugs in your existing code 👍 This is a slightly different approach that simplifies the code.
Currently you are trying to rendering a select and separately rendering checkboxes. The select and its contents are never actually used, asides of visually to show the dropdown icon.
Personally, I find the native select tag hard to work with, particularly when you want to select multiple options. Most select libraries seem to create their own component out of divs and style them to look and feel like a select, which I think might be the best approach here as well 😀
Approach
Conditionally render the checkboxes based on whether the dropdown has been expanded
Keep track of selections in local state
Update state whenever one of the checkboxes is clicked
Render selections in the dropdown header
Style dropdown header to look like a select
In my example I added a little triangle in css, but you would use something that better matches the designs you have
Example
I put together this CodePen which is hopefully useful for you
Other suggestions
react-select is a really good library for doing what you would like, take a look at the Multi example.
Given that selectionOptions never changes, there's no reason to hold that as state. You can just declare an array instead. In my example, this is the PLATFORMS constant.
remove <div className="absolute left-0 right-0 top-0 bottom-0" />. It looks like the effect is to create a div that covers the whole area? This gave me troubles because it interferes with some click events 😅

All you want to have a dropdown of with checkbox for Each element. Please refer to Sandbox
to understand the making of this whole dropdown.
the approach you are using is not Right and bit complex and not reusable. In react the whole idea is to create common reusable components but your code is very tightly coupled to your use-case. There is no point in creating a "Select" and then separately creating a list of checkboxes.
Checkboxes should appear as a child to you dropdown and not outside it, making it very easy for you to manage dropdown and its state.

Related

How to access and use a value from a child component on redux-form to make a checkbox work and enable conditionally rendered extra options

I'm using redux-form. I need to conditionally render extra options on user click of a checkbox in a child component.
I am able to do this on the parent file (in the SOW Type section of the form) using 'formValueSelector' (see sandbox: https://codesandbox.io/s/currying-cloud-isu8c) but on the third option in this section ('Custom Professional Services SOW') on clicking the checkbox I need to further condionally render some options on clicking the new checkbox that is exposed inside ('Customised Professional Services').
The file containing the code for the child component is 'CustomProfExtOptions'. What code do I need in this child component and/or the parent component in order to get this nested checkbox to work/enable conditional rendering on checking the box
I understand I need to access the value of the field in this child component on the form in order to manipulate it's state (mapStateToProps?) and allow the conditional rendering to work. I have done this successfully in the parent (as the sandbox shows) but I don't know how to get his working in the child component.
I have googled extensively on this for three days and tried to tweak other examples I have found to fit this specific problem (the greyed out text at the bottom of 'CustomProfExtOptions.js' where the child component code is held) but I've yet to find anything that works.
What is working so far:
The 'formValueSelector' code (as per the Redux-Form docs which allows access the values in the fields of the form) I am using in the parent container for the form (a file called 'PdfGenFormContainerRedux.js') is as follows:
PdfGenFormContainerRedux = reduxForm({
form: "StatementOfWorkApplication"
})(PdfGenFormContainerRedux);
const selector = formValueSelector("StatementOfWorkApplication");
PdfGenFormContainerRedux = connect((state) => {
const hasProductSowValue = selector(state, "productSOW");
const hasTeradataExtOptionsValue = selector(state, "teradata");
const hasCustomProfExtOptions = selector(state, "customExtOptions"); //selector created
return {
hasProductSowValue,
hasTeradataExtOptionsValue,
hasCustomProfExtOptions
};
})(PdfGenFormContainerRedux);
export default PdfGenFormContainerRedux;
This allows me to conditionally render the second hidden field on the click of the checkbox by using a variable called props.hasCustomProfExtOptions as demonstrated below:
<div className="checkbox-group">
<div>
<label className="checkbox-group">
<Field
name="customExtOptions"
className="form-checkbox"
component="input"
type="checkbox"
/>
Custom Professional Services SOW
{props.hasCustomProfExtOptions && ( //conditional rendering bit here
<div>
<Field
name="custProfServices"
type="input"
component={CustomProfExtOptions}
label="Custom Options Info"
placeholder="Location"
formId={formId}
/* hasProfServ={props.hasProfServ} */
/>
</div>
)}
</label>
</div>
</div>
</div>
This works fine on the parent container but how do I get it working on the checkbox labelled 'Customised Professional Services' in the child component? (the file called 'CustomProfExtOptions')
Just to add, i need any input from this nested section to be included in the final submission of the form.
Any help would be greatly appreciated

Where to handle parent-child event React

I'm getting start with React basic concepts, and I'm not sure how to proceed...
I'm trying to build a simple Minesweeper game, my structure is a MinesweeperField wrapper, with height, width and minesNumber as props, and a MinesweeperTile, with value (is it a mine/number of close mines) and explored as props.
My problem is, I'm not sure how to handle clicking on the tiles. The MinesweeperField component must be notified when a tile is explored to handle the new game state (ex: in case of game over or win), and it must also be able to trigger explore in other tiles (for example, if you click on a tile with 0 close mines, all of the adjacent tiles must be explored automatically).
From what I understood there are two approaches:
Handle click on the Tile, Tile notifies Field, Field triggers explore also on adjacent Tiles - notifying Field from Tile is easy, but calling Tile from Field would require to use refs, plus a different click handler function is instantiated for each Tile
Handle click on Field, Field updates the prop that in render is used to assign the isExplored prop to the Tile - since I render the Tile based on an array variable in the Field's state, I have to use setState to trigger a redraw of child Tiles components.
this is a simplified sample of the render function, in the "Handle Click on Field" version:
return (
<div className="minesweeper-wrapper">
<div className="minesweeper-field" style={this.getDimensions()}>
{this.state.proximityMap.map(function(verticalRow, vIndex){
return verticalRow.map(function(tileValue, hIndex){
return <MinesweeperTile value={tileValue} hIndex={hIndex} vIndex={vIndex} onClick={this.exploreTile.bind(this, vIndex, hIndex, tileValue)} isExplored={this.state.explorationMap[vIndex][hIndex]} />
});
})}
</div>
</div>
);
and here's the exploreTile function:
exploreTile(vIndex, hIndex, tileValue) {
this.unveilTile(vIndex, hIndex);
if (tileValue < 0) {
this.gameOver();
} else {
if (tileValue === 0) {
this.propagateTileExplore(vIndex, hIndex);
}
}
}
this.state.proximityMap contains values indicating how many mines are close to this tile/is this tile a mine. this.state.explorationMap contains bools indicating which tiles have been explored.
My problem with this approach is that, from what I understood, if I want to redraw a single tile, I have to call setState on Field and update this.state.explorationMap array, which will redraw every tile!
Any ideas as to how I could limit the redraw to a single Tile? Should I keep exploring the "handle click on Field" way or go back to "handle click on Tile"?
I started with "handle click on Tile" but I stopped when I got to the "explore adjacent tiles" problem.
Also please no jQuery/js tricks or hacks to solve this in an unconventional way, it's not about finding a way to do this, but finding the most appropriate. It won't probably make a noticeable difference in the context of a Minesweeper game, but as I said, it's for training purposes :)
The most appropriate way would be to let the parent handle the changes. The children just need to know whom to call when they are clicked and how they should look like. I did the same thing in my Game of Life clone using React.
CodePen Link
changeTile(row, col) {
if (!this.state.isPlaying) {
const newTile = this.state.board[row][col] === 1 ? 0 : 1;
const newRow = [...this.state.board[row]];
newRow.splice(col, 1, newTile);
const newBoard = [...this.state.board];
newBoard.splice(row, 1, newRow);
this.setState({
board: newBoard
});
}
}

To limit number of items to be displayed in react predictive input

Implementing React from couple of months. I used 'react-predictive-input' for predicting items from the pre-defined list on onchange event.
With "large" data sets (500/1000 items) the AutoComplete component becomes very slow especially when typing the first 3 characters because it will render many items in the dropdown slowing the entire interface, even using a maxHeight for the component.
Do we have any attribute as 'max-items' to show from the list?
Below is the snippet of code :
<Autocomplete
id="items"
placeholder="My Items"
data={this.props.items}
onSelected={this.onItemSelected.bind(this)} />
Function is invoked if item is selected
onItemSelected(value){
console.log(`${value} was selected`);
}
List of items
static defaultProps = {
items:[
'car','Bicycle','Truck','Green Van'............................1000items]
};
Text prediction works fine, speed and display are becoming an issue to handle.
I used Material UI Autocomplete for retrieving the use cases mentioned in the question. Link for reference here.

Updating array model deselects iterated <Input> elements

Trying to implement a add/remove sub-item entry; increment/decrement buttons add slots into the array and input fields are added/removed automatically:
<div *ngFor="let item of itemsInNewOrder; let i = index">
<input [(ngModel)]="itemsInNewOrder[i]" xtype="text" name="order" title="order" />
</div>
This is working functionally, but every time a letter is entered into the input, the element is deselected and must be clicked again to enter yet one more letter. How can I solve this?
http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
The link above explains change detection in Angular 2. It also describes some other change detection strategies that you might find useful.
Basically since you did a 2 way binding [(ngModel)]="itemsInNewOrder[i]" to the itemsInNewOrder array, Angular is trying to be helpful and update the view for you.
Each keystroke updates your model (itemsInNewOrder)
Angular detects the update
Angular renders the view again with new form controls
Again, these are new form controls so the user hasn't focused one yet. This is why you have to click into the control after every entered character.
You can fix this by removing your 2 way binding and instead listening to the blur event for each input or implementing something described in the link above.
Blur event listening example:
<input (blur)="blurHandler(item,i)" type="text" name="order" title="order" />
Component
blurHandler(val:string,ndx:number) {
this.itemsInNewOrder[ndx] = val;
}
This might help too
Angular 2 change event - model changes
You can also wrap the values into objects. I don't know why you don't lose focus if you do it like this.
Component:
itemsInNewOrder = [{value: 'Soda'}, {value: 'Burger'}, {value: 'Fries'}];
template:
<div *ngFor="let item of itemsInNewOrder; let i = index">
<input
type="text"
name="order"
title="order"
[(ngModel)]="itemsInNewOrder[i].value"/>
</div>
The simplest answer to this was that I needed to learn and implement Angular 2's reactive forms library, which is part of Angular 2. Using this, I ran into none of difficulties I was experiencing.

Angular 2 change template input values on click

Working with a set of column components with id input in my template:
<div class="panel-body" *ngIf="columns">
<div class="col-md-4">
<column [id]=columns[current_left_column].Id></column>
</div>
<div class="col-md-4">
<column [id]=columns[current_middle_column].Id></column>
</div>
<div class="col-md-4">
<column [id]=columns[current_right_column].Id></column>
</div>
</div>
I have buttons that increment and decrement the columns by changing the values of current_left_column, current_middle_column, and current_right_column. When I click those buttons I log the values of the three column id's and they're representative of where they should be however the template doesn't reload.
I did attempt to use ApplicationRef.tick() to trigger change detection, but the fact that it didn't change makes me think it's a binding issue, but I'm thus far unable to find anything that matches my case as 2-way binding seems to necessitate a more traditional input element and it currently is one way bound.
I think you need to change the name of the input. I have seen it several times that it collides with the id property every HTML element has.
I really appreciate the comments and info given, it helped me get a better handle on what I was actually trying to do.
I ended up taking an example from a different answer and using ngModel with an input to show the individual column id's. Then since I could prove my id's were switching but it wasn't updating even with the tick() I realized I was working on one component level too high and had to be initiating the change from the Column level. So I took the ngOnInit method that populates the columns with data and put it in a ngOnChanges() method, cleared the OnInit() because it was doubling up, and it started moving fine.
Then I had to deal with the information from the columns being added on top of the prior column, but that was relatively minor.
ngOnChanges(){
this._columnService.GetSingleColumn(this.id).subscribe(column => { this.columnData = column; });
this._cardService.GetCardsByColumnId(this.id).subscribe(cards => { this._cardColumnService.LoadCards(cards); console.log(this.cards); });
if (this.columnData == undefined) {
this.LoadDummyData();
}
}

Categories

Resources