Multiple dropdowns without repeating code in ReactJS - reactjs

I have created dropdown onMouseOver with help of state. So far its working good enough. Because i don't have much knowledge about ReactJS i'm not sure is it possible to make multiple dropdowns with this or different method without writing all code over and over again.
Here is my code:
..........
constructor(props) {
super(props);
this.handleMouseOver = this.handleMouseOver.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.state = {
isHovering: false
}
}
handleMouseOver = e => {
e.preventDefault();
this.setState({ isHovering: true });
};
handleMouseLeave = e => {
e.preventDefault();
this.setState({ isHovering: false })
};
............
<ul className="menu">
<li onMouseOver={this.handleMouseOver} onMouseLeave={this.handleMouseLeave}>Categories
{this.state.isHovering?(
<ul className="dropdown">
<li>Computerss & Office</li>
<li>Electronics</li>
</ul>
):null}
</li>
</ul>
............
So if I want to add one more dropdown I need to make new state and 2 more lines in constructor() and 2 functions to handle MouseOver/Leave.So repeating amount would be about this:
constructor(props) {
super(props);
this.handleMouseOver = this.handleMouseOver.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.state = {
isHovering: false
}
}
handleMouseOver = e => {
e.preventDefault();
this.setState({ isHovering: true });
};
handleMouseLeave = e => {
e.preventDefault();
this.setState({ isHovering: false })
};
I will have maybe 10+ dropdowns and at the end will be load of codes. So is there any possibility to not repeat code ? Thank You!

You should use your event.target to achieve what you want. With this, you'll know which dropdown you're hovering and apply any logic you need. You can check for example if the dropdown you're hovering is the category dropdown like this:
if(e.target.className === "class name of your element")
this.setState({hoveredEl: e.target.className})
then you use it this state in your code to show/hide the element you want.
you can check an example on this fiddle I've created: https://jsfiddle.net/n5u2wwjg/153708/
I don't think you're going to need the onMouseLeave event, but if you need you can follow the logic I've applied to onMouseOver
Hope it helps.

1. You need to save the state of each <li> item in an array/object to keep a track of hover states.
constructor(props) {
super(props);
...
this.state = {
hoverStates: {} // or an array
};
}
2. And set the state of each item in the event handlers.
handleMouseOver = e => {
this.setState({
hoverStates: {
[e.target.id]: true
}
});
};
handleMouseLeave = e => {
this.setState({
hoverStates: {
[e.target.id]: false
}
});
};
3. You need to set the id (name doesn't work for <li>) in a list of menu items.
Also make sure to add key so that React doesn't give you a warning.
render() {
const { hoverStates } = this.state;
const menuItems = [0, 1, 2, 3].map(id => (
<li
key={id}
id={id}
onMouseOver={this.handleMouseOver}
onMouseLeave={this.handleMouseLeave}
className={hoverStates[id] ? "hovering" : ""}
>
Categories
{hoverStates[id] ? (
<ul className="dropdown menu">
<li>#{id} Computerss & Office</li>
<li>#{id} Electronics</li>
</ul>
) : null}
</li>
));
return <ul className="menu">{menuItems}</ul>;
}
4. The result would look like this.
You can see the working demo here.
Shameless Plug
I've written about how to keep a track of each item in my blog, Keeping track of on/off states of React components, which explains more in detail.

Related

Reactjs dropdown setState being set on second value change

In the following component, the handleDropdownChange function does not set the state for "data" on the first change, it only sets it after the second change. For example, when I select 'Electronics', nothing happens. I then select 'Produce', and the page loads all 'Electronics' products.
export default class ProductList extends Component {
constructor(props){
super(props);
this.handleDropdownChange= this.handleDropdownChange.bind(this);
this.state = {
data: fulldata
};
}
handleDropdownChange(e){
e.preventDefault();
var filtered_data = fulldata.filter(prod => prod.category == e.target.value);
this.setState({ data:filtered_data })
}
render(){
return(
<div>Category</div>
<select id="filter-cat" onChange={this.handleDropdownChange}>
<option value="Apparel">Apparel</option>
<option value="Electronics">Electronics</option>
<option value="Produce">Produce</option>
</select>
{this.state.data}
);
}
}
Your code looks okay, although I'm not entirely sure how you're rendering this.state.data because by the looks of it it is an array. You can modify your handleDropdownChange to store the selected item instead.
Your state:
this.state = {
data: fulldata[0]
};
Your onChange handler:
handleDropdownChange(e) {
const filtered_data = fulldata.find(
(prod) => prod.category === e.target.value
);
this.setState({ data: filtered_data });
}
Here's the working codesandbox example
The only thing that worked for me was found here:
setState doesn't update the state immediately
My updated method looks like this:
async handleDropdownChange(e){
e.preventDefault();
var filtered_data = fulldata.filter(prod => prod.category == e.target.value);
await this.setState({ data:filtered_data })
}
This feels hacky and I wish there was a better way, but for now this is only solution that works me.

In ReactJS how do I keep track of the state of a group of checkboxes?

I am trying to keep track of which boxes are checked in my local state(you can check multiple boxes). I want to be able to check and uncheck the boxes and keep track of the ids of the boxes that are checked. I will do something with the values later. This is what I have so far:
import React, { Component } from 'react'
import './App.css'
import CheckBox from './CheckBox'
class App extends Component {
constructor(props) {
super(props)
this.state = {
fruits: [
{id: 1, value: "banana", isChecked: false},
{id: 2, value: "apple", isChecked: false},
{id: 3, value: "mango", isChecked: false},
{id: 4, value: "grape", isChecked: false}
],
fruitIds: []
}
}
handleCheckChildElement = (e) => {
const index = this.state.fruits.findIndex((fruit) => fruit.value === e.target.value),
fruits = [...this.state.fruits],
checkedOrNot = e.target.checked === true ? true : false;
fruits[index] = {id: fruits[index].id, value: fruits[index].value, isChecked: checkedOrNot};
this.setState({fruits});
this.updateCheckedIds(e);
}
updateCheckedIds = (e) => {
const fruitIds = [...this.state.fruitIds],
updatedFruitIds= fruitIds.concat(e.target.id);
this.setState({updatedFruitIds});
}
render() {
const { fruits } = this.state;
if (!fruits) return;
const fruitOptions = fruits.map((fruit, index) => {
return (
<CheckBox key={index}
handleCheckChildElement={this.handleCheckChildElement}
isChecked={fruit.isChecked}
id={fruit.id}
value={fruit.value}
/>
);
})
return (
<div className="App">
<h1>Choose one or more fruits</h1>
<ul>
{ fruitOptions }
</ul>
</div>
);
}
}
export default App
So basically I am able to check and uncheck the boxes, but I cannot seem to update and store the fruitIds. Here is my checkbox component also:
import React from 'react'
export const CheckBox = props => {
return (
<li>
<input key={props.id}
onChange={props.handleCheckChildElement}
type="checkbox"
id={props.id}
checked={props.isChecked}
value={props.value}
/>
{props.value}
</li>
)
}
export default CheckBox
Also if you have a cleaner ways to do this than the way I am doing it, I would love to see it.
This is what if I were to approach it I will do. I will create a one dimensional array that holds the id's of the fruits when A fruit if clicked(checked) I will add it id to the array and when its clicked the second time I check if the array already has the id I remove it. then the presence of id in the array will mean the fruit is checked otherwise its not checked So I will do something like below
this.state={
fruitsIds: []
}
handleCheckChildElement=(id) => {
//the logic here is to remove the id if its already exist else add it. and set it back to state
const fruitsIds = this.state.fruitsIds;
this.setState({fruitsIds: fruitsIds.contains(id) ? fruitsIds.filter(i => i != id) : [...fruitsIds, id] })
}
then I render the checkboxes like
<CheckBox key={index}
handleCheckChildElement={this.handleCheckChildElement}
isChecked = { this.state.fruitsIds.contains(fruit.id)}
id={fruit.id}
/>
This is because you can always use the id to get all the other properties of the fruit so there is absolutely no need storing them again.
then the checkbox component should be as follows
export const CheckBox = props => {
return (
<li>
<input key={props.id}
onChange={() => props.handleCheckChildElement(props.id)}
type="checkbox"
id={props.id}
checked={props.isChecked}
value={props.value}
/>
{props.value}
</li>
)
}
The reason you are not getting your ids updated because:
You are trying to concat a non array element to an array.
concat is used for joining two or more arrays.
updatedFruitIds = fruitIds.concat(e.target.id);
You are not updating your actual fruitIds state field. I dont know why you are using "updatedFruitIds" this variable but due to above error it will always result into a single element array.
this.setState({ updatedFruitIds });
updateCheckedIds = e => {
const fruitIds = [...this.state.fruitIds],
updatedFruitIds = fruitIds.concat([e.target.id]);
this.setState({ fruitIds: updatedFruitIds });
};
OR
updateCheckedIds = e => {
const fruitIds = [...this.state.fruitIds, e.target.id],
this.setState({ fruitIds });
};

How to Add filter into a todolist application in Reactjs with using .filter

im new to react, trying to make an todolist website, i have the add and delete and displaying functionality done, just trying to add an search function, but i cant seem to get it working, where as it doesn't filter properly.
i basically want to be able to filter the values on the todos.title with the search value. such as if i enter an value of "ta" it should show the todo item of "take out the trash" or any item that matches with that string.
when i try to search, it gives random outputs of items from the filtered, i am wondering if my filtering is wrong or if i am not like displaying it correctly.
ive tried to pass the value into todo.js and display it there but didn't seem that was a viable way as it it should stay within App.js.
class App extends Component {
state = {
todos: [
{
id: uuid.v4(),
title: "take out the trash",
completed: false
},
{
id: uuid.v4(),
title: "Dinner with wife",
completed: true
},
{
id: uuid.v4(),
title: "Meeting with Boss",
completed: false
}
],
filtered: []
};
// checking complete on the state
markComplete = id => {
this.setState({
todos: this.state.filtered.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
})
});
};
//delete the item
delTodo = id => {
this.setState({
filtered: [...this.state.filtered.filter(filtered => filtered.id !== id)]
});
};
//Add item to the list
addTodo = title => {
const newTodo = {
id: uuid.v4(),
title,
comepleted: false
};
this.setState({ filtered: [...this.state.filtered, newTodo] });
};
// my attempt to do search filter on the value recieved from the search field (search):
search = (search) => {
let currentTodos = [];
let newList = [];
if (search !== "") {
currentTodos = this.state.todos;
newList = currentTodos.filter( todo => {
const lc = todo.title.toLowerCase();
const filter = search.toLowerCase();
return lc.includes(filter);
});
} else {
newList = this.state.todos;
}
this.setState({
filtered: newList
});
console.log(search);
};
componentDidMount() {
this.setState({
filtered: this.state.todos
});
}
componentWillReceiveProps(nextProps) {
this.setState({
filtered: nextProps.todos
});
}
render() {
return (
<div className="App">
<div className="container">
<Header search={this.search} />
<AddTodo addTodo={this.addTodo} />
<Todos
todos={this.state.filtered}
markComplete={this.markComplete}
delTodo={this.delTodo}
/>
</div>
</div>
);
}
}
export default App;
search value comes from the header where the value is passed through as a props. i've checked that and it works fine.
Todos.js
class Todos extends Component {
state = {
searchResults: null
}
render() {
return (
this.props.todos.map((todo) => {
return <TodoItem key={todo.id} todo = {todo}
markComplete={this.props.markComplete}
delTodo={this.props.delTodo}
/>
})
);
}
}
TodoItem.js is just the component that displays the item.
I not sure if this is enough to understand the issue 100%, i can add more if needed.
Thank you
Not sure what is wrong with your script. Looks to me it works fine when I am trying to reconstruct by using most of your logic. Please check working demo here: https://codesandbox.io/s/q9jy17p47j
Just my guess, it could be there is something wrong with your <TodoItem/> component which makes it not rendered correctly. Maybe you could try to use a primitive element such as <li> instead custom element like <TodoItem/>. The problem could be your logic of markComplete() things ( if it is doing hiding element works ).
Please let me know if I am missing something. Thanks.

Trying to create a controlled switcher button

I've picked up this code online (it's high up in the google results) :
https://codepen.io/BuiltByEdgar/pen/jWOVYQ
but I've grown annoyed with it's code.
I'm trying to have a duo of these buttons where default state is on and having both off is not allowed. The UI behavior I've chosen for that is to simply re-enable the other if it's sibling is being turned off :
onONEClick() {
let {
TWO,
ONE,
} = this.state;
if (TWO) {
ONE = !ONE;
} else if (ONE) {
TWO = !TWO;
ONE = !TWO;
} else {
ONE = !ONE;
}
this.setState({
ONE,
TWO,
});
this.props.callBack({
ONE,
TWO,
});
}
onTWOClick() {
let {
TWO,
ONE,
} = this.state;
if (ONE) {
TWO = !TWO;
} else if (TWO) {
TWO = !TWO;
ONE = !ONE;
} else {
TWO = !TWO;
}
this.setState({
ONE,
TWO,
});
this.props.callBack({
ONE,
TWO,
});
}
and the render:
<div>
<div className="switch-container">
<label>
<input
onChange={this.onONEClick}
type="checkbox"
className="switch"
value={this.state.ONE}
checked={this.state.ONE}
/>
<div>
<span><g className="icon icon-toolbar grid-view" /></span>
<span><g className="icon icon-toolbar ticket-view" /></span>
<div />
</div>
</label>
</div>
ONE
</div>
<div>
<div className="switch-container">
<label>
<input
onChange={this.onTWOClick}
type="checkbox"
className="switch"
value={this.state.TWO}
checked={this.state.TWO}
/>
<div>
<span><g className="icon icon-toolbar grid-view" /></span>
<span><g className="icon icon-toolbar ticket-view" /></span>
<div />
</div>
</label>
</div>
TWO
</div>
simple enough right?
problem is react hates that my switcher is an input of type "checkbox" :
and as you can see I've done my best to avoid this happening but I think it simply isn't in the cards to have an input type and change it's checked state other than by a human.
So I'm thinking maybe I can solve the issue by reconstructing the checkbox with just divs. (the label tag is also in violation of ESLint)
ANSWERED BY JLAITIO :
and here's what I added (top part of the file with props declaration and constructor) :
const propTypes = {
value: React.PropTypes.object,
type: React.PropTypes.string,
callBack: React.PropTypes.func,
ONE: React.PropTypes.bool,
TWO: React.PropTypes.bool,
};
const defaultProps = {
callBack: () => {},
onChange: () => {
},
value: { type: '' },
ONE: true,
TWO: true,
};
class ProviderInfosComponent extends Component {
constructor(props) {
super(props);
const initialValue = props.value;
this.state = {
...initialValue,
ONE: this.props.ONE,
TWO: this.props.TWO,
};
this.onONEClick = this.onONEClick.bind(this);
this.onTWOClick = this.onTWOClick.bind(this);
}
You get this error, if at any point your value attribute is not set, or set to a value that is undefined, and then subsequently it is an existing React state value.
My best guess would be that your constructor does not initialize ONE and TWO, hence the value being undefined to start with. Then when you actually have a value, the component transfers from being an uncontrolled (="component state independent of React") to controlled(="component state tied directly to React state").
As an addition to #jlaitio's answer,
You don't have to set a value for a checkbox input. You can just use checked prop of the component. Also a small improvement for your changed functions might be useful to follow up the values easier.
constructor(props) {
super(props);
this.state = {
ONE: true,
TWO: true
};
}
onONEChange = (event) => {
this.setState((prevState) => {
ONE: event.target.checked,
TWO: (event.target.checked === false ? true : prevState.TWO) // change state of TWO if ONE is false else just leave it as is
}, () => {
this.props.callBack({ ONE: this.state.ONE, TWO: this.state.TWO });
});
}
onTWOChange = (event) => {
this.setState((prevState) => {
ONE: (event.target.checked === false ? true : prevState.ONE), // change state of ONE if TWO is false else just leave it as is
TWO: event.target.checked
}, () => {
this.props.callBack({ ONE: this.state.ONE, TWO: this.state.TWO });
});
}

Updating selected item in JavaScript apps with immutable state

Let's say I have a JavaScript/HTML app that
Manages items
Has a concept of a selected item
Gets realtime updates to items as the app runs.
If I want my app to use immutable state (immutable.js, redux, react, etc), conceptually, how would I keep updates to an item in the list in sync with the app's selected item, if they are the same?
If I am using plain JavaScript objects/arrays and mutation, then the selected item would be a reference to some object, and the list of items would be actually a list of references to item objects. If I change the property on the object in the list of items, then it will be in sync with the selected item because it's the same object.
How would I manage something similar if I am using immutable state?
If an update came through for an item that was selected, would I have to also remember to return a new state where the item in the list is different and the selected item has the same transformations applied?
One thought is to not have a selected item, but have a selected index, but when I reorder my list of items would I then have to remember to return a new state where the selected index is different?
Are there patterns for dealing with something like this?
Here is one way I do that. You could change the reference to index to an item.id.
class JDropSelectRender extends React.Component {
render() {
let items = this.props.options.map((option) => {
if (option.type == 'seperator') {
return (<div style={DropdownSeperatorSty} key={option.key}></div>)
} else {
let selected = Boolean(option.label == this.state.selected.label);
let labelSpanSty = {cursor: 'pointer'};
labelSpanSty.color = selected ? 'green' : 'black';
return (
<div
id='DropdownOptionSty'
key={option.value}
style={DropdownOptionSty}
onMouseDown={this.setValue.bind(this, option)}
onClick={this.setValue.bind(this, option)}
>
<span style={labelSpanSty}>{option.label}</span>
</div>
)
}
});
let value = (<div style={placeSty}>{this.state.selected.label}</div>);
let menu = this.state.isOpen ? <div style={DropdownMenuSty}>{items}</div> : null;
return (
<div id='DropdownSty' style={DropdownSty}>
<div
id='DropdownControlSty'
style={DropdownControlSty}
onMouseDown={this.handleMouseDown}
onTouchEnd={this.handleMouseDown}
>
{value}
<span id='DropdownArrowSty' style={DropdownArrowSty} />
</div>
{menu}
</div>
)
}
}
export default class JDropSelect extends JDropSelectRender {
constructor() {
super();
this.state = { isOpen: false, selected: {} };
}
componentWillMount() {
this.setState({selected: this.props.defaultSelected || { label: 'Select...', value: '' }})
}
componentWillReceiveProps(newProps) {
if (newProps.defaultSelected && newProps.defaultSelected !== this.state.selected) {
this.setState({selected: newProps.defaultSelected});
}
}
handleMouseDown = (event) => {
if (event.type == 'mousedown' && event.button !== 0) return;
event.stopPropagation();
event.preventDefault();
this.setState({ isOpen: !this.state.isOpen })
}
setValue = (option) => {
if (option !== this.state.selected && this.props.onChange) this.props.onChange(this.props.itemName, option);
this.setState({ selected: option, isOpen: false });
}
}

Resources