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.
Related
I need help with this code. It is a website that shows users on the screen and the button on the right should open the user information details in a modal. I can't get the information of the user I click on, to appear in the modal. Show me the last one.
web photo
Foto of the website
Foto of the modal
And this is my code
import { Modal } from "antd";
import 'antd/dist/antd.css';
import { useEffect, useState } from "react"
import { userApi } from "../api/userApi";
import { Usuario } from "../interfaces/fetchAllUserResponse";
export const List = () => {
const [users, setUsers] = useState<Usuario[]>([]);
const [currentPage, setCurrentPage] = useState(0);
const [isModalOpen, setIsModalOpen] = useState(false);
useEffect(() => {
// call API
userApi.get<Usuario[]>('/default/techJobMission')
.then( resp => {
setUsers( resp.data );
})
.catch( console.log )
}, []);
const filteredUsers = (): Usuario[] => {
return users.slice(currentPage, currentPage + 6);
}
const nextUser = () => {
setCurrentPage( currentPage + 5 )
}
const prevUser = () => {
if( currentPage > 0 ){
setCurrentPage( currentPage - 5 )
};
}
const renderItem = ({ _id, firstName, lastName, ticket, present }: Usuario) => {
const openModal = () => {
setIsModalOpen(true)
}
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
return (
<tr key={ _id }>
<td >
{( present ? <img className="ticket" src="icons/ticket-green.svg"/> : <img className="ticket" src="icons/ticket-red.svg"/>)}
</td>
<td className="user">
<p className="encabezado">{firstName } {lastName}</p>
{( present ? <p className="estado">Ha entrado</p> : <p className="estado">No ha entrado</p> )}
</td>
<td className="id-user">
<p className="encabezado2">ID</p>
<p className="estado">{_id}</p>
</td>
<td className="ticket-td">
<p className="encabezado2">NÂș de ticket</p>
<p className="estado">{ticket + 1}</p>
</td>
<td>
<button onClick={openModal} className="btn-modal"> <img src="icons/menu-modal.svg"/> </button>
<Modal open={isModalOpen} onOk={handleOk} onCancel={handleCancel} >
<p>{firstName}</p>
</Modal>
</td>
</tr>
)
}
return (
<>
<table className="table">
<tbody>
{
filteredUsers().map( renderItem )
}
</tbody>
</table>
<br />
<button onClick={ prevUser } className="paginacion">
<img src="icons/anterior.svg"/> Anterior
</button>
<button onClick={ nextUser } className="paginacion">
Siguiente <img src="icons/siguiente.svg"/>
</button>
</>
)
}
It's opening all of the modals.
You have multiple modals and one boolean value indicating if "the modal" is open or not. So either they're all open or they're all not open.
Instead of tracking a single boolean value, track something like an identifier for which modal to display. For example:
const [modalID, setModalID] = useState();
And:
const openModal = (id) => {
setModalID(id)
}
const handleCancel = () => {
setModalID(undefined);
};
Then pass that id to the function from your record:
onClick={() => openModal(_id)}
And use it to determine if the modal is open:
open={modalID === _id}
When a user deletes a client, I have created a modal that asks the user(Are you sure you want to delete it).
The problem is as soon as the modal is open, the client is deleted, even though the user has not clicked yes to confirm the delete.
ClientList.js
export default function ListClients() {
const [showModal, setShowModal] = useState();
const [userlist, setUserlist] = useState([]);
function deleteClient() {
const userParams = {
clientName:
clientName,
country: country,
clientid: selectedID,
};
axios
.delete(process.env + "client", {
data: clientParams,
})
.then((response) => {
setClientlist(clientlist.filter((client) => client.id !== clientId));
})
.catch((error) => {
console.log(error);
});
// const openModal = () => {
// setShowModal(prev => !prev);
// };
}
return(
<div>
<tbody>
{userlist.length > 0 ? (
userlist.map((userlist) => (
<tr key={userlist.id}>
<td>
<div">
{userlist.id}
</div>
</td>
<td>
<button type="button" onClick={() => {deleteClient(clientlist.id); setShowModal(true)}}>
Delete
</button>
</td
</tr>
</tbody>
//the idea is to pass the state for modal to show
<ModalDelete showModal={showModal} setShowModal={setShowModal} onDel={() => deleteClient(clientlist.id)}/>
</div>
);
ModalDelete.js
export default function ModalDelete({ showModal, setShowModal,onDel }) {
return(
<div>
{ showModal ? <Transition.Root show={showModal}>
<div>
<p> Are you sure you want to delete the client?</p>
</div>
<div>
<button type="button" onClick={() => {onDel(); setShowModal(false);}>Yes</button>
<button type="button" onClick={() => {setShowModal(false);}} >
Go Back
</button>
</div>
</Transition.Root> : null }
</div>
);
}
When the user clicks yes in the modal, I want the client to be deleted.
How can I make it?
You are calling the deleteClient function in the event handler assigned to the delete button, before setting showModal to true. This causes the client to be deleted first and the modal to be displayed afterwards.
To fix this, you should update the event handler to not call deleteClient. Instead, define a function in the ListClient component which will close the modal and call the deleteClient function. Pass this as the onDel prop to your ModalDelete component.
(Edit: Fixed grammar)
I am displaying a todo list, every todo have an edit button that triggers a modal. From the modal (child component), you can update the description and send the new description to the database.
In the parent component there is update and setUpdate hook. I am sending both through props so that I can change the update value to the opposite when click save in the modal.
I was expecting the parent component to re render and make the axios call again when using setUpdate in the child component, this way when I click save button in modal, the list of todos will show the updated todos. But is not working.
I do not understand why the parent component do not re render if I am changing update state using setUpdate in the child component.
Thanks for your help.
Parent component to display todos
import axios from "axios";
import { Fragment, useState, useEffect } from "react";
import EditTodo from "./EditTodo";
const ListTodo = () => {
const [todos, setTodos] = useState(null);
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(false);
const [update, setUpdate] = useState(false);
useEffect(() => {
async function getData() {
try {
setIsPending(true);
const response = await axios.get("http://localhost:5000/todos");
setTodos(response.data);
setIsPending(false); // We have to changed back to false because the response finished and the data is in todos state
} catch (err) {
setError(err.message); // I am changing the error state to the message that comes from the backend if there is an error
setIsPending(false); // I am changing back to false because the call and response ended and throw an error
}
}
getData();
}, []);
const handleDelete = async (id) => {
try {
const response = await axios.delete(`http://localhost:5000/todos/${id}`);
console.log(response.data);
setTodos(todos.filter((todo) => todo.todo_id !== id)); //This is the way to delete, so that the component can rerender once the todos state is changed
} catch (err) {
setError(err.message);
}
};
console.log(todos);
return (
<Fragment>
{isPending && <p>Loading...</p>}
{error && <p>{error}</p>}
{todos && (
<table className="table mt-5">
<thead>
<tr>
<th scope="col">Description</th>
<th scope="col">Edit</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>
{todos.map((todo) => {
return (
<Fragment key={todo.todo_id}>
<tr>
<th scope="row">{todo.description}</th>
<td>
<EditTodo
todo={todo}
update={update}
setUpdate={setUpdate}
/>
</td>
<td>
<button
className="btn btn-primary"
onClick={() => handleDelete(todo.todo_id)}
>
Delete
</button>
</td>
</tr>
</Fragment>
);
})}
</tbody>
</table>
)}
</Fragment>
);
};
export default ListTodo;
Child component to update todo
import axios from "axios";
import React, { Fragment, useState } from "react";
import "./EditTodo.css";
const EditTodo = ({ todo, update, setUpdate }) => {
const [showModal, setShowModal] = useState(false);
const [description, setDescription] = useState(todo.description);
const handleSubmit = async (e) => {
e.preventDefault();
try {
const updatedTodo = { description };
console.log(updatedTodo);
const response = await axios.put(
`http://localhost:5000/todos/${todo.todo_id}`,
updatedTodo
);
setUpdate(!update);
setShowModal(false);
// window.location = "/";
} catch (err) {
console.error(err.message);
}
};
return (
<Fragment>
<button
type="button"
className="btn btn-primary"
data-bs-toggle="modal"
data-bs-target="#exampleModal"
onClick={() => setShowModal(true)}
>
Edit
</button>
{showModal && (
<div className="modal-background">
<div className="modal-content">
<div
style={{
display: "flex",
justifyContent: "flex-end",
}}
onClick={() => setShowModal(false)}
>
<p
style={{
cursor: "pointer",
}}
>
x
</p>
</div>
<div>
<h2>Input Todo</h2>
<form className="d-flex flex-column" onSubmit={handleSubmit}>
<input
type="text"
placeholder="edit todo"
className="form-control"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<br />
<button className="btn btn-success">Save</button>
</form>
</div>
</div>
</div>
)}
</Fragment>
);
};
export default EditTodo;
Your component is rerendering, but the effect that gets the list of todos is not dependent on the update state, so it is not be ran again when update changes. You might want to read more into how the dependency array works with the useEffect hook for more information about why that is, but shortly, an empty dependency array means that an effect will be called only twice, on mount and dismount.
Moving on to how to fix your problem, lift the logic that retrieves the whole list of todos and sets the state into its own function. Call that function in the effect, and pass the function to the child, call it after the update to the child is complete.
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>
);
};
I'm trying to re render my HTML after I have clicked on a badge:
onClick={() => filterCategories(c.category)}
The code:
const BlogPage = props => {
console.log(props);
//After I click on the button comes back with the correct posts.
let posts =
props.data.allContentfulPost !== undefined
? props.data.allContentfulPost.edges
: props.data;
const categoriesStyle = {
marginBottom: '8px',
};
const filterCategories = category => {
posts = posts.filter(p => p.node.category === category);
BlogPage({ data: posts });
};
return(
<span tabIndex="0" key={c.id}
onClick={() => filterCategories(c.category)}
role="button"
onKeyDown={filterCategories}>
<Badge value={c.category} category={c.category} color="#fff">
{c.category}
</Badge>
</span>
)
}
So if I'm not mistaking I have to make use of setState, but when I try to use it I can't do it because is not a class and I will need a class to add the constructor as well right? The problem with this code is that it was writing by someone else using Gatsby and I'm new to React and Gatsby
I'd recommend you maintain the category in state and then filter posts based on that stateful category.
const BlogPage = props => {
const [category, setCategory] = useState(null);
const posts =
props.data.allContentfulPost !== undefined
? props.data.allContentfulPost.edges
: props.data;
const categoriesStyle = {
marginBottom: '8px',
};
const filterCategories = category => {
setCategory(category)
};
// These are your filtered posts
const filteredPosts = category ?
posts.filter(p => p.node.category === category) :
posts;
return(
<span tabIndex="0" key={c.id}
onClick={() => filterCategories(c.category)}
role="button"
onKeyDown={filterCategories}>
<Badge value={c.category} category={c.category} color="#fff">
{c.category}
</Badge>
</span>
)
}