working with user blocking system in react - reactjs

I am making a web app like Tinder. Currently, I am working with user blocking. I have a "blocked" table in the database. In my application, A user already can block another user. Now, I want to hide the users in the search result if the logged user already blocked them.
Here is the code I have. This code renders for infinity (it doesn't stop). Can anyone tell me how can I make it working?
const ListOfUsers = ({ users }) => {
//console.log('users list rendered', users)
var coords = JSON.parse(window.localStorage.getItem('loggedMatchaUser'));
var from_user_id = coords.user_id;
const [check, setCheck] = useState(0)
const checkBlock = (to_user_id) => {
blockService.blockedUser({from_user_id, to_user_id})
.then(res => {
//If this row exist in the table it return 1 otherwise 0
setCheck(res.value);
})
.catch(e => {
console.log(("Error: couldn't get block info"))
})
}
return (
users && users.length > 0
? <ListGroup className="text-left" variant="flush">
{users.map(u => <Link to={`/users/${u.user_id}`} key={u.user_id}>
{checkBlock(u.user_id)}
{!check &&
<ListGroup.Item>
<div style={{display: "inline-block", width: "60%"}}>{u.username}, {u.age.years}</div>
<div style={{display: "inline-block", width: "40%", textAlign: "right"}}>{parseInt(u.distance)} km<br />
<FontAwesomeIcon icon={faAward} /> {u.fame}</div>
</ListGroup.Item>
}
</Link>)}
</ListGroup>
: <div className="text-info">Could not find any matching users<br />please try different filters</div>
)
}

You are issuing a side-effect (i.e. calling a function that updates state) in the render return.
const ListOfUsers = ({ users }) => {
...
const [check, setCheck] = useState(0);
const checkBlock = (to_user_id) => {
blockService
.blockedUser({ from_user_id, to_user_id })
.then((res) => {
//If this row exist in the table it return 1 otherwise 0
setCheck(res.value); // <-- updates state
})
.catch((e) => {
console.log("Error: couldn't get block info");
});
};
return users && users.length > 0 ? (
<ListGroup className="text-left" variant="flush">
{users.map((u) => (
<Link to={`/users/${u.user_id}`} key={u.user_id}>
{checkBlock(u.user_id)} // <-- function call updates state
...
</Link>
))}
</ListGroup>
) : (
...
);
};
You'll likely want to use an effect hook to check blocked users when the users prop updates. You'll need to "preprocess" your users array and augment it with the blocked status, or simply filter them out, which is probably easier anyway. The reason is because you can't use a single check state value for every user being rendered.
const ListOfUsers = ({ users }) => {
const coords = JSON.parse(window.localStorage.getItem("loggedMatchaUser"));
const from_user_id = coords.user_id;
const [nonBlockedUsers, setNonBlockedUsers] = useState([]);
useEffect(() => {
const checkBlock = (to_user_id) =>
// If this row exist in the table it return 1 otherwise 0
blockService.blockedUser({ from_user_id, to_user_id });
Promise.all(users.map(({ user_id }) => checkBlock(user_id))).then(
(blockedUserStatus) => {
const nonBlockedUsers = [];
users.forEach((user, index) => {
if (!blockedUserStatus[index]) nonBlockedUsers.push(user);
});
setNonBlockedUsers(nonBlockedUsers);
}
);
}, [from_user_id, users]);
return nonBlockedUsers.length > 0 ? (
<ListGroup className="text-left" variant="flush">
{nonBlockedUsers.map((u) => (
<Link to={`/users/${u.user_id}`} key={u.user_id}>
<ListGroup.Item>
<div style={{ display: "inline-block", width: "60%" }}>
{u.username}, {u.age.years}
</div>
<div
style={{
display: "inline-block",
width: "40%",
textAlign: "right"
}}
>
{parseInt(u.distance)} km
<br />
<FontAwesomeIcon icon={faAward} /> {u.fame}
</div>
</ListGroup.Item>
</Link>
))}
</ListGroup>
) : (
<div className="text-info">
Could not find any matching users
<br />
please try different filters
</div>
);
};

Related

How save style in the local storage

I have such a project. Here I want the button border save in the local storage.The buttons are divided into categories. For example when you refresh the page after selecting a sports button, the border of the button disappears. I want save btn border in the localstorage. I saved the categories in memory, but I can't make the border of the selected button.How can I fix it?
import React, { useEffect, useState } from "react";
import SpinnerLoad from './components/SpinnerLoad'
import NewsItem from "./components/NewsItem";
import Category from "./components/data/Category"
const App = () => {
const [state, setState] = useState([]);
const [loading, setLoading] = useState(false)
const [selected, setSelected] = useState('');
const fetchValue = (category, index) => {
localStorage.setItem("category", category);
localStorage.setItem("selected", index);
fetch(`https://inshorts-api.herokuapp.com/news?category=${category}`)
.then(res => res.json())
.then(res => {
setState(res.data)
setLoading(true)
})
.catch((error) => console.log(error))
setLoading(false);
};
const CategoryButton = ({ category, i }) => (
// passing index --> i to the fetch Value
<button onClick={() =>{ fetchValue(category,i) ; setSelected(i)} }
style={{border : selected === i ? '1px solid red' : null}} >{category}</button>
);
useEffect(() => {
let categoryValue = localStorage.getItem("category") || "all";
fetchValue(categoryValue)
const select = localStorage.getItem("selected") || "";
setSelected(select);
}, []);
return (
<>
<div className="header-bg">
<h1 className="mb-3">News</h1>
<div className="btns ">
{Category.map((value,i) => {
return <CategoryButton category={value} i={i}/>;
})}
</div>
</div>
<div className="news">
<div className="container">
<div className="row">
{
!loading
? <SpinnerLoad />
:
state.map((data, index) => {
return (
<NewsItem
imageUrl={data.imageUrl}
author={data.author}
title={data.title}
content={data.content}
date={data.date}
key={data.id}
/>
);
})
}
</div>
</div>
</div>
</>
);
};
export default App;
According to the code looks like you want to display data specific to a category set when the user clicks on the category buttons. and after the click, the correct data is rendered and the current category button receives a change in its style highlighting it is the current state.
I don't understand why you need to store anything in a client's localstorage,
I would not recommend storing too much in localStorage as it is limited and is used by different sites a user visits, I only store authentication tokens in localstorage and I believe that is the norm.
I've tried to create the effect you want without the need to store in local storage
import React, { useState, useCallback, useEffect } from "react";
import ReactDOM from "react-dom";
import { cat } from "../categories.js";
import { news } from "../news.js";
function Example() {
const [state, setState] = useState([]);
const [loading, setLoading] = useState(false);
const [selected, setSelected] = useState(null);
useEffect(() => {
function fetchFunction() {
setLoading(true);
for (let i = 0; i < news.length; i++) {
if (news[i].id === selected) {
const current = news[i].c;
setState(current);
}
}
setLoading(false);
}
fetchFunction();
}, [selected]);
return (
<>
<ol
style={{
width: "50%",
listStyle: "none",
display: "flex",
justifyContent: "space-between"
}}
>
{cat.map((item, index) => {
return (
<li key={index}>
<button
style={{ border: selected === item.id && "none" }}
onClick={() => {
setSelected(item.id);
}}
>
{item.name}
</button>
</li>
);
})}
</ol>
<section style={{ width: "100%", height: "70%" }}>
{state.map((item, index) => {
return (
<div
key={index}
style={{
width: "30%",
height: "30%",
background: "red",
display: "flex",
alignItems: "center",
justifyContent: "center",
margin: "1% 0 2% 0"
}}
>
{item.name}
</div>
);
})}
</section>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Example />, rootElement);
You can save the selectedIndex in localStorage and retrieve it in the useEffect..
const CategoryButton = ({ category, i }) => (
// passing index --> i to the fetch Value
// setting selected as string instead of index for type checking
<button onClick={() =>{ fetchValue(category,i) ; setSelected(`${i}`)} }
style={{border : selected === `${i}` ? '1px solid red' : null}} >{category}</button>
);
const fetchValue = (category, index) => {
localStorage.setItem("category", category);
localStorage.setItem("selected", index);
// ...
}
useEffect(() => {
const select = localStorage.getItem("selected") || "";
// passing selectedIndex to the fetchValue, otherwise it becomes
//undefined..
fetchValue(categoryValue,select)
setSelected(select);
},[])

How to get the index of items visible in the viewport?

folks, I am building a chat app. Currently, I am working on a feature in which when the user scrolls a sticky date is shown to the top, based on the date of the messages visible in the viewport area. But I don't know how to get the index of the last item visible in the viewport.
Here is the codesandbox
Here is the code
const MessageComponent = () => {
const { id } = useParams();
const {
data: messageData,
isSuccess,
isError,
isLoading,
isFetching,
hasMoreData,
firstData,
isUninitialized,
} = useGetData(id);
const { loadMore } = useGetScrollData(messageData, id);
const user = User();
const chatId =
messageData[0]?.type == "date"
? messageData[1]?.chat?._id
: messageData[0]?.chat?._id;
const islastItemChatId: boolean = messageData.length > 0 && chatId != id;
const scrollRef = useScrollRef(islastItemChatId, id);
const scrollFunc = (e: any) => {
// let m = e.target.scrollHeight + e.target.scrollTop;
// let i = e.target.scrollHeight - m;
// console.log({ i, e });
if (!scrollRef.current) return;
const containerMiddle =
scrollRef.current.scrollTop +
scrollRef.current.getBoundingClientRect().height / 2;
const infiniteScrollItems = scrollRef.current.children[0].children;
console.log({ containerMiddle, infiniteScrollItems, e: e.target });
};
return (
<>
{messageData.length > 0 && (
<div
id="scrollableDiv"
style={{
height: "80%",
overflow: "auto",
display: "flex",
flexDirection: "column-reverse",
padding: "10px 0px",
}}
ref={scrollRef}
>
<InfiniteScroll
dataLength={messageData.length}
hasMore={hasMoreData}
onScroll={scrollFunc}
loader={
<div className="loading-container">
<div className="lds-ring">
<div></div>
</div>
</div>
}
endMessage={
<div className="message-container date">
<div className={`text-container sender large-margin`}>
<span>You have seen all the messages</span>
</div>
</div>
}
style={{ display: "flex", flexDirection: "column-reverse" }}
next={loadMore}
inverse={true}
scrollableTarget="scrollableDiv"
>
{messageData.map((item, index: number) => {
const isUserChat = item?.sender?._id === user._id;
const className =
item?.type == "date"
? "date"
: isUserChat
? "user-message"
: "sender-message";
const prevItem: IMessageData | null =
index < messageData?.length ? messageData[index - 1] : null;
const nextItem: IMessageData | null =
index < messageData?.length ? messageData[index + 1] : null;
return (
<Message
key={item._id}
item={item}
prevItem={prevItem}
className={className}
isUserChat={isUserChat}
index={index}
nextItem={nextItem}
/>
);
})}
</InfiniteScroll>
</div>
)}
</>
);
};
The recommended way to check the visibility of an element is using the Intersection Observer API.
The lib react-intersection-observer can help you to observe all the elements and map the indexes with some states.
However, for your use case, you can minimize the element to observe by only observing the visibility of where a new date starts and where the last message is for that date and setting the stickiness of the date label accordingly.
I implemented the solution here https://codesandbox.io/s/sticky-date-chat-ykmlx2?file=/src/App.js
I built an UI with this structure
------
marker -> observed element that signals when the block of a date starts going up
------
date label -> a label to show the date which has conditional stickiness (position: sticky)
------
date messages -> the block of messages for the same date
------
last date message -> observe the last message to know if a block of dates is still visible
------
This logic is contained in the MessagesBlock component
const MessagesBlock = ({ block }) => {
const [refDateMarker, inViewDateMarker] = useInView();
const [refLastMessage, inViewLastMessage, entryLastMessage] = useInView();
let styles = {};
// make the date sticky when the marker has already gone up
// and the last message is either visible or hasn't reached the viewport yet
if (
!inViewDateMarker &&
(inViewLastMessage ||
(!inViewLastMessage && entryLastMessage?.boundingClientRect?.y > 0))
) {
styles = { position: "sticky", top: 0 };
}
return (
<>
<div ref={refDateMarker} style={{ height: 1 }} />
<div
style={{
...styles,
// some more styles omitted for brevity
}}
>
{dayjs(block[0].createdAt).format("DD/MM/YYYY")}
</div>
{block.map((item, index) => {
...
return (
<Message
ref={index === block.length - 1 ? refLastMessage : null}
key={item._id}
item={item}
className={className}
isUserChat={isUserChat}
index={index}
/>
);
})}
</>
);
};
Of course, to achieve this, I had to change the data a little. I used the groupby function provided by lodash
import groupBy from "lodash.groupby";
...
const byDate = groupBy(data, (item) =>
dayjs(item.createdAt).format("DD/MM/YYYY")
);
And render the blocks in the scroll container
<InfiniteScroll
...
>
{Object.entries(byDate).map(([date, block]) => (
<MessagesBlock block={block} />
))}
</InfiniteScroll>
I had to make some style changes and omit some details from your version, but I hope you can add them to my provided solution.

Update Child state from Parent using Context in React

I have a few buttons and "view all" button. The individual buttons load the coresponding data of that index or will show all the data by clicking the "view all" button. Problem I am running into is when I click my "view all" button in the parent it's not updating the state in the child component. On mounting it works as normal but on event handler in the "view all" it doesn't update. Any thoughts on where I am going wrong here?
JS:
...
const Context = createContext(false);
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ id, styles, discription }) => {
const { activeViewAll, handleChange } = useContext(Context);
const [toggleThisButton, setToggleThisButton] = useState();
const handleClick = () => {
setToggleThisButton((prev) => !prev);
handleChange(discription, !toggleThisButton);
};
return (
<>
<Avatar
className={toggleThisButton && !activeViewAll ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(toggleThisButton)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item, idx) => (
<div key={idx}>Content {item}</div>
))}
</div>
);
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(false);
useEffect(() => {
setActiveViewAll(true);
setSelected([...data]);
}, []);
const handleChange = (val, action) => {
let newVal = [];
if (activeViewAll) {
selected.splice(0, 3);
setActiveViewAll(false);
}
if (action) {
newVal = [...selected, val];
} else {
// If toggle off, then remove content from selected state
newVal = selected.filter((v) => v !== val);
}
console.log("action", action);
setSelected(newVal);
};
const handleViewAll = () => {
console.log("all clicked");
setActiveViewAll(true);
setSelected([...data]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer styles={classes} selected={selected} />
</div>
</Context.Provider>
);
}
Codesanbox:
https://codesandbox.io/s/72166087-forked-jvn59i?file=/src/App.js:260-3117
Issue
The issue seems to be that you are mixing up the management of the boolean activeViewAll state with the selected state.
Solution
When activeViewAll is true, pass the data array as the selected prop value to the ToggleContainer component, otherwise pass what is actually selected, the selected state.
Simplify the handlers. The handleViewAll callback only toggles the view all state to true, and the handleChange callback toggles the view all state back to false and selects/deselects the data item.
function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]); // none selected b/c view all true
const [activeViewAll, setActiveViewAll] = useState(true); // initially view all
const handleChange = (val, action) => {
setActiveViewAll(false); // deselect view all
setSelected(selected => {
if (action) {
return [...selected, val];
} else {
return selected.filter(v => v !== val)
}
});
};
const handleViewAll = () => {
setActiveViewAll(true); // select view all
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected} // pass all data, or selected only
/>
</div>
</Context.Provider>
);
}
In the ToggleContainer don't use the array index as the React key since you are mutating the array. Use the element value since they are unique and changing the order/index doesn't affect the value.
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update
Since it is now understood that you want to not remember what was previously selected before toggling activeViewAll then when toggling true clear the selected state array. Instead of duplicating the selected state in the children components, pass the selected array in the context and computed a derived isSelected state. This maintains a single source of truth for what is selected and removes the need to "synchronize" state between components.
const ToggleItem = ({ id, styles, description }) => {
const { handleChange, selected } = useContext(Context);
const isSelected = selected.includes(description);
const handleClick = () => {
handleChange(description);
};
return (
<>
<Avatar
className={isSelected ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(isSelected)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update the handleChange component to take only the selected value and determine if it needs to add/remove the value.
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(true);
const handleChange = (val) => {
setActiveViewAll(false);
setSelected((selected) => {
if (selected.includes(val)) {
return selected.filter((v) => v !== val);
} else {
return [...selected, val];
}
});
};
const handleViewAll = () => {
setActiveViewAll(true);
setSelected([]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange, selected }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={d}>
<ToggleItem id={id} styles={classes} description={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected}
/>
</div>
</Context.Provider>
);
}

How to get Name in div in react js?

Code:-
const [myButtons, setmyButtons] = useState([]);
setmyButtons(response.data.Rundowns);//api using axios
let idx = [];
let [myDivs, setmyDivs] = useState([]);
const handleClick = (val, id) => {
idx.push(id);
console.log(idx);
const newArr = [...flipDivs];
console.log("newA", newArr);
newArr[idx].show = !flipDivs[idx].show;
console.log("flipDivs", flipDivs)
setFlipDivs(newArr);
};
myButtons.map((val, i) =>
`My Div # ${val}`
)
myDivs = [...Array(2).keys()].map(i => `My Div # ${i + 1}`);
const [flipDivs, setFlipDivs] = useState([
...myDivs.map(
(text, id) => ({ id, text, show: false })
)
]);
return (
<div className="allDivs">
<div style={{ color: "white" }}> Fixed Div </div>
{
flipDivs.filter(({ show }) => !!show).map(
({ text, id, show }, idx) => (
<div
key={idx}
className="simpleMargin rightBorder"
style={{ color: "white" }}
>
{text}
</div>
)
)
}
</div>
<div className="buttons">
{myButtons.map((val, id) => {
// console.log("myButtons",val);
return (
<Fragment key={id}>
<button
id={id}
className="simpleMargin"
onClick={() => handleClick(val, id)}
>
{val}
</button>
</Fragment>
)
})}
</div>
)
getting the btn name as according the api I fetch but when i click the button getting the Show in image below:-
when i added the myDivs = myButtons.map(i => ${i});
getting nothing to display
so I want that when I click the button same same will appear in div location.
How can i do that?
I am fetching the button name from api.
Please help.

Dropdown Item not making api call on first try

Im having an issue where when I select a new Dropdown Item the Item isnt saved or the api call isnt made until I select the item again. So if I select Sales from the the Dropdown menu and and refresh the old value will still be there. Ive consoled out the value of the state (permission and adminID) and as soon as I select a dropdown item they are both set to the state so Im not sure whats causing it not to set on the first try.
const UserCard = (props) => {
const [dropdownOpen, setDropdownOpen] = useState(false);
const [permission, setPermission] = useState()
const [adminID, setAdminID] = useState()
const item = props.item;
const toggle = () => {
setDropdownOpen(prevState => !prevState)
};
const adminUpdate = (perm, id) => {
setPermission(perm)
if(!permission || !adminID){
return
}else{
api.UserManagement.put(
adminID,
{permissions: [permission]}
).catch(err => {
console.log("error", err, err.status)
})
}
}
console.log(permission, adminID)
return (
<>
<tr key={item.id}>
<td>{item.name || "-"}</td>
<td>{item.email}</td>
<td>{!permission ? item.permissions : permission}</td>
<td>
<Dropdown style={{ fontSize: "30px",borderColor: "white", backgroundColor: "white", color: "gray"}} isOpen={dropdownOpen} toggle={toggle}>
<DropdownToggle
tag="span"
data-toggle="dropdown"
aria-expanded={dropdownOpen}
>
...
</DropdownToggle>
<DropdownMenu onClick={() => setAdminID(item.id)} key={item.id}>
<DropdownItem value={"Admin"} onClick={e => adminUpdate(e.target.value)}>Admin</DropdownItem>
<DropdownItem value={"Sales"} onClick={e => adminUpdate(e.target.value)}>Sales</DropdownItem>
<DropdownItem value={"Medical"} onClick={e => adminUpdate(e.target.value)}>Medical</DropdownItem>
</DropdownMenu>
</Dropdown>
</td>
</tr>
</>
);
};
const UserList = ({}) => {
const [list, setList] = useState([]);
const [errors, setErrors] = useState();
useEffect(() => {
api.UserManagement.list()
.then((result) => {
let list = [];
if (result) {
list = result.items;
}
setList(list);
})
.catch((err) => {
if (err.status) {
console.log("error", err, err.status);
setErrors([err]);
}
});
}, []);
return (
<>
<ContentRow>
<Row>
<Col md="8">
<h1 style={{ marginTop: "25px" }}>
<Link to={"/"}>
<Button color="link"><</Button>
</Link>
<span style={{ fontSize: "25px" }}>User Management</span>
</h1>
</Col>
<div
className="d-flex justify-content-around align-items-stretch"
style={{ marginTop: "15px" }}
>
<Link to={"/invitations"}>
<Button className="header-button" block color="primary">
Invite Users
</Button>
</Link>
</div>
<Col md="4"></Col>
</Row>
</ContentRow>
<ContentRow>
<ErrorList errors={errors}/>
<Table>
<thead>
<tr>
<th width="30%">User</th>
<th width="30%">Email Address</th>
<th width="30%">Role</th>
<th width="10%"></th>
</tr>
</thead>
<tbody>
{!list ? (
<PageLoadSpinner />
) : (
list.map((item) => <UserCard item={item} />)
)}
</tbody>
</Table>
</ContentRow>
</>
);
};
export default UserList;
Reason
The code is not working due to improper usage of useState hook. useState is asynchronous in nature. Due to this, the variables are not updated with proper values when you are using it. The current code is assuming variables to be updated in synchronous manner.
setPermission(perm) is done in one line and permission variable is used in next line. But permission variable won't be updated at that time.
Same seems for admin variable.
That's why for the first time, the code doesn't work (the variables are not updated). But when you click again, the older values are picked; condition is satisfied; making it work.
Read more about it here.
Fix
There can be two ways in which this code can be fixed.
Approach 1
In the if condition, you can use the function arguments directly instead of variable from use state.
The relevant fixed code will be:
const UserCard = (props) => {
...
const adminUpdate = (perm, id) => {
if(!perm || !id){ // Change this condition here
return
}else{
api.UserManagement.put(
id,
{permissions: [perm]} // Change here as well
).catch(err => {
console.log("error", err, err.status)
})
}
}
return (
<>
...
</DropdownToggle>
<DropdownMenu onClick={() => setAdminID(item.id)} key={item.id}>
// Pass id onClick of DropdownItem also
<DropdownItem value={"Admin"} onClick={e => adminUpdate(e.target.value, item.id)}>Admin</DropdownItem>
<DropdownItem value={"Sales"} onClick={e => adminUpdate(e.target.value, item.id)}>Sales</DropdownItem>
<DropdownItem value={"Medical"} onClick={e => adminUpdate(e.target.value, item.id)}>Medical</DropdownItem>
</DropdownMenu>
</Dropdown>
...
</>
);
};
This approach almost removes the usage of useState hook as you are directly using variables passed from function
Approach 2
You can also use useEffect hook along with useState. useEffect takes dependency array as second argument and will trigger function call when any of the variable is changed. So, the logic will be splitted into 2 parts:
Variables will be updated separately by useState
Api call will be triggered when any of variable is updated in useEffect.
The relevant fixed code will be:
const UserCard = (props) => {
const [dropdownOpen, setDropdownOpen] = useState(false);
const [permission, setPermission] = useState()
const [adminID, setAdminID] = useState()
const item = props.item;
const toggle = () => {
setDropdownOpen(prevState => !prevState)
};
useEffect(() => {
if (permission && adminID) {
api.UserManagement.put(
adminID,
{permissions: [permission]}
).catch(err => {
console.log("error", err, err.status)
})
}
}, [permission, adminID]) // This will trigger the function call when any of these 2 variables are modified.
return (
<>
...
</DropdownToggle>
// setAdminID on click
<DropdownMenu onClick={() => setAdminID(item.id)} key={item.id}>
// setPermission onClick
<DropdownItem value={"Admin"} onClick={e => setPermission(e.target.value)}>Admin</DropdownItem>
<DropdownItem value={"Sales"} onClick={e => setPermission(e.target.value)}>Sales</DropdownItem>
<DropdownItem value={"Medical"} onClick={e => setPermission(e.target.value)}>Medical</DropdownItem>
</DropdownMenu>
</Dropdown>
</td>
</tr>
</>
);
};
Approach 1 vs Approach 2
It's upto you which approach to chose from. Both will get you the result but differ in base approach. Approach 1 is triggering the API call directly on click of dropdown item but Approach 2 is updating the variables on click of dropdown items and the change of those variables is triggering the API call.
Hope it helps. Revert for any doubts/clarifications.

Resources