Change the state of arrows in a dropdown list - javascript

The code below illustrates a normal drop down list. To indicate a drop down list, I use a down arrow with
arrow_drop_down
This arrow remains static for me in any state of the list (open or closed). However, I would like that when clicking on the list, the arrow changes to
arrow_drop_up
.
Those. so that with two different states of the list, there would be two different arrows.
export default function FilterStatusCode() {
const [values, setValues] = React.useState([]);
const [isExpanded, setIsExpanded] = useState(false);
const toggleExpand = () => {
setIsExpanded(!isExpanded);
};
return <>
<div className="item-toggle-statuscode" onClick={toggleExpand}>
<h6>Status Code</h6>
<span class="material-icons">
arrow_drop_down
</span>
</div>
{ isExpanded &&
<div>
<TagInput
inputProps={{ placeholder: 'Add status code...' }}
values={values}
onChange={(values) => {
setValues(values)}}>
</TagInput>
</div>
}
</>;
}

try
<div className="item-toggle-statuscode" onClick={toggleExpand}>
<h6>Status Code</h6>
<span class="material-icons">
{ isExpanded ? arrow_drop_up : arrow_drop_down }
</span>
</div>

You can choose which arrow you use depending on the current state:
// If the list is open show the `up` arrow
// otherwise show the `down` arrow
<span className={open ? "up" : "down"}></span>
I had to improvise in this example and used unicode in the class names.
const { useState } = React;
function Example() {
return (
<div>
<Item />
<Item />
</div>
);
}
function Item() {
const [ input, setInput ] = useState('');
const [ open, setOpen ] = useState(false);
function handleChange(e) {
setInput(e.target.value);
}
function handleOpen() {
setOpen(!open);
}
function handleClick() {
console.log(input);
}
return (
<div className="item">
<div onClick={handleOpen} className="heading">
<span>Status code</span>
<span className={open ? "up" : "down"}></span>
</div>
{open && (
<div>
<input
type="text"
onChange={handleChange}
value={input}
/>
<button
type="button"
onClick={handleClick}
>Submit
</button>
</div>
)}
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.down:after { content: '\25BC'; }
.up:after { content: '\25B2'; }
.heading:hover { cursor: pointer; color: red; }
.item { margin-bottom: 1em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Additional documentation
Conditional (ternary) operator

Related

Filtering Data to load a particular response on click

Currently I have a component that is loaded when I call my API. This content has a CitizenshipType field that separates the items from each other. I have 2 buttons on top which I want to use to filter my data. 1 button is called Europe which should bring out all the content where CitizenshipType=Europe, etc. Currently I have all my data showing without any filtering. Here is my code:
Citizenship Page:
export default function Citizenship({ items, citi }) {
return (
<>
<div>
<div onClick=//SomeFunction>
CARRIBEAN
</div>
<div onClick=//SomeFunction>
EUROPE
</div>
</div>
<div>
<div onClick=//SomeFunction>
OTHER PROGRAMS
</div>
</div>
<div>
{items &&
items.map((item) => (
<div style={{ padding: "40px" }}>
<div class="citizen-item" key={item.id}>
<div className="container6">
<img
src={`http://localhost:1337${item.Thumbnail.url}`}
/>
<div>
{item.Title}
</div>
<div>
Access to {item.CountriesAccessible} countries
</div>
<div>
<button class="findButton">FIND OUT MORE</button>
</div>
</div>
</div>
</div>
))}
{citi &&
citi.map((test) => (
<div style={{ padding: "40px" }}>
<div class="citizen-item" key={test.id}>
<div className="container6">
<img
src={`http://localhost:1337${test.Thumbnail.url}`}
/>
<div>
{test.Title}
</div>
<div>
Access to {test.CountriesAccessible} countries
</div>
<div>
<button class="findButton">FIND OUT MORE</button>
</div>
</div>
</div>
</div>
))}
</>
);
}
Home Page where I am calling the APIs:
export default function Home({ items, citi }) {
return (
<div>
<Benefits />
<Citizenship items={items} citi={citi} />
<Video />
</div>
);
}
export async function getStaticProps() {
const CitizenshipEUres = await fetch(
"http://localhost:1337/citizenships?_limit=5&CitizenshipType=Europe"
);
const CitizenshipCAres = await fetch(
"http://localhost:1337/citizenships?_limit=5&CitizenshipType=Caribbien"
);
const items = await CitizenshipEUres.json();
const citi = await CitizenshipCAres.json();
return {
props: { items, citi },
};
}
you toggle them with states:
import React, { useState } from 'react'
export const TestComponent = () => {
const [carribeanIsShowing, setShowCarribean] = useState(false)
const [europeIsShowing, setShowEurope] = useState(false)
const toggleCarribean = () => {
if (!carribeanIsShowing) {
if(europeIsShowing) {
setShowEurope(false)
}
setShowCarribean(!carribeanIsShowing)
} else {
return
}
}
const toggleEurope = () => {
if (!europeIsShowing) {
if(carribeanIsShowing) {
setShowCarribean(false)
}
setShowEurope(!europeIsShowing)
} else {
return
}
}
return (
<div>
<button onClick={() => toggleCarribean()}>
CARRIBEAN
</button>
<button onClick={() => toggleEurope()}>
EUROPE
</button>
{europeIsShowing && <div>Europe</div>}
{carribeanIsShowing && <div>carribean</div>}
</div>
)
}
Create a new variable where you store the current CitizenshipType, with a default value of 'Europe'.
const [currentCitizenshipType, setCurrentCitizenshipType] = useState(
"Europe"
);
You change your onClick event
<div onClick={() => setCurrentCitizenshipType('Europe')}>
EUROPE
</div>
And finally add a filter statment to your items.map call:
{
items
.filter((item) => item.citizenshipType === currentCitizenshipType)
.map((item)
...}

using button to increment divs in react

I am fairly new to React/Next and I had a quick question.
I am trying to create a button that will increment the number of divs in real time.
Here is my code:
import React from 'react'
const Clown = () => {
const [clownCounter, setClownCounter] = React.useState(1);
function addClown(event) {
event.preventDefault();
}
return(
<React.Fragment>
<div>
<form>
{Array.from({ length: clownCounter}, (_unused, index) => index + 1).map(
(clownIndex) => {
const clownid = `${clownIndex}`
return (
<div key={clownid } className="clown-box">
<label htmlFor={clownid }>Activity {clownIndex}</label>
<br />
<input type="text" onChange={(e)=> onChangeForm(e)} name={activityId} id={activityId} />
<br />
</div>
)
},
)}
<span className="clown-add">
<button onClick={addClown} onChange={() => { setClownCounter(clownCounter++) }}>Add Clown</button>
</span>
<br />
</form>
</div>
</React.Fragment>
)
}
export default Clown
As you can see the goal is to increase the amount of clown-box divs everytime the button is clicked. I think I am close but it is not currently working. Can anyone help?
There are few small this wrong with your code.
First, you have an extra comma(,) after the return statement in map function
Second, you are updating state clownCounter on onChange event in button, which is incorrect. You should update it on click and also prevent the default behaviour of form submit on click of button or you can define the button type to be type="button"
Lastly, you need to define your onChangeForm function
const Clown = () => {
const [clownCounter, setClownCounter] = React.useState(1);
function onChangeForm() {
}
function addClown(event) {
event.preventDefault();
setClownCounter(prev=> prev+1);
}
console.log(clownCounter);
return(
<div>
<form>
{Array.from({ length: clownCounter}, (_unused, index) => index + 1).map(
(clownIndex) => {
const clownid = `${clownIndex}`;
return (
<div key={clownid } className="clown-box">
<label htmlFor={clownid }>Activity {clownIndex}</label>
<br />
<input type="text" onChange={(e)=> onChangeForm(e)} name={'activityId'} id={'activityId'} />
<br />
</div>
)
})
}
<span className="clown-add">
<button type="button" onClick={addClown}>Add Clown</button>
</span>
<br />
</form>
</div>
)
}
ReactDOM.render(<Clown />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app" />
Edit: Thought issue was caused by Array.from, but, it's not. I've removed that part, but kept the example since OP might find it useful
const { useState } = React;
const Clowns = ({ title }) => {
const [clownCounter, setClownCounter] = React.useState(1);
return (
<div>
<button onClick={() => setClownCounter(clownCounter + 1)}>
Add clown
</button>
<div className='clowns'>
{Array.from({ length: clownCounter}, (_unused, index) => index + 1).map((e, i) => (
<div>
<h4>{`Clown #${i + 1}`}</h4>
<img src={`https://placehold.it/150x150&text=Clown%20%23${i + 1}`} />
</div>
))}
</div>
</div>
);
};
ReactDOM.render(<Clowns />, document.getElementById("react") );
.clowns { display: flex; flex-direction: column; }
h4 { margin-bottom: 5px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Identify Clicked Button In Unordered List: React

I a week new in learning react coming from an angular background. I have the following unordered list in React.
const QueueManage: React.FC = () => {
const { queue, setQueue, loading, error } = useGetQueue();
const [btnState, setBtnState] = useState(state);
const enterIconLoading = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
const item = '';
const btn = '';
console.log(item, btn);
setBtnState({ loading: true, iconLoading: true, item: item, btnType: btn });
};
<ul className="listCont">
{queue.map(queueItem => (
<li className="col-12" key={queueItem.id}>
<div className="row">
<div className="listName col-3">
<p>{queueItem.user.firstName} {queueItem.user.lastName}</p>
</div>
<div className="listName col-5">
<div className="row">
<div className="col-3">
<Button type="primary" loading={btnState.loading} onClick={enterIconLoading}>
Assign
</Button>
</div>
<div className="col-3">
<Button type="primary" loading={btnState.loading} onClick={enterIconLoading}>
Absent
</Button>
</div>
<div className="col-3">
<Button type="primary" loading={btnState.loading} onClick={enterIconLoading}>
Done
</Button>
</div>
<div className="col-3">
<Button type="primary" loading={btnState.loading} onClick={enterIconLoading}>
Cancel
</Button>
</div>
</div>
</div>
</div>
</li>
)
)}
</ul>
}
For each list item, the list item will have for buttons, namely Assign, Absent, Done, Cancel. My goal is to identify which button was clicked and for which list item so that I can apply a loader for that specific button. Can any one please assist me with an explanation of how I can achieve this in my code
Here is a visual representation of the list that i get
https://i.imgur.com/kxcpxOo.png
At the moment went i click one button, all buttons are applied a spinner like below:
Your assistance and explanation is highly appreciated.
The Reactful approach involved splitting the li into a separate component. This will help keep each item's state separate. Let's call that QueueItem.
const QueueItem = ({ user }) => {
const [loading, setLoading] = useState(false)
function onClickAssign() {
setLoading(true)
// do something
setLoading(false)
}
function onClickAbsent() {
setLoading(true)
// do something
setLoading(false)
}
function onClickDone() {
setLoading(true)
// do something
setLoading(false)
}
function onClickCancel() {
setLoading(true)
// do something
setLoading(false)
}
return (
<li className='col-12'>
<div className='row'>
<div className='listName col-3'>
<p>
{user.firstName} {user.lastName}
</p>
</div>
<div className='listName col-5'>
<div className='row'>
<div className='col-3'>
<Button type='primary' loading={loading} onClick={onClickAssign}>
Assign
</Button>
</div>
<div className='col-3'>
<Button type='primary' loading={loading} onClick={onClickAbsent}>
Absent
</Button>
</div>
<div className='col-3'>
<Button type='primary' loading={loading} onClick={onClickDone}>
Done
</Button>
</div>
<div className='col-3'>
<Button type='primary' loading={loading} onClick={onClickCancel}>
Cancel
</Button>
</div>
</div>
</div>
</div>
</li>
)
}
Here I've also split out each button's onClick into a separate callback since they are well defined and probably have unique behaviours. Another approach mentioned above in a comment is
function onClickButton(action) {
...
}
<Button type='primary' loading={loading} onClick={() => onClickButton('cancel')}>
Cancel
</Button>
This follows the action / reducer pattern which might be applicable here instead of state (useState)
Move the buttons or the whole li to a component and let each list manage it's state.
// Get a hook function
const {useState} = React;
//pass the index of li as prop
const Buttons = ({ listId }) => {
const [clicked, setClickedButton] = useState(0);
return (
<div>
<button
className={clicked === 1 && "Button"}
onClick={() => setClickedButton(1)}
>
Assign
</button>
<button className={clicked === 2 && "Button"} onClick={() => setClickedButton(2)}>Absent</button>
<button className={clicked === 3 && "Button"} onClick={() => setClickedButton(3)}>Done</button>
<button className={clicked === 4 && "Button"} onClick={() => setClickedButton(4)}>Cancel</button>
</div>
);
};
// Render it
ReactDOM.render(
<Buttons />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<style>
.Button {
background-color: #4CAF50; /* Green */
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
</style>
<div id="react"></div>
In addition to the previous answer it's worth adding that making simple components (in our case buttons) stateful is often considered a bad practice as it gets harder to track all the state changes, and to use state from different buttons together (e.g. if you want to disable all 4 buttons in a row after any of them is pressed)
Take a look at the following implementation, where entire buttons state is contained within parent component
enum ButtonType {
ASSIGN, ABSENT, DONE, CANCEL
}
// this component is stateless and will render a button
const ActionButton = ({ label, loading, onClick }) =>
<Button type="primary" loading={loading} onClick={onClick}>
{label}
</Button>
/* inside the QueueManage component */
const [buttonsState, setButtonsState] = useState({})
const updateButton = (itemId: string, buttonType: ButtonType) => {
setButtonsState({
...buttonsState,
[itemId]: {
...(buttonsState[itemId] || {}),
[buttonType]: {
...(buttonsState[itemId]?.[buttonType] || {}),
loading: true,
}
}
})
}
const isButtonLoading = (itemId: string, buttonType: ButtonType) => {
return buttonsState[itemId]?.[buttonType]?.loading
}
return (
<ul className="listCont">
{queue.map(queueItem => (
<li className="col-12" key={queueItem.id}>
<div className="row">
<div className="listName col-3">
<p>{queueItem.user.firstName} {queueItem.user.lastName}</p>
</div>
<div className="listName col-5">
<div className="row">
<div className="col-3">
<ActionButton
label={'Assign'}
onClick={() => updateButton(queueItem.id, ButtonType.ASSIGN)}
loading={isButtonLoading(queueItem.id, ButtonType.ASSIGN)}
/>
</div>
<div className="col-3">
<ActionButton
label={'Absent'}
onClick={() => updateButton(queueItem.id, ButtonType.ABSENT)}
loading={isButtonLoading(queueItem.id, ButtonType.ABSENT)}
/>
</div>
<div className="col-3">
<ActionButton
label={'Done'}
onClick={() => updateButton(queueItem.id, ButtonType.DONE)}
loading={isButtonLoading(queueItem.id, ButtonType.DONE)}
/>
</div>
<div className="col-3">
<ActionButton
label={'Cancel'}
onClick={() => updateButton(queueItem.id, ButtonType.CANCEL)}
loading={isButtonLoading(queueItem.id, ButtonType.CANCEL)}
/>
</div>
</div>
</div>
</div>
</li>
)
)}
</ul>
)
The goal here is to keep buttons loading state in parent component and manage it from here. buttonsState is a multilevel object like
{
'23': {
[ButtonType.ASSIGN]: { loading: false },
[ButtonType.ABSENT]: { loading: false },
[ButtonType.DONE]: { loading: false },
[ButtonType.CANCEL]: { loading: false },
},
...
}
where keys are ids of queueItems and values describe the state of the 4 buttons for that item. It is usually preferred to use useReducer instead of nested spreading in updateButton but it is good to start with

React bind function to each item inside array

I'm trying to make it so when you click on the dropdown arrow the settings dropdown will appear.
When I currently press an arrow dropdown, all the settings dropdown open that are within the array loop.
This is the function that renders the loop:
viewPublishedPages() {
const pages = this.state.pages;
return (
<div>
{pages.map((val, i) => {
let dropdown = 'none';
return (
<div className="block" key={i}>
<div className="columns">
<div className="column is-10">
<p>PUBLISHED</p>
<h2>{val.title}</h2>
</div>
<div className="column">
<div className="settings">
<div className="arrow__container">
<div className="arrow" onClick={this.showSettings.bind(this, i)} />
</div>
{
this.state.settingPanel
?
<ClickOutside onClickOutside={::this.hide}>
<div className="arrow__dropdown">
<Link href={{pathname: '/admin/edit-page', query: {title: val.title}}}>
<a className="arrow__dropdown__link">Edit</a>
</Link>
<button
className="arrow__dropdown__delete"
onClick={() => this.handleDelete(i)}>Delete</button>
</div>
</ClickOutside>
: null
}
</div>
</div>
</div>
</div>
);
})}
</div>
);
}
Notice: <div className="arrow" onClick={this.showSettings.bind(this, i)} />
This is the state:
static dataStruc () {
return {
loading: true,
settingPanel: false,
pages: [],
};
}
Your are currently saving a boolean value to settingPanel and therefore all dropdowns open upon click.
My suggestion is replace settingPanel from boolean to the respective page id. In case you don't have page ids, then store the current page index on it.
That makes it easier to render the dropdown so you have access/control to the selected one and later render its settings:
showSettings(index) {
this.setState({
settingPanel: index,
})
}
And then in viewPublishedPages:
{this.state.settingPanel === i &&
<ClickOutside onClickOutside={::this.hide}>
..
</ClickOutside>}
I wrote a sample code so you get the idea.
class App extends React.Component {
constructor() {
super()
this.state = {
pages: [
{ title: 'Home' },
{ title: 'Contact' },
{ title: 'Page' }
],
settingPanel: -1,
}
this.showSettings = this.showSettings.bind(this)
}
showSettings(index) {
this.setState({
settingPanel: this.state.settingPanel === index ? -1 : index,
})
}
render() {
const { pages, settingPanel } = this.state
return (
<div>
{pages.map((page, index) =>
<div key={index} className="page">
<div onClick={this.showSettings.bind(this, index)}>
{page.title}
</div>
{settingPanel === index &&
<div className="settings">
<div>Setting 1</div>
<div>Setting 2</div>
<div>Setting 3</div>
</div>
}
</div>
)}
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
.page {
background-color: cyan;
margin-top: 10px;
padding: 10px;
}
.settings {
background-color: black;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Programmatically cause onBlur to trigger in react

I use onBlur to close a dropdown, but I also want to handle a click handler of an li which is render within, setState won't work here, the behavior is broken when user try to open the dropdown again, try it here:
http://jsfiddle.net/ur1rbcrz
My code:
toggleDropdown = () => {
this.setState({
openDropdown: !this.state.openDropdown
})
}
render() {
return (
<div>
<div tabIndex="0" onFocus={this.toggleDropdown} onBlur={this.toggleDropdown}>
MyList
<ul className={this.state.openDropdown ? 'show' : 'hide'}>
<li>abc</li>
<li>123</li>
<li onClick={()=> this.setState({openDropdown:false})}>xyz</li> {/* not working */}
</ul>
</div>
</div>
);
}
Your code is not working because, even though you click li, a div container with onBlur event still is focused.
We add to your list container ref, after that we can call .blur(). We use it in your onClick li event handler.
this.dropDownList.blur()
See working example jsfiddle.
Or run this snippet:
class Hello extends React.Component {
constructor() {
super()
this.state = {
isDropdownVisible: false
}
this.toggleDropdown = this.toggleDropdown.bind(this);
}
toggleDropdown() {
this.setState({
isDropdownVisible: !this.state.isDropdownVisible
})
}
render() {
return (
<div>
<div
tabIndex="0"
ref={c => this.dropDownList = c}
onFocus={this.toggleDropdown}
onBlur={this.toggleDropdown}>
MyList
<ul
className={this.state.isDropdownVisible ? 'show' : 'hide'}>
<li>abc</li>
<li>123</li>
<li onClick={() => this.dropDownList.blur()}>xyz</li> {/* not working */}
</ul>
</div>
</div>
);
}
}
ReactDOM.render(
<Hello initialName="World"/>,
document.getElementById('container')
);
.hide {
display: none
}
.show {
display: block !important;
}
div:focus {
border: 1px solid #000;
}
div:focus {
outline: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
i added onClick event to your div and it worked, your code becomes:
render() {
return (
<div>
<div tabIndex="0" onClick={() => this.setState({openDropdown: !this.state.openDropdown})} onFocus={this.toggleDropdown} onBlur={this.toggleDropdown}>
MyList
<ul className={this.state.openDropdown ? 'show' : 'hide'}>
<li>abc</li>
<li>123</li>
<li onClick={()=> this.setState({openDropdown:false})}>xyz</li> {/* not working */}
</ul>
</div>
</div>
);
}
OnBlur is a React Synthetic event and can be used in two ways:
To trigger something:
const {useState} = React;
const Example = ({title}) => {
const [field, setField] = useState("");
return (
<div>
<p>{title}</p>
<p>Uppercase on blur</p>
<input type="text"
value={field}
onChange={e=>setField(e.target.value)}
//LOOK HERE !
onBlur={e=>setField(e.target.value.toUpperCase())}
/>
</div>
);
};
// Render it
ReactDOM.render(
<Example title="OnBlur triggering:" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Be triggered by something
const {
useState,
} = React;
const Example = ({
title
}) => {
const [field, setField] = useState("");
return ( <
div >
<
p > {
title
} < /p> <
p > Remove focus by pressing enter < /p> <
input type = "text"
value = {
field
}
onChange = {
e => setField(e.target.value)
}
//LOOK HERE !
onBlur = {
e => setField(e.target.value.toUpperCase())
}
onKeyPress = {
e => (e.key === 'Enter' ? setField(e.target.value.toLowerCase()) || e.target.blur() : null)
}
/> < /
div >
);
};
// Render it
ReactDOM.render( <
Example title = "OnBlur triggered:" / > ,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
So to programmatically cause onBlur to trigger in react is necessary add an event to watch your change.
More info:
React SyntheticEvents

Categories

Resources