Identify Clicked Button In Unordered List: React - javascript

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

Related

How to create "Selected tab" in Next.Js?

I am trying to create selected tab in Next.Js.
The User will have the option to search for data it can be Users or Posts, what the user will search for will be selected by clicking on one of the buttons.
Once the user clicks on the button the button will change background to blue.
However I can't make it to work properly, when the User clicks on the button the .Selected class gets added to the button but the button doesn't render the CSS.
import React, { MouseEventHandler, ReactElement, useState } from 'react'
import { PageWithLayout } from '../components/Layouts/LayoutConfig'
import MainLayout from '../components/Layouts/MainLayout'
import style from '../styles/Search.module.css'
const Search: PageWithLayout = () => {
const [searchPosts, setPostsSearch] = useState < String > ();
const setSearchOption = (searchFor: String) => {
let searchOption = '';
if (searchFor == 'POSTS') {
searchOption = 'POSTS';
} else {
searchOption = 'USERS';
let button = document.getElementById('usersOption') as HTMLElement;
button.className += style.Selected;
}
console.log(searchOption);
setPostsSearch(searchOption);
}
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...' />
</div>
<div className={style.SearchOptions}>
<button id='usersOption' onClick={() => setSearchOption('USERS')}>Users</button>
<button id='postsOption' onClick={() => setSearchOption('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)
}
Search.getLayout = function getLayout(page: ReactElement) {
return (
<MainLayout>
{page}
</MainLayout>
)
}
export default Search
you can use searchOption data for className style
import React, { MouseEventHandler, ReactElement, useState } from 'react'
import { PageWithLayout } from '../components/Layouts/LayoutConfig'
import MainLayout from '../components/Layouts/MainLayout'
import style from '../styles/Search.module.css'
const Search: PageWithLayout = () => {
const [searchPosts, setPostsSearch] = useState<String>();
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...'/>
</div>
<div className={style.SearchOptions}>
<button id='usersOption' className={searchPosts === 'USERS' ? style.Selected : undefined } onClick={() => setPostsSearch('USERS')}>Users</button>
<button id='postsOption' className={searchPosts === 'POSTS' ? style.Selected : undefined } onClick={() => setPostsSearch('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)
}
Search.getLayout = function getLayout(page: ReactElement){
return(
<MainLayout>
{page}
</MainLayout>
)
}
export default Search
Just have a state for active searchOption and apply the class conditionally directly into the JSX.
const [activeSearchOption, setActiveSearchOption] = useState('USERS')
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...'/>
</div>
<div className={style.SearchOptions}>
<button id='usersOption' className={activeSearchOption === 'USERS' ? 'active' : ''} onClick={() => setSearchOption('USERS')}>Users</button>
<button id='postsOption' className={activeSearchOption === 'POSTS' ? 'active' : ''} onClick={() => setSearchOption('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)

Change the state of arrows in a dropdown list

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

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)
...}

Modal dialog displays from all elements of mapped array. How to select each item by ts and react js?

This code:
How to display a dialog when a button is clicked using react and typescript?
I wanna open dialog from each todos, how to make it ? I used react js and typescript. Help me to resolve this problem.
interface ListProps {
todos: INote[];
onDelete: (title: string) => void;
}
const TodoList: React.FunctionComponent<ListProps> = ({ todos, onDelete }) => {
const [showAlert, setShowAlert] = useState(false);
const [todo, setTodos] = useState(null);
How to select each item by ts?It doesn't work. What is reason? Thanks!
const handleOpenDialog = (todos: any) => {
setTodos(todos);
setShowAlert(true);
};
const handleCloseDialog = () => {
setShowAlert(false);
};
return (
<>
<section className="list list--wrapper">
{todos.map((todos) => (
<div className="item list__item" key={todos.title}>
<span className="item__title">{todos.title}</span>
<div className="item__group">
<input
className="item__completed"
type="checkbox"
checked={todos.completed}
/>
<span className="item__decs">{todos.desc}</span>
</div>
<div className="item__btn">
<button
className="item__btnd"
onClick={() => handleOpenDialog(todos)}
>
Delete
</button>
<button className="item__btne">Edit</button>
</div>
{showAlert && todo && (
<AlertDialog
handleCloseDialog={handleCloseDialog}
title={todos.title}
/>
)}
</div>
))}
</section>
</>
);
};
export default TodoList;
just add a condition to only show the AlertDialog on selected todos
<section className="list list--wrapper">
{todos.map((todos) => (
<div className="item list__item" key={todos.title}>
<span className="item__title">{todos.title}</span>
<div className="item__group">
<input
className="item__completed"
type="checkbox"
checked={todos.completed}
/>
<span className="item__decs">{todos.desc}</span>
</div>
<div className="item__btn">
<button
className="item__btnd"
onClick={() => handleOpenDialog(todos)}
>
Delete
</button>
<button className="item__btne">Edit</button>
</div>
{showAlert && todos.title===todo?.title && (
<AlertDialog
handleCloseDialog={handleCloseDialog}
title={todos.title}
/>
)}
</div>
))}
</section>
or just move the AlertDialog outside the map
<section className="list list--wrapper">
{todos.map((todos) => (
<div className="item list__item" key={todos.title}>
<span className="item__title">{todos.title}</span>
<div className="item__group">
<input
className="item__completed"
type="checkbox"
checked={todos.completed}
/>
<span className="item__decs">{todos.desc}</span>
</div>
<div className="item__btn">
<button
className="item__btnd"
onClick={() => handleOpenDialog(todos)}
>
Delete
</button>
<button className="item__btne">Edit</button>
</div>
</div>
))}
{showAlert && todo && (
<AlertDialog
handleCloseDialog={handleCloseDialog}
title={todos.title}
/>
)}
</section>

React: Update component data/props after promise resolved

I'm a bit new to React and I've been having some problems understanding the workarounds of certain methods that I've used in other languages.
The Problem:
What I was hoping to achieve is that whenever the user clicks a button, the app will render a new section displaying some variable values. What I did was that when the user clicked a button, an state changed, and let the new Component render, and I passed the data through its props.
The problem is, If I understand correctly, that I'm passing the old values when I create the component and not the actual/updated values that I want to render...
Let's say I have this following variables.
const user_data = {
pic_url: 'null',
followers: 'Loading...',
followings: 'Loading...',
nTweets: 'Loading...',
};
Those variables are going to change value whenever the user click a button.
This next block of code is what I use to render the next component where I want the new values.
const SomeComponent = props => {
const [resolved, setResolved] = useState({ display: false });
const displayValidation = props => {
setResolved({ ...resolved, display: !resolved.display });
};
function getData(username) {
const url = 'https://www.twitter.com/' + username;
getHTML(url)
.then(res => {
getUserData(res).then(res => {
user_data.followers = res.followers;
user_data.followings = res.followings;
user_data.nTweets = res.nTweets;
user_data.pic_url = res.pic_url;
console.log('Updated data:', user_data);
displayValidation();
});
})
.catch(function(error) {
console.error('Username was not found.');
});
}
const handleSubmit = event => {
event.preventDefault();
console.log('Resolving data...');
getData(user.username);
};
return (
<React.Fragment>
<Navbar />
<div className="container lg-padding">
<div className="row" id="getTracker">
<div className="col-sm-12 center">
<div className="card text-center hoverable">
<div className="card-body">
<div className="input-field">
<i className="material-icons prefix">account_circle</i>
<input
id="username"
type="text"
className="validate"
value={user.username}
onChange={handleChange}
/>
<label htmlFor="username">Enter a username to track</label>
</div>
<input
type="button"
onClick={handleSubmit}
value="Track"
className="btn-large blue darken-4 waves-effect waves-light"
/>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-sm-12">
**{resolved.display && <DisplayData type={1} data={user_data} />}**
</div>
</div>
</div>
<Footer />
</React.Fragment>
);
};
I want to see the new values, but it always render the first values that I passed when creating the component.
This is the component that I create
import React from 'react';
const DisplayData = props => {
const user = props.data;
console.log('Display', user);
switch (props.type) {
case 1: //Twitter
return (
<React.Fragment>
<div className="row lg-padding">
<div className="col-sm-12 lg-padding center">
<img
src={user.pic_url}
alt="profile_picture"
style={{ width: 50 + '%' }}
/>
</div>
<h2>{user.username}</h2>
</div>
<div className="row lg-padding">
<div className="col-sm-4">
<h4>Tweets: {user.nTweets}</h4>
</div>
<div className="col-sm-4">
<h4>Followers: {user.followers}</h4>
</div>
<div className="col-sm-4">
<h4>Followings: {user.followings}</h4>
</div>
</div>
</React.Fragment>
);
case 2: //Instagram
return <React.Fragment />;
default:
return (
<React.Fragment>
<div className="row lg-padding">
<div className="col-sm-12 lg-padding center">
<img
src={user.pic_url}
alt="profile_picture"
style={{ width: 50 + '%' }}
/>
<h2>Instagram_User</h2>
<h4>Posts: ND</h4>
<h4>Followers: ND</h4>
<h4>Followings: ND</h4>
</div>
</div>
</React.Fragment>
);
}
};
export default DisplayData;
How can I update the data in the component or render the component when the data is updated?
Maybe your user_data might to be a state object.
// Somecomponent ...
const [user_data, setUser_data] = useState({
pic_url: 'null',
followers: 'Loading...',
followings: 'Loading...',
nTweets: 'Loading...'
})
/* Rest of stuff */
const handleSubmit = async event => {
/*...*/
const userData = await getData(user.username)
setUser_data(userData)
}
// Then display the stated-stored user_data
<div className="col-sm-12">
**{resolved.display && <DisplayData type={1} data={user_data} />}**
</div>

Categories

Resources