Assign state to dynamically rendered component using map function - javascript

I have a table with players that is loaded in from an API call and rendered using a map function. The last column of the table contains a delete button. When clicking the delete button, I would like to disable all the other delete buttons until the delete call is completed.
Here is the function that performs the API call to get the players.
loadPlayers() {
const { cookies } = this.props;
const AuthStr = 'Bearer '.concat(this.state.access_token);
axios.get(
configVars.apiUrl + 'team/get-team',
{ headers: { Authorization: AuthStr } }
).then(response => {
var teamArray = response.data;
//Information gotten
this.setState({
teamArray: teamArray,
});
}).catch((error) => {
//Error, remove the access token from cookies and redirect home.
cookies.remove('access_token');
this.props.history.push("/");
});
}
}
Mapping and rendering is done like this:
<Grid item xs={12}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell align="right">Tier</TableCell>
<TableCell align="right">Value</TableCell>
<TableCell align="right">Delete</TableCell>
</TableRow>
</TableHead>
{this.state.teamArray.map((player, i) => {
return <TableBody key={i}>
<TableRow key={i}>
<TableCell align="left">{player.full_name}</TableCell>
<TableCell align="right">{player.tier}</TableCell>
<TableCell align="right">{player.value}</TableCell>
<TableCell align="right">
<IconButton disabled={this.state.deleteDisable}
onClick={() => {this.handleDelete(player.id)}} >
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
</TableBody>;
})}
</Table>
</Grid>
Inside the handleDelete function I start by setting deleteDisabledin the state to true. However, this has no effect since disabled is set to false once the table is loaded and never changed after.
How do I make sure this.state.deleteDisable is passed to the button as a variable instead of assigned once?

You should store the players into the state, then in the render method you can display the table
function loadPlayer() {
const { cookies } = this.props;
const AuthStr = 'Bearer '.concat(this.state.access_token);
axios.get(
configVars.apiUrl + 'team/get-team',
{ headers: { Authorization: AuthStr } }
)
.then(response => this.setState({players: response.data})})
.catch((error) => {
// Error ....
});
}
render() {
return (
...
{
this.state.players((player, i) => (
<TableBody key={i}>
<TableRow>
<TableCell align="left">{player.full_name}</TableCell>
<TableCell align="right">{player.tier}</TableCell>
<TableCell align="right">{player.value}</TableCell>
<TableCell align="right">
<IconButton disabled={this.state.deleteDisabled}
onClick={() => {this.handleDelete(player.id)}} >
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
</TableBody>
)}
...
);
}

Related

How to render API data in ReactJs by unique ID?

I have a page where every data saved in the database is rendering in a table with very limited information, I have an action button (Detail) to view all the information for the particular company.
Code for the table:
const PendingApplication = () => {
let history = useHistory();
const [data, setData] = useState([]);
const handleClick = (location) => {
console.log(location);
history.push(location);
};
useEffect(() => {
axios
.get('http://localhost:5000/api/kam')
.then((response) => {
console.log(response);
setData(response.data.kamData);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<div className="content">
<Table>
<TableHead>
<TableRow>
<TableCell>Ticket No</TableCell>
<TableCell align="right">Category</TableCell>
<TableCell align="right">Sub Category</TableCell>
<TableCell align="right">Request Time & Date</TableCell>
<TableCell align="right">Company Name</TableCell>
<TableCell align="right">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((item, index) => (
<TableRow key={index}>
<TableCell>{item.ticketno}</TableCell>
<TableCell>{item.approvecategory}</TableCell>
<TableCell>{item.subcategory}</TableCell>
<TableCell>{item.date}</TableCell>
<TableCell>{item.companyname}</TableCell>
<TableCell>
<Button color="#71BD44" onClick={() => handleClick('/detail')}>
Detail
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
};
export default PendingApplication;
Here is the code for detail page:
const Details = () => {
const setPopup = useContext(SetPopupContext);
let history = useHistory();
const [data, setData] = useState([]);
const handleClick = (location) => {
console.log(location);
history.push(location);
};
useEffect(() => {
axios
.get('http://localhost:5000/api/kam/:id')
.then((response) => {
console.log(response);
setData(response.data.kamData);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<div className="content">
<Box
sx={{
width: '90%',
padding: '24px 20px', // theme padding
border: '1px solid rgba(0, 0, 0, 0.12)',
borderRadius: 4,
}}
>
<div className="ticket-details">
<h3>TICKET DETAILS</h3>
{data.map((item, index) => (
<TableRow>
<p>Ticket ID: {item.ticketno}</p>
<p>Category: {item.approvecategory}</p>
<p>Category: {item.subcategory}</p>
<p>Category: {item.date}</p>
</TableRow>
))}
</div>
<div className="additional-info">
<h3>ADDITONAL INFO</h3>
{data.map((item, index) => (
<TableRow>
<p>Company Name: {item.companyname}</p>
<p>KCP Name: {item.kcpname}</p>
<p>KCP Contact No: {item.kcpcontact}</p>
<p>KCP NID No: {item.kcpnid}</p>
<p>No of MSISDN: {item.msisdn}</p>
</TableRow>
))}
</div>
</Box>
</div>
);
};
export default Details;
I have created the API for unique ID, Here is the API:
router.get('/kam/:id', (req, res) => {
console.log(req.params.id);
kamForm
.findById(req.params.id)
.then((result) => {
res.status(200).json({
kamData: result,
});
})
.catch((err) => {
console.log(err);
res.status(500).json({
message: err,
});
});
});
After clicking the detail button i want that particular info in detail page, can anyone help me, how to do that?
In your Database, you must have an id column, send that column also with your data into fetch API. In your code, you will get item.id, use that id for the handleClick button.
see below code.
const PendingApplication = () => {
let history = useHistory();
const [data, setData] = useState([]);
const handleClick = (id) => {
console.log(id);
//use id here
history.push(location);
};
useEffect(() => {
axios
.get('http://localhost:5000/api/kam')
.then((response) => {
console.log(response);
setData(response.data.kamData);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<div className="content">
<Table>
<TableHead>
<TableRow>
<TableCell>Ticket No</TableCell>
<TableCell align="right">Category</TableCell>
<TableCell align="right">Sub Category</TableCell>
<TableCell align="right">Request Time & Date</TableCell>
<TableCell align="right">Company Name</TableCell>
<TableCell align="right">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((item, index) => (
<TableRow key={index}>
<TableCell>{item.ticketno}</TableCell>
<TableCell>{item.approvecategory}</TableCell>
<TableCell>{item.subcategory}</TableCell>
<TableCell>{item.date}</TableCell>
<TableCell>{item.companyname}</TableCell>
<TableCell>
<Button color="#71BD44" onClick={() => handleClick(item.id)}>
Detail
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
};
export default PendingApplication;

How do I map over data from API in a collapsible table?

I don't know if the question captures what I had in my but I will explain below...
I fetched data from API and mapped into a collapsible table. Full details of the data should be embedded in EACH row such that onclick on each row, reveals the full details. Here's the code below
function Encounter() {
const [open2, setOpen2] = useState(false);
const [details, setDetails] = useState([]);
const getDetails = async () => {
try {
const fetch = await Axios.get(
"https://pshs3.herokuapp.com/all/encounter"
);
setDetails(fetch.data.data)
} catch (err) {
console.log(err);
}
};
useEffect(() => {
getDetails();
}, []);
return (
<Wrapper>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
<TableCell/>
<TableCell >
Enrollment ID
</TableCell>
<TableCell >
Encounter
</TableCell>
<TableCell>
Facility Code
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{details.map((detail, idx) => {
return (
<>
<TableRow sx={{ "& > *": { borderBottom: "unset" } }}>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</TableCell>
<TableCell key={idx}>
{detail.enrollment_id}
</TableCell>
<TableCell key={idx}>
{detail.encounter}
</TableCell>
<TableCell key={idx}>
{detail.faciity_code}
</TableCell>
</TableRow>
<TableRow>
<TableCell
style={{ paddingBottom: 0, paddingTop: 0 }}
colSpan={6}
>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box sx={{ margin: 1 }}>
<Typography variant="h6" gutterBottom component="div">
Details
</Typography>
<Tooltip />
</Box>
</Collapse>
</TableCell>
</TableRow>
</>
);
})}
</TableBody>
</Table>
</Wrapper>
);
}
export default Encounter;
The problem I have is how to implement the open and setOpen state to individual row, also the Tooltip component(which is a the full table details from the API) to display full details of each row onclick which should correspond to the selected row in question.
Here are two solutions to the first problem.
1. Create a separate component for each <TableRow />
This component will have its own state and allows you to collapse/expand each row individually.
2. Use a dictionary for the open state
Since you have multiple (dynamic) rows, you can introduce a dictionary for the open state.
const [open, setOpen] = useState({});
For each row, you will use the open[idx] property to determine if the row should be "open"
<Collapse in={open[idx]} timeout="auto" unmountOnExit>
And in the <IconButton /> component, set the state based on the current row state.
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(current => ({ ...current, [idx]: !current[idx] }))}
>
Firstly add a variable in your details object:
const getDetails = async () => {
try {
const fetch = await Axios.get(
"https://pshs3.herokuapp.com/all/encounter"
);
const response = fetch.data.data;
response.map((elem) => elem.open = false)
setDetails(response)
} catch (err) {
console.log(err);
}
};
Then you can change the open variable for each element in details:
<IconButton
aria-label="expand row"
size="small"
onClick={() => detail.open = !detail.open)}
>
You might need to update the state, so change your onClick:
onClick={() => changeOpenStatus(idx))}
and the function:
const changeOpenStatus = (idx) => {
const newDetails = {...details}
newDetails[idx].open = !newDetails[idx].open;
setDetails(newDetails)
}

Selection Checkbox in React using Hooks

I have a problem selecting a single checkbox or multiple checkbox in a table in React. I'm using Material-UI. Please see my codesandbox here
CLICK HERE
I wanted to achieve something like this in the picture below:
<TableContainer className={classes.tableContainer}>
<Table>
<TableHead className={classes.tableHead}>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
checked={false}
inputProps={{ "aria-label": "select all desserts" }}
/>
</TableCell>
{head.map((el) => (
<TableCell key={el} align="left">
{el}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{body?.excluded_persons?.map((row, index) => (
<TableRow key={row.id}>
<TableCell padding="checkbox">
<Checkbox checked={true} />
</TableCell>
<TableCell align="left">{row.id}</TableCell>
<TableCell align="left">{row.name}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
Seems you are just missing local component state to track the checked status of each checkbox, including the checkbox in the table header.
Here is the implementation for the AddedPersons component since it's more interesting because it has more than one row of data.
Create state to hold the selected persons state. Only add the additional local state, no need to duplicate the passed body prop data (this is anti-pattern anyway) nor add any derived state, i.e. is indeterminate or is all selected (also anti-pattern).
const [allSelected, setAllSelected] = React.useState(false);
const [selected, setSelected] = React.useState({});
Create handlers to toggle the states.
const toggleAllSelected = () => setAllSelected((t) => !t);
const toggleSelected = (id) => () => {
setSelected((selected) => ({
...selected,
[id]: !selected[id]
}));
};
Use a useEffect hook to toggle all the selected users when the allSelected state is updated.
React.useEffect(() => {
body.persons?.added_persons &&
setSelected(
body.persons.added_persons.reduce(
(selected, { id }) => ({
...selected,
[id]: allSelected
}),
{}
)
);
}, [allSelected, body]);
Compute the selected person count to determine if all users are selected manually or if it is "indeterminate".
const selectedCount = Object.values(selected).filter(Boolean).length;
const isAllSelected = selectedCount === body?.persons?.added_persons?.length;
const isIndeterminate =
selectedCount && selectedCount !== body?.persons?.added_persons?.length;
Attach all the state and callback handlers.
return (
<>
<TableContainer className={classes.tableContainer}>
<Table>
<TableHead className={classes.tableHead}>
<TableRow>
<TableCell colSpan={4}>{selectedCount} selected</TableCell>
</TableRow>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
checked={allSelected || isAllSelected} // <-- all selected
onChange={toggleAllSelected} // <-- toggle state
indeterminate={isIndeterminate} // <-- some selected
inputProps={{ "aria-label": "select all desserts" }}
/>
</TableCell>
...
</TableRow>
</TableHead>
<TableBody>
{body?.persons?.added_persons?.map((row, index) => (
<TableRow key={row.id}>
<TableCell padding="checkbox">
<Checkbox
checked={selected[row.id] || allSelected} // <-- is selected
onChange={toggleSelected(row.id)} // <-- toggle state
/>
</TableCell>
<TableCell align="left">{row.id}</TableCell>
<TableCell align="left">{row.name}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</>
);
Update
Seems there was a bug in my first implementation that disallowed manually deselecting people while the select all checkbox was checked. The fix is to move the logic in the useEffect into the toggleAllSelected handler and use the onChange event to toggle all the correct states. Also to add a check to toggleSelected to deselect "select all" when any person checkboxes have been deselected.
const [allSelected, setAllSelected] = React.useState(false);
const [selected, setSelected] = React.useState({});
const toggleAllSelected = (e) => {
const { checked } = e.target;
setAllSelected(checked);
body?.persons?.added_persons &&
setSelected(
body.persons.added_persons.reduce(
(selected, { id }) => ({
...selected,
[id]: checked
}),
{}
)
);
};
const toggleSelected = (id) => (e) => {
if (!e.target.checked) {
setAllSelected(false);
}
setSelected((selected) => ({
...selected,
[id]: !selected[id]
}));
};
Note: Since both AddedPersons and ExcludedPersons components are basically the same component, i.e. it's a table with same headers and row rendering and selected state, you should refactor these into a single table component and just pass in the row data that is different. This would make your code more DRY.
I have updated your added person table as below,
please note that I am using the component state to update the table state,
const AddedPersons = ({ classes, head, body }) => {
const [addedPersons, setAddedPersons] = useState(
body?.persons?.added_persons.map((person) => ({
...person,
checked: false
}))
);
const [isAllSelected, setAllSelected] = useState(false);
const [isIndeterminate, setIndeterminate] = useState(false);
const onSelectAll = (event) => {
setAllSelected(event.target.checked);
setIndeterminate(false);
setAddedPersons(
addedPersons.map((person) => ({
...person,
checked: event.target.checked
}))
);
};
const onSelect = (event) => {
const index = addedPersons.findIndex(
(person) => person.id === event.target.name
);
// shallow clone
const updatedArray = [...addedPersons];
updatedArray[index].checked = event.target.checked;
setAddedPersons(updatedArray);
// change all select checkbox
if (updatedArray.every((person) => person.checked)) {
setAllSelected(true);
setIndeterminate(false);
} else if (updatedArray.every((person) => !person.checked)) {
setAllSelected(false);
setIndeterminate(false);
} else {
setIndeterminate(true);
}
};
const numSelected = addedPersons.reduce((acc, curr) => {
if (curr.checked) return acc + 1;
return acc;
}, 0);
return (
<>
<Toolbar>
{numSelected > 0 ? (
<Typography color="inherit" variant="subtitle1" component="div">
{numSelected} selected
</Typography>
) : (
<Typography variant="h6" id="tableTitle" component="div">
Added Persons
</Typography>
)}
</Toolbar>
<TableContainer className={classes.tableContainer}>
<Table>
<TableHead className={classes.tableHead}>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
checked={isAllSelected}
inputProps={{ "aria-label": "select all desserts" }}
onChange={onSelectAll}
indeterminate={isIndeterminate}
/>
</TableCell>
{head.map((el) => (
<TableCell key={el} align="left">
{el}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{addedPersons?.map((row, index) => (
<TableRow key={row.id}>
<TableCell padding="checkbox">
<Checkbox
checked={row.checked}
onChange={onSelect}
name={row.id}
/>
</TableCell>
<TableCell align="left">{row.id}</TableCell>
<TableCell align="left">{row.name}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</>
);
};
export default AddedPersons;
Please refer to this for a working example: https://codesandbox.io/s/redux-react-forked-cuy51

Render data as a functional component with React.js and Material-UI

I have a problem with the management of the data, when i try to render some data from the pokemon api my table render multiple times the titles, i tried to move only the data to a different component but not luck.
How can i fix this?
API CAll
export const PokemonApi = () => {
const [poke, setPoke] = useState([]);
const data = () => {
axios.get('https://pokeapi.co/api/v2/pokemon?limit=10&offset=20').then(( response ) => {
setPoke(response.data.results);
console.log(response.data.results);
})
.catch( err => {
console.log(err);
})
}
useEffect(() => {
data()
}, []);
return (
<>
{
poke.map(( info, name ) => {
return <Lista key={ name } info={ info } />
})
}
</>
)
}
component where I try to render
export const Lista = (props) => {
const classes = useStyles();
return (
<div>
<Container maxWidth="md">
<TableContainer component={Paper}>
<Table className={ classes.table } size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell align="right">URL</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow key={ props.info.name }>
<TableCell component="th" scope="row">
{ props.info.name }
</TableCell>
<TableCell align="right">{ props.info.url }</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Container>
</div>
)
}
This is the page where i render the table
import React from 'react'
import { PokemonApi } from '../api/PokemonApi'
export const Pokes = () => {
return (
<>
<PokemonApi />
</>
)
}
And here is the table.
I hope anyone can help me!
As your code is written, you are not rendering one table with a row for each line. You are creating one Lista par record, you have as many tables as pokemon.
What you are looking to achieve is more like :
function PokemonRow(props) {
return (
<TableRow key={ props.info.name }>
<TableCell component="th" scope="row">
{ props.info.name }
</TableCell>
<TableCell align="right">{ props.info.url }</TableCell>
</TableRow>
)
}
export const PokemonTable() {
const classes = useStyles();
const [poke, setPoke] = useState([]);
const data = () => {
axios.get('https://pokeapi.co/api/v2/pokemon?limit=10&offset=20').then(( response ) => {
setPoke(response.data.results);
console.log(response.data.results);
})
.catch( err => {
console.log(err);
})
}
useEffect(() => {
data()
}, []);
return (
<div>
<Container maxWidth="md">
<TableContainer component={Paper}>
<Table className={ classes.table } size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell align="right">URL</TableCell>
</TableRow>
</TableHead>
<TableBody>
{poke.map(infos => <PokemonRow info={infos}/>)}
</TableBody>
</Table>
</TableContainer>
</Container>
</div>
)
}

How to re-render a list of an array after an item is deleted? It give an error and doesn't re-renders

I am fetching a list of user from an API and I am trying to delete a user using "DELETE" method it gets delete but after the item is deleted it doesn't re-renders the list of the users, it give an error where I used map to map all the user in a table. The "DELETE" method is working properly it gives a 200 response but the maps doesn't work after that picks up the delete message.
Played around with the code tried different things which came to my mind but no luck.
Fetching users list :
export const getUsers = () =>{
let url ='https://api.net/api/users';
return dispatch => {
dispatch(initiateUserRequest(true));
api({
url: url,
method: 'GET'
})
.then(response=>{
dispatch(receiveUsers(response.data.data), false)
})
.catch(err=>{
dispatch(errorUsers("Something went wrong:"+ err.message))
})
}
};
Deleting the user :
export const deleteUser = (username) =>{
let url = `https://deletingusers/api/users/${username}`;
return dispatch => {
dispatch(initiateUserRequest(true));
api({
url: url,
method: 'DELETE',
})
.then(response=>{
dispatch(receiveUsers(response),false)
})
.catch(err=>{
dispatch(errorUsers("Something went wrong:"+ err.message))
})
}
};
Table where data is mapped tabledata.map :
<TableBody>
{tableData.map((prop, key) => {
return (
<TableRow key={key}>
<TableCell className={classes.tableCell}>
{prop.username}
</TableCell>
<TableCell className={classes.tableCell}>
{prop.name}
</TableCell>
<TableCell className={classes.tableCell}>
{prop.email}
</TableCell>
<TableCell className={classes.tableCell}>
{prop.phoneNumber}
</TableCell>
<TableCell className={classes.tableCell}>
{prop.zip}
</TableCell>
<TableCell className={classes.tableCell}>
{prop.address}
</TableCell>
<TableCell className={classes.tableCell}>
{prop.status}
</TableCell>
<TableCell className={classes.tableCell}>
{prop.mobile}
</TableCell>
<Tooltip
id="tooltip-top"
title="Edit Task"
placement="top"
classes={{ tooltip: classes.tooltip }}
>
<IconButton
aria-label="Edit"
className={classes.tableActionButton}
>
<Edit
onClick={() => this.props.onEditUser(prop)}
className={
classes.tableActionButtonIcon + " " + classes.edit
}
/>
</IconButton>
</Tooltip>
<Tooltip
id="tooltip-top-start"
title="Delete"
placement="top"
classes={{ tooltip: classes.tooltip }}
>
<IconButton
aria-label="Close"
className={classes.tableActionButton}
>
<Close
className={
classes.tableActionButtonIcon + " " + classes.close
}
onClick={() => this.props.onDeleteUser(prop.username)}
/>
</IconButton>
</Tooltip>
</TableRow>
);
})}
</TableBody>
Can add more code if needed.
You need to filter out the deleted username from the original users array in the deleteUser function by sending the tableData as an argument to the deleteUser.
export const deleteUser = (username,tableData) =>{ // send tableData overhere
let url = https://deletingusers/api/users/${username};
return dispatch => {
dispatch(initiateUserRequest(true));
api({
url: url,
method: 'DELETE',
})
.then(response=>{
const updatedTableData = tableData.filter(data => data.username !== username)
dispatch(receiveUsers(updatedTableData),false)
})
.catch(err=>{
dispatch(errorUsers("Something went wrong:"+ err.message))
})
}
};
Reducer and Action were missing for deleting an item, I was trying to delete it directly which was causing an error of not deleting it from the store. So I added an action and reducer for delete and included my DELETE method properly. I was deleting it using a username of the user which I selected. So I sent the selected user to my reducer through which the user was also deleted from the store and the api as well.
Reducer :
case types.DELETE_USER:
newState = {
usersData: {},
isFetching: true,
error: false
};
return{
...state, usersDetail: newState.usersData.filter( item => item.username !==action.username)
};
Action :
const deleteUsers =(username, isFetching) =>{
return{
type: types.DELETE_USER,
username,
isFetching
}
};
export const deleteUser = (username) =>{
let url = `www.api.com ${username}`;
return dispatch => {
dispatch(initiateUserRequest(true));
api({
url: url,
method: 'DELETE',
})
.then(response=>{
dispatch(deleteUsers(response),true)
})
.catch(err=>{
dispatch(errorUsers("Something went wrong:"+ err.message))
})
}
};

Categories

Resources