I'm trying to add and remove a dropdown field on click of two buttons:
one 'Add' button which adds/creates a new dropdown field (predefined with options & values)
A 'Remove' button next to each dropdown (in order to remove them individually)
This is a multi page app so I'm using React Router to navigate from one page to another - so to keep the state I'm using Redux.
I managed to add and remove the dropdown fields on click of the buttons - but I'm facing two issues:
I'm falling to save the dropdowns to the store, so if I navigate to another page and come back, the added dropdowns are gone from the page
Anytime a new dropdown is added, the option of the selects is reset
Here is my code so far:
import React from 'react'
import { connect } from 'react-redux'
import { addData, saveSelect, removeSelect } from '../actions.js'
class AddSelectField extends React.Component {
constructor(props){
super(props);
this.state = {
inputSelect: []
}
}
saveData = (e) => {
let data = {}
data[e.target.name] = e.target.value
this.context.store.dispatch(
addData(data)
)
}
addInput = () => {
const inputSelect = this.state.inputSelect.concat(this.renderDropDown);
this.setState({ inputSelect });
this.context.store.dispatch(
saveSelect(this.state.inputSelect)
)
}
removeInput = (item) => {
let index = this.state.inputSelect.indexOf(item);
let newInputSelect = this.state.inputSelect;
newInputSelect.splice(index,1);
this.setState({
inputSelect: newInputSelect
});
this.context.store.dispatch(
removeSelect(newInputSelect)
)
}
renderDropDown = (el, index) => {
return(
<div>
<select
key={index}
name={'document-'+ index}
value={'document-'+ index}
onChange = {this.saveData}
>
<option value="0">Please Select</option>
<option value="1">Australia</option>
<option value="2">France</option>
<option value="3">United Kingdom</option>
<option value="4">United States</option>
</select>
<button onClick={ this.removeInput }>Remove</button>
</div>
)
}
render(){
return(
<div>
<button onClick={ this.addInput }>Add</button>
<div className="inputs">
{this.state.inputSelect.map(this.renderSelect)}
</div>
</div>
)
}
}
class AccountUpgrade extends React.Component {
constructor(props) {
super(props);
}
continueClick() {
this.context.router.push('/AccountUpgrade/Confirmation/')
}
render(){
return (
<div>
<div className="row">
<AddSelectField />
<ButtonRow
primaryProps={{
children: 'Continue',
onClick: this.continueClick.bind(this)
}} />
</div>
</div>
)
}
}
AccountUpgrade.contextTypes = {
store: React.PropTypes.object.isRequired,
router: React.PropTypes.object.isRequired
}
const mapStateToProps = (state) => {
return {
store: state.EligibleAbout
}
}
const EligibleAbout = connect(mapStateToProps)(AccountUpgrade)
export default EligibleAbout
action.js
export const ADD_DATA = 'ADD_DATA'
export const ADD_SELECT = 'ADD_SELECT'
export const REMOVE_SELECT = 'REMOVE_SELECT'
export function addData(data) {
return { type: ADD_DATA, data }
}
export function saveSelect(data) {
return { type: ADD_SELECT, data }
}
export function removeSelect(data) {
return { type: REMOVE_SELECT, data }
}
reducer.js
import ObjectAssign from 'object.assign'
import { combineReducers } from 'redux'
import { ADD_DATA, ADD_SELECT, REMOVE_SELECT } from './actions'
function EligibleAbout(state = {}, action){
switch (action.type){
case ADD_DATA:
return ObjectAssign({}, state, action.data)
case ADD_SELECT:
return ObjectAssign({}, state, action.data)
case REMOVE_SELECT:
return ObjectAssign({}, state, action.data)
default:
return state
}
}
const FormApp = combineReducers({
EligibleAbout
})
export default FormApp
You are managing state in the component as well as in the reducer. In the render function, you are always taking values of inputSelect from the state which is blank initially. You are not using values stored in the reducer for rendering purpose that's why you are not getting it on coming back.
Do not store inputSelect in your component state. Just store that in reducer and use inputSelect from reducer for rendering purpose.
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import uuidV4 from 'uuid/v4'
class AddSelectField extends React.Component {
static propTypes = {
ids: PropTypes.array,
removeSelect: PropTypes.func,
saveSelect: PropTypes.func,
}
static defaultProps = {
ids: [],
}
saveData = (e) => {
// Just do your stuff here.
}
addInput = () => {
this.props.saveSelect(uuidV4())
}
removeInput = index => {
this.props.removeSelect(index)
}
renderDropDown = (id, index) => {
return (
<div>
<select
key={id}
name={id}
onChange={this.saveData}
>
<option value="0">Please Select</option>
<option value="1">Australia</option>
<option value="2">France</option>
<option value="3">United Kingdom</option>
<option value="4">United States</option>
</select>
<button
onClick={() => {
this.removeInput(index)
}}>
Remove
</button>
</div>
)
}
render() {
const ids = this.props.ids
return(
<div>
<button onClick={ this.addInput }>Add</button>
<div className="inputs">
{ids.map(this.renderDropDown)}
</div>
</div>
)
}
}
const mapStateToProps = state => {
const eligibleAbout = state.EligibleAbout
return {
ids: eligibleAbout.ids,
}
}
const EligibleAbout = connect(mapStateToProps, { saveSelect, removeSelect })(AddSelectField)
export default class AccountUpgrade extends React.Component {
continueClick() {
// do your stuff here.
}
render() {
return (
<div>
<div className="row">
<EligibleAbout />
</div>
</div>
)
}
}
Updated Reducer is:
function eligibleAbout(state = { ids: [] }, action = {}){
switch (action.type) {
case ADD_DATA:
// set according to requirement.
case ADD_SELECT:
return {
...state,
ids: [].concat(state.ids, action.data),
}
case REMOVE_SELECT:
return {
...state,
ids: state.ids.filter((id, index) => (index !== action.data)),
}
default:
return state
}
}
If I had been at your place, I would do it like this except state management. I would recommend using immutable will be a good option for state management.
Note: I have only implemented adding and removing input functionality. Not clear about your saveData function requirement.
Related
I am making project, where you get books cards using Google Books API. I need to count amount of books I get in the end and print in in Header.js(after search). I think I need to add new parameter like 'count' in constructor that will get books.length but still don't know how to pass it in the end.
Books.js
import React, { Component } from 'react';
import SearchArea from './SearchArea';
import request from 'superagent';
import BookList from './BookList';
class Books extends Component {
constructor(props){
super(props);
this.state = {
books: [],
searchField: '',
sort: ''
}
}
searchBook = (e) => {
e.preventDefault();
request
.get("https://www.googleapis.com/books/v1/volumes")
.query({ q: this.state.searchField })
.then((data) => {
console.log(data);
const cleanData = this.cleanData(data)
this.setState({ books: cleanData })
})
}
handleSearch = (e) => {
this.setState ({ searchField: e.target.value })
}
handleSort = (e) => {
console.log(e.target.value)
this.setState({sort: e.target.value})
}
cleanData = (data) => {
const cleanedData = data.body.items.map((book) => {
if(book.volumeInfo.hasOwnProperty('publishedDate') === false){
book.volumeInfo['publishedDate'] = '0000';
}
else if(book.volumeInfo.hasOwnProperty('imageLinks') === false) {
book.volumeInfo['imageLinks'] = {thumbnail: 'https://vignette.wikia.nocookie.net/pandorahearts/images/a/ad/Not_available.jpg/revision/latest?cb=20141028171337'}
}
console.log(this.state.books.length)
return book;
})
return cleanedData;
}
render(){
const sortedBooks = this.state.books.sort((a,b) => {
if(this.state.sort === 'Newest') {
return parseInt(b.volumeInfo.publishedDate.substring(0,4)) - parseInt(a.volumeInfo.publishedDate)
}
else if(this.state.sort === 'Oldest') {
return parseInt(a.volumeInfo.publishedDate.substring(0,4)) - parseInt(b.volumeInfo.publishedDate)
}
})
return (
<div>
<SearchArea searchBook = {this.searchBook} handleSearch={this.handleSearch} handleSort={this.handleSort}/>
<BookList books={this.state.books} />
</div>
);
}
}
export default Books;
SearchArea.js
import React from 'react'
const SearchArea = (props) => {
return(
<div className="search-area">
<form onSubmit={props.searchBook} action="">
<input onChange={props.handleSearch} type="text"/>
<button id="search" type="submit">Search</button>
<select defaultValue="Sort" onChange={props.handleSort}>
bled v<option disaalue="Sort">Sort</option>
<option value="Newest">Newest</option>
<option value="Oldest">Oldest</option>
</select>
</form>
</div>
)
}
export default SearchArea;
BookList.js
import React from 'react';
import BookCard from './BookCard';
const BookList = (props) => {
return(
<div className="list">
{
props.books.map((book,i) => {
return <BookCard
key={i}
image={book.volumeInfo.imageLinks.thumbnail}
title={book.volumeInfo.title}
author={book.volumeInfo.authors}
published={book.volumeInfo.publishedDate}
/>
})
}
</div>
)
}
export default BookList;
Header.js
import React from 'react';
const Header = () => {
return(
<header>
<h1>Book Cards</h1>
</header>
)
}
export default Header;
I have been building a trivia game using the Open Trivia API and I am getting an error that I have been trying to figure out but am stuck on. It says: Invalid value for prop value on tag. Either remove it from the element, or pass a string or number value to keep it in the DOM.
I will include my code pages. Thank you in advance for you help.
App.js page:
import React from 'react'
import './App.css'
import axios from 'axios'
import Questions from './components/questions'
import CategorySelector from './components/CategorySelector'
class App extends React.Component {
constructor () {
super()
this.state = {
categories: [],
selectedCategory: null
}
this.selectedCategory = this.selectedCategory.bind(this)
}
componentDidMount () {
axios
.get('https://opentdb.com/api_category.php')
.then(response => {
console.log(response.data)
this.setState({
categories: response.data.trivia_categories
})
})
}
selectedCategory (category) {
this.setState({ selectedCategory: category })
}
render () {
const { categories, selectedCategory } = this.state
return (
<div className='App'>
<h2>Trivia Game</h2>
{
selectedCategory
? <Questions selectedCategory={selectedCategory} />
: (
<CategorySelector
categories={categories}
onSelect={event => selectedCategory}
selectedCategory={this.selectedCategory}
/>
)
}
</div>
)
}
}
export default App
CategorySelector.js page:
import React from 'react'
class CategorySelector extends React.Component {
render () {
const { categories, selectedCategory, onSelect } = this.props
return (
<div className='CategorySelector'>
<select
value={selectedCategory} onChange={onSelect}
>
<option value=''>-- No category selected --</option>
{categories.map(category => (
<option value={selectedCategory} key={category.id}>{category.name}</option>
))}
</select>
</div>
)
}
}
export default CategorySelector
Question.js page
import React from 'react'
import axios from 'axios'
class Questions extends React.Component {
constructor () {
super()
this.state = {
questions: [],
currentQuestionIndex: 0
}
this.handleNextQuestion = this.handleNextQuestion.bind(this)
}
componentDidMount () {
const qUrl = `https://opentdb.com/api.php?amount=3&category=${this.props.selectedCategory.id}`
axios
.get(qUrl)
.then(response => {
console.log(response.data)
this.setState({
questions: response.data.results
})
})
}
handleNextQuestion () {
this.setState({ currentQuestionIndex: this.state.currentQuestionIndex + 1 })
}
render () {
const { selectedCategory } = this.props
const { questions, currentQuestionIndex } = this.state
const currentQuestion = questions[currentQuestionIndex]
let answers = []
if (currentQuestion) {
answers = currentQuestion.incorrect_answers.concat([currentQuestion.correct_answer])
}
return (
<div className='Questions'>
<h2>{selectedCategory.name} Questions</h2>
{currentQuestion && (
<div>
<h3>{currentQuestion.questions}</h3>
<div>
{answers.map((answer, index) => <p key={index}>{answer}</p>)}
</div>
</div>
)}
{(currentQuestionIndex < questions.length - 1) &&
<button onClick={this.handleNextQuestion}>Next Question</button>}
</div>
)
}
}
export default Questions
While passing the prop, you're referring the state variable as this.selectedCategory
Change this line
selectedCategory={this.selectedCategory}
to
selectedCategory={selectedCategory}
I'm trying to do some react/redux basics here, but have the problem that the change in state inside the state store isn't reflected in component UI. here is my code, what wrong did I made?
projectReducer.js
Here is the reducer:
const initState = {
projects: [],
};
const projectReducer = (state = initState, action) => {
switch (action.type) {
case CREATE_PROJECT:
const project = action.project;
state.projects.unshift(project);
return {
...state
};
case GET_PROJECTS:
state.projects = action.projects;
return {
...state
};
default:
break;
}
return state;
}
export default projectReducer
projectAction.js
Here is the action
import axios from 'axios';
export const createProjectActionCreator = project => {
return (dispatch, getState) => {
// make async call to dispatch
axios.post('http://localhost:4000/projects/create-project', project).then(result => {
dispatch({
type: 'CREATE_PROJECT',
project: result.data.project
});
}).catch(err => {
console.log(err)
});
}
}
export const getProjectsActionsCreator = () => {
return (dispatch, getState) => {
axios.get("http://localhost:4000/projects").then(result => {
dispatch({
type: 'GET_PROJECTS',
projects: result.data.projects
});
}).catch(err => {
console.log(err)
});
};
}
createProjectComponent.js
Here is compnent has create project form
import React from 'react';
import { connect } from "react-redux";
import { createProjectActionCreator } from "../../store/actions/projectActions";
class CreateProject extends React.Component {
state = {
projectData: {
title: '',
content: ''
},
createProjectErrors: []
}
handleChange = e => {
const { id, value } = e.target;
const { projectData } = this.state;
projectData[id] = value;
this.setState({projectData});
}
handleSubmit = (e) => {
e.preventDefault();
this.props.createProject(this.state.projectData);
}
render() {
return (
<div className="container">
<form onSubmit={e => this.handleSubmit(e)} className="white">
<h5 className="grey-text text-darken-3">Create New Project</h5>
<div className="input-field">
<label htmlFor="title">Title</label>
<input type="text" id="title" onChange={e => this.handleChange(e)}/>
</div>
<div className="input-field">
<label htmlFor="content">Content</label>
<textarea className="materialize-textarea" id="content" onChange={e => this.handleChange(e)}></textarea>
</div>
<div className="input-field">
<button className="btn pink lighten-1 z-depth-0">Create Project</button>
</div>
</form>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
createProject: project => dispatch(createProjectActionCreator(project))
}
}
export default connect(null, mapDispatchToProps)(CreateProject)
Dashboard.js
This component act like home page which renders project list and the form of project creation
import React, { Component } from 'react';
// import Notifications from './Notifications';
import ProjectList from '../projects/PorjectList';
import { connect } from 'react-redux';
import CreateProject from '../projects/CreateProject';
import { getProjectsActionsCreator } from "../../store/actions/projectActions";
class Dashoard extends Component {
componentWillMount() {
this.props.fetchProjects();
}
render() {
return (
<div className="dashboard container">
<div className="row">
<div className="col s12 m6">
<ProjectList projects={this.props.projects} />
</div>
<div className="col s12 m6">
<CreateProject />
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
console.log(state.project);
return {
projects: state.project.projects
}
}
const mapDispatchToProps = dispatch => {
return {
fetchProjects: () => dispatch(getProjectsActionsCreator())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashoard)
[enter image description here][1]
The problem is in your reducer, you shouldn't mutate state (See the Redux docs on immutability).
const projectReducer = (state = initState, action) => {
switch (action.type) {
case CREATE_PROJECT:
const project = action.project;
state.projects.unshift(project);
return {
...state
};
case GET_PROJECTS:
state.projects = action.projects;
return {
...state
};
default:
break;
}
return state;
}
In each of your cases you are returning a referentially different copy of state, but you're mutating the original state first. Here's how you want to do it instead:
const projectReducer = (state = initState, action) => {
switch (action.type) {
case CREATE_PROJECT:
return {
...state, projects: [action.project, ...state.projects]
};
case GET_PROJECTS:
return {
...state, projects: action.projects
};
default:
return state;
}
}
Note that ...state isn't strictly necessary in this case, since projects is your only state (and you want to overwrite it), but if you add more state, you'll need to spread state to avoid overwriting any other state in the store.
I am new to to redux and react. Still doing simple tutorials. I managed to create 2 simple components; one that outputs on the screen (as a list) whatever is in the array in the redux store, and the other component contains a button and a textfield which basically adds to that array in the store.
I would like to add a feature that will enable me to delete a specific entry in the list depending on what the user clicked on. I am thinking of creating a <button> next to each <li> tag that gets rendered as it loops through the array, and these buttons will correspond to the respective list elements. But I'm not sure how to do that.
I've tried creating a button when each <li> tag gets created but I was getting an error on the console stating that each element in a list needs a unique ID. I then decided to create another array in my store called buttons which will contain a unique id as well as the id of the list but it got out of hand. I think I might be overcomplicating this. This is what I have at the moment:
Components:
List.jsx (responsible for outputting the list)
import React from 'react'
import { connect } from "react-redux";
const ListComp = ({ lists }) => (
<div>
<ul>
{console.log(lists)}
{lists.map( element => (
<li key={element.id}>
{element.titleToBeAddedToList}
</li>
))}
</ul>
</div>
)
const mapStateToProps = state => {
return {
lists: state.lists
};
}
const List = connect(mapStateToProps)(ListComp)
export default List;
SubmitButton.jsx (responsible for outputting the button and textfield)
import React from 'react'
import { connect } from "react-redux";
import uuidv1 from "uuid";
import { addList } from "../actions/index";
import { addButton } from "../actions/index"
function mapDispatchToProps(dispatch){
return {
addlist: article => dispatch(addList(article)),
addbutton: idOfButton => dispatch(addButton(idOfButton))
};
}
class Submit extends React.Component{
constructor(){
super();
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ [event.target.id]: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
const {titleToBeAddedToList} = this.state;
const id = uuidv1();
const button_id = uuidv1();
//Dispatching the action:
this.props.addlist({ titleToBeAddedToList, id });
this.props.addbutton({id, button_id});
//Once we've dispatched an action, we want to clear the state:
this.setState({ titleToBeAddedToList: "" });
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="titleToBeAddedToList"
onChange={this.handleChange}
/>
</div>
<button type="submit" className="btn btn-success btn-lg">
SAVE
</button>
</form>
);
}
}
const SubmitButton = connect(null, mapDispatchToProps)(Submit)
export default SubmitButton;
Reducers:
const initialState = {
lists: [],
buttons: []
};
function rootReducer (state = initialState, action) {
if(action.type === "ADD_LIST" ){
return Object.assign({}, state, {
lists: state.lists.concat(action.payload)
});
} else if(action.type === "ADD_BUTTON"){
return Object.assign({}, state, {
buttons: state.lists.concat(action.payload)
});
} else if(action.type === "DELETE_FROM_LIST"){
//.....//
}
return state;
}
export default rootReducer;
Action:
export function addList(payload) {
return { type: "ADD_LIST", payload }
};
export function addButton(payload){
return {type: "ADD_BUTTON", payload }
}
export function deleteList(payload){
return { type: "DELETE_FROM_LIST", payload }
}
Store:
import { createStore } from "redux";
import rootReducer from "../reducers/index";
const store = createStore(rootReducer);
export default store;
You can use Math.random() as an unique key identifier, if the button is click it will call action deleteItem with the ID, action is bound to reducer pass on the ID, you can then use the ID to indentify elements and remove it in the list.
import React from 'react'
import { connect } from "react-redux";
import { deleteItem } from './actions';
const ListComp = ({ lists }) => (
<div>
<ul>
{console.log(lists)}
{lists.map( element => (
<li key={Math.random()} key={element.id}>
{element.titleToBeAddedToList}
<button onClick={() => deleteItem(element.id)}>X</button>
</li>
))}
</ul>
</div>
)
const mapStateToProps = state => {
return {
lists: state.lists
};
}
const List = connect(mapStateToProps, {deleteItem})(ListComp) // Make it available to component as props
export default List;
Action:
export function deleteElement(id) {
return function(dispatch) {
return dispatch({type: "DELETE_FROM_LIST", payload: id})
}
}
Reducer:
case 'DELETE_FROM_LIST': {
const id = action.payload;
return {
...state,
list: state.list.filter(item => item.id !== id)
}
}
else if (action.type === "DELETE_FROM_LIST") {
return Object.assign({}, state, {
buttons: state.lists.filter(item => (item.id !==action.payload))
});
}
you can use filter() for delete.
This is a minimal working react-redux example containing all the pieces to delete an item from an array in redux store.
// reducer.js
const reducer = (state, action) => {
switch (action.type) {
case 'DELETE':
return state.filter(item => (
item.id !== action.payload.id
))
default: return state;
}
}
// Item.js
const Item = ({id, onClick, label}) => (
<li>
{label}
<button onClick={ () => onClick(id) }>
delete
</button>
</li>
)
// ListContainer.js
const mapStateToProps = state => ({ items: state })
const ListContainer = ReactRedux.connect(mapStateToProps)(class extends React.Component {
handleDelete = id => {
const { dispatch } = this.props;
dispatch({ type: 'DELETE', payload: { id } })
}
render() {
const { items } = this.props;
return items.map(({id, label}) => (
<Item
label={label}
id={id}
onClick={this.handleDelete}
/>
))
}
})
// Main.js
const initialState = [
{ id: 1, label: 'item 1' },
{ id: 2, label: 'item 2' },
{ id: 3, label: 'item 3' },
{ id: 4, label: 'item 4' }
]
const store = Redux.createStore(reducer, initialState);
class App extends React.Component {
render(){
return (
<ReactRedux.Provider store={store}>
<ListContainer />
</ReactRedux.Provider>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/6.0.1/react-redux.js"></script>
<div id="root"></div>
I have a search bar (the parent) that has 3 child components - location dropdown, calender(date picker) and duration dropdown. By default the location is selected to wherever the user's location is. I want a change of focus to date picker where the calendar dropdown is opened automatically after there's an interaction with the first dropdown (location dropdown) and same applies from the second input to the third input. How can I go about doing this?
Parent:
import React, { Component } from 'react';
import { Icon, Btn } from '#appearhere/bloom';
import Title from '../Title';
import LocationDropdown from '../LocationDropdown';
import AppearDateCalendar from '../AppearDateCalendar';
import DurationDropdown from '../DurationDropdown';
import css from './SegmentationBar.css';
import i18n from 'utils/i18n/i18n';
const t = i18n.withPrefix('client.apps.static.screens.home.header.segmentation');
type Props = {
onLocationDropdownChange: Function,
onDateChange: Function,
onDurationChange: Function,
onSubmit: Function,
onMobileSearchClick: Function,
fullSupport: boolean,
};
export default class SegmentationBar extends Component<Props> {
render() {
const {
onLocationDropdownChange,
onDateChange,
onDurationChange,
onSubmit,
fullSupport,
} = this.props;
return (
<div className={css.container}>
<Title />
{fullSupport && (
<div className={css.barWrapper}>
<div className={css.segmentationBar}>
<LocationDropdown onDropdownChange={onLocationDropdownChange} />
<AppearDateCalendar onDateChange={onDateChange} />
<DurationDropdown onDropdownChange={onDurationChange} />
</div>
<div className={css.submitButton} onClick={onSubmit}>
<Icon name="search" />
</div>
</div>
)}
</div>
);
}
}
Children:
1st dropdown:
import React, { Component } from 'react';
import i18n, { getCurrentLocale } from 'utils/i18n/i18n';
import { Icon } from '#appearhere/bloom';
import locationOrder from './locationOrder.json';
import locations from './locations.json';
import css from './LocationDropdown.css';
const t = i18n.withPrefix('client.apps.static.screens.home.header.segmentation.location');
type Props = {
onDropdownChange: Function,
};
type Location = {
key: string,
name: string,
country: string,
placeId: string,
searchString: string,
priority: number,
};
class LocationDropdown extends Component<Props> {
componentDidMount() {
const selectedCity = this.findCitiesForCountry(locationOrder[getCurrentLocale()][0])[0];
this.props.onDropdownChange(selectedCity.searchString, selectedCity.placeId);
}
citySorting = (a: Location, b: Location): number => {
if (a.priority > b.priority) return 1;
if (a.priority < b.priority) return -1;
if (a.name >= b.name) return 1;
return -1;
};
findCitiesForCountry = (countryKey: string): Array<Location> =>
locations
.filter((location: Location): boolean => location.country === countryKey)
.sort(this.citySorting);
handleDropdownChange = (event: SyntheticInputEvent<EventTarget>) => {
const city = locations.find(
(location: Location): boolean => location.key === event.target.value,
);
this.props.onDropdownChange(city.searchString, city.placeId);
};
renderSelectOptions = (countryKey: string): React.Node => {
const cities = this.findCitiesForCountry(countryKey);
return cities.map((city: Location): React.Node => (
<option key={city.key} value={city.key}>
{city.name}
</option>
));
};
renderSelectOptionGroups = (): React.Node =>
locationOrder[getCurrentLocale()].map((countryKey: string): React.Node => (
<optgroup key={countryKey} label={t(countryKey)}>
{this.renderSelectOptions(countryKey)}
</optgroup>
));
render() {
return (
<div className={css.LocationDropdown}>
<div className={css.searchIcon}>
<Icon name="search" />
</div>
<select role="listbox" className={css.locationSelect} onChange={this.handleDropdownChange}>
{this.renderSelectOptionGroups()}
</select>
</div>
);
}
}
2nd input:
Calendar:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import i18n from 'utils/i18n/i18n';
import { DayPicker } from '#appearhere/bloom';
import moment from 'utils/moment/moment';
import cx from 'classnames';
import css from './AppearDateCalendar.css';
const t = i18n.withPrefix('client.apps.static.screens.home.header.segmentation.calendar');
type Props = {
onDateChange: Function,
};
type State = {
calendarOpen: boolean,
month: moment,
day: ?moment,
};
export default class AppearDateCalendar extends Component<Props, State> {
state = {
calendarOpen: false,
month: moment(),
day: undefined,
};
componentDidMount() {
document.addEventListener('mousedown', this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleClickOutside);
}
handleClickOutside = (event: SyntheticMouseEvent<EventTarget>) => {
const currentNode = ReactDOM.findDOMNode(this);
if (currentNode && !currentNode.contains(event.target)) {
this.setState({ calendarOpen: false });
}
};
handleCalendarClick = () => {
this.setState({ calendarOpen: !this.state.calendarOpen });
};
handleMonthChange = (_event: any, newMonth: moment) => {
this.setState({ month: newMonth });
};
handleDaySelect = (_event, day: moment) => {
this.props.onDateChange(day);
this.setState({ day, calendarOpen: false });
};
dateDisplayText = (): string => {
if (this.state.day) return this.state.day.format('Do MMM, YY');
return t('choose_date');
};
render() {
return (
<div className={cx(css.appearDateCalendar, this.state.calendarOpen ? css.focused : '')}>
<div onClick={this.handleCalendarClick}>
<h5 className={css.heading}>{t('heading')}</h5>
<p className={css.dateText}>{this.dateDisplayText()}</p>
</div>
{this.state.calendarOpen && (
<div className={css.calendarDropdown}>
<DayPicker
month={this.state.month}
onInteraction={this.handleDaySelect}
onMonthChange={this.handleMonthChange}
/>
</div>
)}
</div>
);
}
}
3rd dropdown:
Duration:
import React, { Component } from 'react';
import i18n from 'utils/i18n/i18n';
import css from './DurationDropdown.css';
import durations from './durations.json';
const t = i18n.withPrefix('client.apps.static.screens.home.header.segmentation.duration');
type Props = {
onDropdownChange: Function,
};
type Duration = {
name: string,
maxDuration: number,
highValue: boolean,
};
export default class DurationDropdown extends Component<Props> {
handleDropdownChange = (event: SyntheticInputEvent<EventTarget>) => {
const durationIndex = event.target.value;
const duration = durations[durationIndex];
this.props.onDropdownChange(duration.maxDuration, duration);
};
renderDurationOptions = (): React.Node =>
durations.map((duration: Duration, index: number): React.Node => (
<option key={duration.name} value={index}>
{t(duration.name)}
</option>
));
render() {
return (
<div className={css.durationDropdown}>
<h5 className={css.heading}>{t('heading')}</h5>
<select role="listbox" className={css.durationSelect} onChange={this.handleDropdownChange}>
<optgroup label={t('booking_duration')}>{this.renderDurationOptions()}</optgroup>
</select>
</div>
);
}
}
You should read about React refs: https://reactjs.org/docs/refs-and-the-dom.html
You can create a ref per input, and pass it from your parent to the 3 children, and use ref.current.focus() in your dropdown handlers.