Multi-level Select Input - javascript

I think an example will best illustrate my question. Suppose I have a form to select books and their pages. The user will have one multi-select dropdown to select the books. Then, from each book, the user can select any number of pages. In the end, the data will look like a two-dimensional array. The top level is the array of books chosen, and the second level is the pages selected for each book:
books: {
"Book 1": ["page1", "page2", "page3"],
"Book 2": ["page1", "page2"]
}
The dropdown for books will be automatically populated. The options for the dropdown to select the pages for a book will be populated on loading.
Here is a hard coded example just to see the basic layout: http://codepen.io/alanqthomas/pen/JbVGzW?editors=1000
I'm doing this in React. I can't figure out a way to set these values in the component's state and display them dynamically. I'm using react-select for the multi-select selection inputs.

Sure. Here is what you want. As you want nested pages in books then the setState I did will need modifying but here's a start for you.
If you need help with it just ask.
class Form extends React.Component {
constructor() {
super();
}
render() {
return (
<form>
<div>
<Books />
</div>
</form>
);
}
}
class Books extends React.Component {
constructor() {
super();
this.state = {
books: {
Book1: {
pages: [
{
text: Page 1,
value: Page1
},
{
text: Page 2,
value: Page2
}],
text: Book 1,
value: Book1
},
Book2: {
pages: [
{
text: Page 1,
value: Page1
},
{
text: Page 2,
value: Page2
}],
text: Book 2,
value: Book2
}
}
}
this.state.currentBookKey = this.state.books.Book1.value;
}
selectOnChange = (event) => {
this.setState((state) => ({ currentBookKey: event.target.value }))
}
render() {
return (
<div>
<label>Book</label>
<select multiple name="book" onChange={this.selectOnChange}>
{this.state.books.map(book => <Book value={book.value}}>{book.text}</Book>)}
</select>
<label>Pages to select</label>
<select multiple name="pages">
{this.state.books[this.state.currentBookKey].pages.map(page => <Page value={page.value}}>{page.text}</Page>)}
</select>
</div>
)
}
}
Stateless components:
//React.children.only gives us an exception if more than one child
const Book = ({children, value}) => (
<option value={value}>{React.Children.only(children)}</option>
);
const Page = ({children, value}) => (
<option value={value}>{React.Children.only(children)}</option>
);

Related

setting state within nested response

I am building a react app and I am setting the state with api of nested response of nested state But the state is not setting the way I want.
response that is receiving from api
[
{
"id": 70,
"title": "feefifef",
"images": [
{
"id": 28,
"text": "First Image"
"blog_id": 70,
},
{
"id": 28,
"text": "First Image",
"blog_id": 70,
}
]
}
]
App.js
class App extends React.Component {
constructor(props){
super(props)
this.state = {
blogs = [
{
id: 0,
title: "",
images: [
{
id:0,
text:""
}
]
}
]
}
}
componentDidMount() {
let data;
axios.get('http://127.0.0.1:8000/api/blogs/').then((res) => {
data = res.data;
this.setState({
blogs: data.map((blog) => {
return Object.assign({}, blog, {
id: blog.id,
title: blog.title,
images: blog.images,
}
})
})
})
}
render() {
const blogs = this.state.blogs.map((blog) => (
<BlogList
id={blog.id}
title={blog.title}
images={blog.images}
/>
))
}
return (
<div>{blogs}</div>
)
}
class BlogList extends React.Component {
constructor(props){
super(props)
}
return (
<div>
Title: {this.props.title}
Images: {this.props.images}
</div>
)
}
What is the problem ?
Images are not showing after Title. I am trying to show all images in BlogList class of every blog.
I have also tried using (in BlogList class)
this.props.images.map((img) => {
return (
<div>
Title: {this.props.title}
Images: {img.text}
</div>
)
}
But it showed me
this.props.images.map is not a function.
then I think the problem is with setting state of images (I may be wrong).
When I tried to print this.props.images then it is showing
0: {id: 28, text: '1111', blog_id: 71}
length: 1
[[Prototype]]: Array(0)
I am new in react, Any help would be much Appreciated. Thank You in Advance
this.props.images is an array and hence you can't use {this.props.images} directly. Other wise you will get an error like this "Objects are not valid as a React child. If you meant to render a collection of children, use an array instead"
You have to use something like this
render() {
return (
<div>
Title: {this.props.title} <br/>
Images:
{this.props.images?.map((image, i) => (
<div key={image.id}>
{image.id}<br/>
{image.text}<br/>
{image.blog_id} <br/>
</div>
))}
</div>
);
}

Why do these similar react function passing statements work in different ways? [duplicate]

This question already has answers here:
What are the differences (if any) between ES6 arrow functions and functions bound with Function.prototype.bind?
(3 answers)
Why and when do we need to bind functions and eventHandlers in React?
(2 answers)
Closed 2 years ago.
I have simple react page showing a Tree component and a series of fields. Right now I have the Tree hardcoded, but the fields as passed in as props. Also passed in as props are two parent callbacks. One for the Tree onSelect and one for the <input> onChange event. In both cases, the specific 'on' Event is a local function that in turn calls the parent's callback. This is all working....
In both cases the local functions reference the 'this' variable. In one local function, the Tree onSelect', I had to use the '.bind(this)' way but, in the other I did not. Both local functions can access the 'this.props.' values. However, if I remove the '.bind(this)' from the one used in the Tree component it fails. The 'this' is undefined.
I am new to react, so I'm just trying to figure what goes where. I'm guessing this has something to do with the Tree being a component and the <input> as something more basic?
Thanks for any insights...
import React, { Component } from "react";
import Tree from '#naisutech/react-tree'
import './RecipePage.css';
class RecipePage extends Component {
constructor(props){
super(props);
this.state = { items: props.items,};
}
onMySelect (props) {
debugger;
const items = this.state.items.slice();
console.log("HI" , props);
}
handleChange = ({ target }) => {
debugger;
const items = this.state.items.slice();
items[parseInt(target.id)+1].defaultValue = target.value;
this.setState({items: items,});
var props = {items, target};
this.props.onInputChanged(props); // call the parent's update function send relavent data.
};
render() {
const t8000 = [
{
label: 'Gauge 1',
id: 1234,
parentId: null,
items: null
},
{
label: 'Target',
id: 1236,
parentId: 1234,
items: null
},
{
label: 'Gage Factor',
id: 5678,
parentId: 1234,
items: null
},
{
label: 'Gauge 2',
id: 1235,
parentId: null,
items: null
},
{
label: 'Target',
id: 2236,
parentId: 1235,
items: null
},
]
const myTheme = {
'my-theme': {
text: '#161616',
bg: '#f1f1f1',
highlight: '#cccccc', // the colours used for selected and hover items
decal: 'green', // the colours used for open folder indicators and icons
accent: '#f1f1f1' // the colour used for row borders and empty file indicators
}
}
return(
<div id="recipePage" className="recipeMenu" >
<div className="closeButton" >
<img src={require('./../CommonImages/closeButton_W_90x90.png')} height="90px" onClick={() => this.props.close()} alt="Menu buttons"></img>
<Tree nodes={t8000} onSelect={this.onMySelect.bind(this)} theme = {'my-theme'} customTheme={myTheme} />
<form>
<fieldset>
<legend>this.state.items[0].label}</legend>
{this.state.items.map((item, key) =>(
<div className={item.show===1 && key!==0 ?"ShowInputs":"HideInputs"}>
<label htmlFor={item.id}>{item.label} </label>
<input type="text" name={item.id}
id={item.id} value={item.defaultValue}
onChange={this.handleChange} />
</div>
))}
</fieldset>
</form>
</div>
</div>
);
}
}
export default RecipePage;

Select Multiple Checkboxes with either Select All OR Individually

So I have a little situation that I am stuck with. I have an array of holidays that looks like so:
export const holidays = {
federal: [
{ name: "New Year's Day", selected: false },
{ name: 'Martin Luther King, Jr. Day', selected: false },
{ name: "George Washington's Birthday", selected: false },
{ name: 'Memorial Day', selected: false },
{ name: 'Independence Day', selected: false },
{ name: 'Labor Day', selected: false },
{ name: 'Columbus Day', selected: false },
{ name: 'Veterans Day', selected: false },
{ name: 'Thanksgiving Day', selected: false },
{ name: 'Christmas Day', selected: false }
],
other: [
{ name: 'Black Friday', selected: false },
{ name: 'Christmas Eve', selected: false },
{ name: "New Year's Eve", selected: false }
]
};
My goal is to render 2 lists of holidays - one with a Federal Holidays label, and the other with a Other Holidays label. I need to be able to select all the checkboxes with an Add all options, and I also need to be able to select the check boxes individually.
Right now, only my Add all functionality is working. Here is my code:
// CheckboxList component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import camelCase from 'lodash';
import Checkbox from 'common/components/Checkbox';
class CheckList extends Component {
static propTypes = {
data: PropTypes.array,
id: PropTypes.string.isRequired
};
...
get checkboxList() {
const { data } = this.props;
const { checked } = this.state;
return data.map(item => (
<Checkbox
id={camelCase(item)}
key={camelCase(item)}
checked={checked}
label={item}
/>
));
}
handleSelectAllCheckboxToggle() {
this.setState(({ checked }) => ({ checked: !checked }));
}
render() {
const { id } = this.props;
return (
<div className={this.baseClass}>
<Checkbox
id={id}
label="Add all"
onChange={this.handleSelectAllCheckboxToggle}
/>
{this.checkboxList}
</div>
);
}
}
The above is a reusable component that basically renders checkboxes with labels depending on how many items are inside the data prop (array) that is passed into the component. This reusable component is then passed into a parent component which is going to render the checkbox list:
// HolidaySchedule component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import camelCase from 'lodash/camelCase';
import { holidays } from 'modules/utilities/constants';
import CheckboxList from './CheckboxList';
import Panel from 'common/components/Panel';
class HolidaySchedule extends Component {
get holidaysList() {
return Object.keys(holidays).map(holiday => (
<CheckboxList
key={camelCase(holiday)}
data={this.holidaysData(holiday)}
id={`${holiday}Holidays`}
/>
));
}
holidaysData(type) {
return holidays[type].map(holiday => holiday.name);
}
render() {
return (
<Panel>
{this.holidaysList}
</Panel>
);
}
}
This all ends up rendering a list of checkboxes with the holidays as their labels:
Sorry for the kinda long and in-depth post, I just wanted to make sure I didn't leave anything out. The functionality when clicking Add all for each group of checkboxes (Federal or Other) works fine.
However, I do not know what to do in order to also add functionality where I can select the checkboxes individually. I have only gotten as far as being able to select only one checkbox at a time, and it deselects the previous one selected. I would like to be able to Add all to select all checkboxes, click one of the checkboxes to uncheck it and deselect the Add all checkbox, and also just select one checkbox at a time. Stumped!
If anyone took the time to go through all this, thank thank you already!! If anyone has any advice or direction, then thank you a million times more!!!!
Give a man a fish, and you feed him for a day. Teach a man to fish, and you feed him for a lifetime
Are you looking for something like this ? I suppose this is the logic that you are looking for.
I've wrote a checkbox list demo with select all function for you. Its just logic, nothing looking fancy.
import React from "react";
import ReactDOM from "react-dom";
const App = () => {
const [holidays, setHolidays] = React.useState({
federal: [
{ name: "New Year's Day", selected: false },
{ name: "Martin Luther King, Jr. Day", selected: false },
{ name: "George Washington's Birthday", selected: false },
{ name: "Memorial Day", selected: false },
{ name: "Independence Day", selected: false },
{ name: "Labor Day", selected: false },
{ name: "Columbus Day", selected: false },
{ name: "Veterans Day", selected: false },
{ name: "Thanksgiving Day", selected: false },
{ name: "Christmas Day", selected: false }
],
other: [
{ name: "Black Friday", selected: false },
{ name: "Christmas Eve", selected: false },
{ name: "New Year's Eve", selected: false }
]
});
const handleOnChange = (e, type) => {
const { name, checked } = e.target;
const newHoliday = [...holidays[type]];
const index = newHoliday.findIndex(h => h.name === name);
if (index > -1) {
newHoliday[index] = { name, selected: checked };
}
setHolidays(h => ({ ...h, [type]: newHoliday }));
};
const handleOnSelectAll = (e, type) => {
const { checked } = e.target;
let newHoliday = [...holidays[type]];
if (!checked) {
newHoliday = newHoliday.map(opt => ({ ...opt, selected: false }));
} else {
newHoliday = newHoliday.map(opt => ({ ...opt, selected: true }));
}
setHolidays(h => ({ ...h, [type]: newHoliday }));
};
const renderCheckboxList = (options, type) =>
options.map(opt => (
<div>
<label>
<input
type="checkbox"
name={opt.name}
onChange={e => handleOnChange(e, type)}
checked={opt.selected}
/>
{opt.name}
</label>
</div>
));
const renderSelectAllCheckbox = type => (
<div>
<label>
<input
type="checkbox"
onChange={e => handleOnSelectAll(e, type)}
checked={holidays[type].every(opt => opt.selected)}
/>
{`Select All ${type}`}
</label>
</div>
);
return (
<section style={{ display: "flex", justifyContent: "space-around" }}>
<div>
<div>
<fieldset>
Federal Holidays
{renderSelectAllCheckbox("federal")}
{renderCheckboxList(holidays.federal, "federal")}
</fieldset>
</div>
<div>
<fieldset>
Other Holidays
{renderSelectAllCheckbox("other")}
{renderCheckboxList(holidays.other, "other")}
</fieldset>
</div>
</div>
<div>
State:
<pre>{JSON.stringify(holidays, null, 2)}</pre>
</div>
</section>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Some tips for you, if you want the final value to be in array form, you can use filter and map to get the name of the option.
Here is a working example: https://codesandbox.io/s/react-controlled-checkbox-list-f4t7g?fontsize=14&hidenavigation=1&theme=dark
Update (11/14/2019)
I've recreated another sandbox demo using React class. This time, I've created a component similar to yours (CheckboxList) that has name and onChange callback. Using these props allow you to update your parent holidays state.
You can find the working example here: https://codesandbox.io/s/react-class-controlled-checkbox-list-ejlfn?fontsize=14&hidenavigation=1&theme=dark
I think what you are missing is the onChange callback to update your parent state everytime there are changes made to the checkboxes. Your code are missing some important stuffs.

How to set multiple dropdown values to each dynamic element Semantic UI React

I'm having trouble figuring out how to set a dynamic dropdown component with multiple-value selections to each rendered element in a feature I'm working on. I think I'm really close but ultimately need a bit of guidance.
Here's the component:
import React, { Component } from 'react'
import { List, Dropdown, Label } from 'semantic-ui-react'
const directions = [
{key: "0.0", text: "0.0", value: "0.0"},
{key: "22.5", text: "22.5", value: "22.5"},
{key: "45.0", text: "45.0", value: "45.0"},
{key: "67.5", text: "67.5", value: "67.5"},
{key: "90.0", text: "90.0", value: "90.0"}
]
const channels = [
{ch: 65, callsign: "TEST1"},
{ch: 49, callsign: "TEST2"},
{ch: 29, callsign: "TEST3"}
]
export default class DirectionalSelection extends Component {
constructor(props) {
super(props)
this.state = {
channels,
directions,
currentValues: {}
}
}
handleDropdownChange = (e, index, { value }) => {
this.setState(({ currentValues }) => {
currentValues[index] = value
return currentValues
})
}
handleDirAddition = (e, index, { value }) => {
this.setState(({ directions }) => {
directions[index] = [{ text: value, value }, ...this.state.directions]
return directions
})
}
render() {
const { channels, currentValues, directions } = this.state
return (
<div>
<List>
{channels.map((el, index) => (
<List.Item key={index}>
<Label>{el.ch}</Label>
<Dropdown
search
multiple
selection
scrolling
allowAdditions
options={directions}
value={currentValues[index]}
placeholder='Choose directions'
onAddItem={this.handleDirAddition.bind(this, index)}
onChange={this.handleDropdownChange.bind(this, index)}
/>
</List.Item>
))}
</List>
</div>
)
}
}
Right now every time I select dropdown values on any channel, currentValues returns as [object Object]: ["22.5", "45.0"]. I want to set the ch key in channels as the key and the dropdown values array as the value and append them to currentValues.
I hope I've clarified the question enough to understand. Here is a link to Semantic-UI-React docs with the original component I'm using: https://react.semantic-ui.com/modules/dropdown#dropdown-example-multiple-allow-additions. Thanks for the help!
I figured it out! It was so simple, just had to switch the params in handleDropdownChange = (e, index, { value }) to handleDropdownChange = (index, e, { value }). It was setting the event function as the object key.

React in Typescript with Semantic: Displaying info in Semantic Dropdown

I have an array data sourcing from a JSON file and I need to display some names in the drop down. I am using Semantic UI Dropdown. How can I achieve this? I was able to get it functioning in react however after rewriting the code to include typescript I am unable to. New to react and typescript. In the Dropdown I have set up options to be this.state.jockeys but no data shows up but console.log(this.state.jockeys) shows me the data. When I click on the dropdown, an empty list (with the right number of rows as my data length but the fields are empty) meaning somehow it's picking up my data but cant it somehow cant be read. How would I get the value and key. That could be the problem.
NB: I want to display login property in the dropdown.
Here's what I have;
import * as React from 'react';
import { Divider, Container, Dropdown } from 'semantic-ui-react';
import 'semantic-ui-css/semantic.min.css';
import 'react-promise';
import { Jockey } from '../Jockey';
const data: JockeyArray[] = require('../../team.json');
import { getUniqueJockeys } from '../../utils/uniqueUtil';
interface Props {
start: boolean;
reset: boolean;
startRandom: boolean;
}
interface JockeyArray {
id: number;
login: string;
avatar_url: string;
}
interface State {
nextRacers: string[];
jockeys: JockeyArray[];
}
// Race Component
export class Race extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
jockeys: data as JockeyArray[],
nextRacers: [],
};
}
handleChange = () => {
}
render() {
const options = data.map(({ id, login }) => ({ key: id, value: login, text: login }));
console.log(options);
return (
<div>
<Divider />
<div className="App-container">
<div className="App-dropdown">
<Container>
<p>Add jockeys to the race in the dropdown below (and click Start)</p>
<Dropdown
placeholder="Select Jockey..."
selection={true}
search={true}
value={}
key={}
options={options}
onClick={this.handleChange}
/>
</Container>
</div>
</div>
</div>
);
}
}
}
Example JSON
[
{
"login": "alvinward",
"id": 18378578,
"avatar_url": "https://avatars3.ttrs",
},
{
"login": "meganswat",
"id": 38345670,
"avatar_url": "https://avatars3.xxx",
},
{
"login": "michaelflinch",
"id": 48378108,
"avatar_url": "https://avatars3.xggj",
},
]

Categories

Resources