I have a table component, wherein a column contains checkbox. If the checkbox is checked, then I dispatch certain action. Now the states are getting updated properly, but on the UI, the checkbox is not getting toggled. The checked attribute is dependent on the value in topUsers array for that particular row. I am unable to understand why this is happening. Any help is greatly appreciated!
The table component:
export default function UserTable({ data, topUser }) {
const [rows, setRows] = useState(data);
const [searched, setSearched] = useState("");
const classes = useStyles();
const [open, setOpen] = useState(false);
const [selectedUser, setSelectedUser] = useState({});
const [topUsers, setTopUsers] = useState(Array(data.length).fill(false));
const handleClickOpen = (user) => {
setSelectedUser(user);
setOpen(true);
};
useEffect(()=>{
const topUsersFromLocal = JSON.parse(localStorage.getItem("topUsers"));
topUsersFromLocal ? setTopUsers(topUsersFromLocal) : setTopUsers(Array(data.length).fill(false))
localStorage.setItem('users', JSON.stringify(data));
console.log(topUsers)
}, [])
const handleClose = (value) => {
setOpen(false);
};
const requestSearch = (searchedVal) => {
const filteredRows = data.filter((row) => {
return (
row.name.toLowerCase().includes(searchedVal.toLowerCase()) ||
row.email.toLowerCase().includes(searchedVal.toLowerCase())
);
});
setRows(filteredRows);
};
const cancelSearch = () => {
setSearched("");
requestSearch(searched);
};
const setTopUser = (event, index) => {
event.stopPropagation();
let tempTopUsers = topUsers;
tempTopUsers[index] = !tempTopUsers[index];
setTopUsers(tempTopUsers);
localStorage.setItem("topUsers", JSON.stringify(topUsers));
};
const onToggleClick = (event, index) => {
event.stopPropagation();
}
return (
<>
<Paper>
<SearchBar
value={searched}
onChange={(searchVal) => requestSearch(searchVal)}
onCancelSearch={() => cancelSearch()}
/>
<TableContainer>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>
<b>ID</b>
</TableCell>
<TableCell>
<b>Name</b>
</TableCell>
<TableCell>
<b>User Name</b>
</TableCell>
<TableCell>
<b>E-Mail</b>
</TableCell>
{!topUser && <TableCell>
<b>Top User?</b>
</TableCell>}
{!topUser && <TableCell>
<b>Disable User</b>
</TableCell>}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, index) => (
<TableRow key={row.id} onClick={() => handleClickOpen(row)}>
<TableCell>{row.id}</TableCell>
<TableCell>{row.name}</TableCell>
<TableCell>{row.username}</TableCell>
<TableCell>{row.email}</TableCell>
{console.log(topUsers[index])}
{!topUser &&<TableCell>
<input
type="checkbox"
id="isTopUser"
checked={topUsers[index]}
onClick={(e) => setTopUser(e, index)}
/>
</TableCell>}
{!topUser &&
<TableCell><Switch color="warning" onClick={(e) => onToggleClick(e, index)}/></TableCell>}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Paper>
<br />
{open && (
<SpecificUserInfoDialog
open={open}
onClose={handleClose}
user={selectedUser}
/>
)}
</>
);
}
Edit 1: I found a solution, by pushing the current page into router will re-render this page, but I don't think that would be a good solution to this problem.
Related
I'm trying to fetch data from api and show it with React.
However I could see that errors and I'm difficult with parsing json response from api.
I think that it will be solved if I make response as array.
I don't know how to make it.
page.js
I try to fetch data from API by Native hook and useEffect
I checked API by Postman. So it is working well.
function Customers(props) {
const [data, setData] = useState({});
const [error, setError] = useState(null);
useEffect(() => {
fetch("http://localhost:8080/contacts")
.then((response) => {
if (response.status !== 200) {
setError("Invalid response: ", response.status);
} else {
setError(null);
}
return response.json();
})
.then((json) => {
setData(json);
});
});
if (error !== null) {
return <div>Error: {error.message}</div>
} else {
return (
<DashboardLayout>
<>
<Head>
<title>
Data
</title>
</Head>
<Box
component="main"
sx={{
flexGrow: 1,
py: 8
}}
>
<Container maxWidth={false}>
<Box sx={{ mt: 3 }}>
<CustomerListResults customers={data} />
</Box>
</Container>
</Box>
</>
</DashboardLayout>
);
}
}
export default Customers;
list_component.js
I made a table. I would like to show API data this table.
I try to use slice and map to parsing data. but it is not working.
export const CustomerListResults = ({ customers, ...rest }) => {
const [limit, setLimit] = useState(25);
const [page, setPage] = useState(0);
const handlePageChange = (event, newPage) => {
setPage(newPage);
};
return (
<Card {...rest}>
<PerfectScrollbar>
<Box sx={{ minWidth: 1050 }}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>
Date
</TableCell>
<TableCell>
Name
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{customers.slice(0,limit).map((customer) => (
<TableRow
hover
key={customers.id}
>
<TableCell>
<Box
sx={{
alignItems: 'center',
display: 'flex'
}}
>
<Typography
color="textPrimary"
variant="body2"
>
{customers.created_date}
</Typography>
</Box>
</TableCell>
<TableCell>
{customers.user_idx}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
</PerfectScrollbar>
<TablePagination
component="div"
count={customers.length}
onPageChange={handlePageChange}
page={page}
rowsPerPage={limit}
/>
</Card>
);
};
CustomerListResults.propTypes = {
customers: PropTypes.array.isRequired
};
That's because data on initial load is equal to an empty object, and object doesn't have a slice method as it's a method for an array.
One solution is set an inital value for data to an empty array.
const [data, setData] = useState([]);
I'm working on a simple React app alongside TypeScript, and I'm using JSONPlaceholder for API calls simulation. I have implemented everything I need, but facing a problem when it comes to re-rendering components that shows response data from API. The thing is that if I do HTTP GET to all data after I do DELETE or PUT, I will again get all same data because data changes don't actually apply on the server.
I will appreciate it if you can help me how to edit my functions for DELETE and PUT.
When I console.log my responses inside components, I get staus ok 200, so the API call part is fine, the only thing that I struggle with is how to re-render components properly
There is a component that shows all data, and also make calls to DELETE endpoint, and shows modal where I call PUT endpoint:
const PostsTable: React.FunctionComponent = () => {
const [posts, setPosts] = useState<Array<IPost>>([]);
const [selectedPost, setSelectedPost] = useState<IPost>({});
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const fetchPosts = () => {
PostService.getAllPosts()
.then((response: any) => {
setPosts(response.data);
})
.catch((e: Error) => {
console.log(e);
});
};
useEffect(() => {
fetchPosts();
}, []);
const editPost = (post: IPost) => (event: any) => {
setSelectedPost(post);
setDialogOpen(true);
};
const handleClose = () => {
setDialogOpen(false);
};
const deletePost =
(id: any): any =>
(event: Event) => {
event.stopPropagation();
PostService.deletePost(id)
.then((response: any) => {
setPosts(posts);
console.log(response);
})
.catch((e: Error) => {
console.log(e);
});
};
return (
<Container fixed>
{!posts || posts.length < 1 ? (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<CircularProgress color="primary" size={100} />
</div>
) : (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell align="left">User Id</TableCell>
<TableCell align="left">Post Id</TableCell>
<TableCell align="left">Title</TableCell>
<TableCell align="left">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{posts.map((post: IPost) => (
<TableRow
key={post.id}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell>{post.userId}</TableCell>
<TableCell align="left">{post.id}</TableCell>
<TableCell align="left">{post.title}</TableCell>
<TableCell align="left">
<Tooltip title="Delete">
<IconButton onClick={editPost(post)}>
<EditIcon />
</IconButton>
</Tooltip>
<Tooltip title="Edit">
<IconButton onClick={deletePost(post.id)}>
<DeleteIcon />
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{
<EditPostDialog
open={dialogOpen}
handleClose={handleClose}
selectedPost={selectedPost}
/>
}
</Container>
);
};
export default PostsTable;
And also, there is a modal component with PUT function:
const EditPostDialog: React.FunctionComponent<IEditPostDialog> = (
props: IEditPostDialog
) => {
const { open, handleClose, selectedPost } = props;
const [post, setPost] = useState<IPost>({});
useEffect(() => {
const newPost = {
id: selectedPost.id,
userId: selectedPost.userId,
title: selectedPost.title,
body: selectedPost.body,
};
setPost(newPost);
}, [selectedPost]);
const handleChange = (event: any) => {
setPost({ ...post, [event.target.name]: event.target.value });
};
const handleSubmit = () => {
PostService.updatePost(post.id, post)
.then((response: any) => {
console.log(response);
})
.catch((e: Error) => {
console.log(e);
});
handleClose();
};
return (
<Dialog onClose={handleClose} open={open}>
<DialogTitle id="simple-dialog-title">Post info</DialogTitle>
<DialogContent classes={{ root: 'dialog-content' }}>
<TextField
id="userId"
label="User Id"
name="userId"
variant="outlined"
value={post.userId}
onChange={handleChange}
style={{ marginTop: 16 }}
/>
<TextField
id="title"
label="Title"
name="title"
variant="outlined"
value={post.title}
onChange={handleChange}
/>
<TextField
id="body"
label="Body"
name="body"
variant="outlined"
value={post.body}
onChange={handleChange}
multiline
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Close</Button>
<Button onClick={handleSubmit}>Submit</Button>
</DialogActions>
</Dialog>
);
};
export default EditPostDialog;
And at the end, my PostService.ts and HTTP files where is axios code to call API points:
const getAllPosts = () => {
return http.get<Array<IPost>>('/posts');
};
const getSinglePost = (id: number | undefined) => {
return http.get<IPost>(`/posts/${id}`);
};
const createPost = (data: IPost) => {
return http.post<IPost>('/posts', data);
};
const updatePost = (id: number | undefined, data: IPost) => {
return http.put<any>(`/posts/${id}`, data);
};
const deletePost = (id: number | undefined) => {
return http.delete<any>(`/posts/${id}`);
};
const PostService = {
getAllPosts,
getSinglePost,
createPost,
updatePost,
deletePost,
};
export default axios.create({
baseURL: 'https://jsonplaceholder.typicode.com',
headers: {
'Content-type': 'application/json',
},
});
Thank you all guys :)
Your delete function uses "setPosts(posts)" which I'm pretty sure is not what you meant. Also, your modal does not tell your main component that a post has been edited, I guess that's the problem ? Pass a callback to the modal to update the posts state in the main component.
I have a table where i render a data from the pokemon api, well in the table i have a button and when you press the button you get the data from the arrow, so i want to send that data from my component table to another component card and in that card render only the data which you select from the table. But i don't know how to send the data to my component Card whitout render my card five times in my table. Now i get the data when i press the button, i just need to send him.
Rows on the table component
export const Lista = (props) => {
const [, setPokeSelec] = useState({
})
const selectArrow = ( poke ) => {
setPokeSelec( poke );
console.log(poke);
}
return (
<>
<TableRow key={ props.info.id }>
<TableCell component="th" scope="row">
{ props.info.id }
</TableCell>
<TableCell align="right">{ props.info.name }</TableCell>
<TableCell align="right">{ props.info.abilities[0].ability.name}</TableCell>
<TableCell align="right">
<img src={ props.info.sprites.front_default } alt={ props.info.name } style={{ height: 60 }} />
</TableCell>
<TableCell align="right">
<Button
variant="contained"
color="primary"
size="small"
onClick={ () => selectArrow( props.info ) }
>
Seleccionar
</Button>
</TableCell>
</TableRow>
</>
)
}
Card component
export const Informacion = () => {
const classes = useStyles();
return (
<div className={ classes.margen } >
<Box display="flex" justifyContent="center" alignItems="center">
<Card className={classes.root}>
<CardMedia
className={classes.cover}
image={pika}
title="Live from space album cover"
/>
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography component="h5" variant="h5">
Pikachu
</Typography>
<Divider/>
<Typography variant="subtitle1" color="textPrimary">
Tipo:
</Typography>
</CardContent>
</div>
</Card>
</Box>
</div>
)
}
PokemonApi Component
Here i call the API, render the table and send to my component table the data
export const PokemonApi = () => {
const classes = useStyles();
const [poke, setPoke] = useState([]);
const data = () => {
axios.get(`https://pokeapi.co/api/v2/pokemon?limit=100`).then(( response ) => {
for(let i = 0; i < response.data.results.length; i++ ) {
axios.get(response.data.results[i].url)
.then( result => {
setPoke(prevArray => [...prevArray, result.data])
// console.log(result.data);
})
}
})
.catch( err => {
console.log(err);
})
}
useEffect(() => {
data()
}, []);
return (
<>
<TableContainer className={ classes.margin } component={Paper}>
<Table className={ classes.table } size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell align="right">Name </TableCell>
<TableCell align="right">Type </TableCell>
<TableCell align="right">Img </TableCell>
<TableCell align="right">Actions </TableCell>
</TableRow>
</TableHead>
<TableBody>
{ poke.map((infos, name) => <Lista key={name} info={infos}/>) }
</TableBody>
</Table>
</TableContainer>
</>
)
}
This is the page where i render the card and the PokemonApi
export const Pokes = () => {
return (
<Container maxWidth="md">
<PokemonApi />
<Informacion />
</Container>
)
}
Thanks for your time!!
Here are the common ways to pass data from a component A to a component B :
Having the component B as a child of the component A (see this link)
Having the component A as a child of the component B, with a callback prop that gets fired in the component A (see this link)
Using the context API
Hope that this answer helped you
I'm trying to recover the selected item on array of elements, I have an API service which returns a list of 5 jobs and a page on React to display the applicants. The rendered table from the list has a menu button for each row, when I click on the menu button it fires the handleMenuOpen event and returns the index of the selected job, but when the See applicants menuItem is clicked the handleSeeApplicantsClick event is fired and the returned value is always 4 (the index of the last job) can you please help me with this?
Thanks in advance
import { useState, useEffect } from 'react';
import ApiService from '../../services/ApiService';
export default function Evaluation(props) {
const [jobs, setJobs] = useState([]);
const [selected, setSelected] = useState(null);
const [anchorEl, setAnchorEl] = useState(null);
const columns = [
{label: 'Description', minWidth: 220, width: 200,},
{label: '', minWidth: 20, width: 20},
];
const loadJobs = async() => {
const result = await ApiService.get('/u/jobs');
setJobs(result.data || []);
};
const loadSelectedJob = async(jobId) => {
const result = await ApiService.get('/u/job/'+jobId);
setSelected(result);
};
useEffect( async() => {
if(!jobs?.length) loadJobs();
}, []);
const handleSeeApplicantsClick = (i) => {
console.log(i);
setAnchorEl(null);
};
const handleMenuOpen = (e, i) => {
console.log(i);
setAnchorEl(e?.currentTarget);
}
return (<div>
<TableContainer component={Paper}>
<Table stickyHeader>
<TableHead>
<TableRow>
{ columns.map((x, i) => <TableCell key={i}>{ x.label }</TableCell>) }
</TableRow>
</TableHead>
<TableBody>
{jobs.map( (x, j) => <TableRow key={x.id} >
<TableCell>
<Typography variant="body2">{ x.description }</Typography>
</TableCell>
<TableCell>
<IconButton onClick={ (e) => handleMenuOpen(e, i) }>
<MenuIcon />
</IconButton>
<Menu anchorEl={anchorEl} open={ Boolean(anchorEl) } onClose={() => handleMenuOpen(null)}>
<MenuItem onClick={ (ex) => { handleSeeApplicantsClick(i) }}>See applicants</MenuItem>
</Menu>
</TableCell>
</TableRow> )}
</TableBody>
</Table>
</TableContainer>
</div>);
}
Edit:
As suggested I've changed the code like this
<IconButton onClick={ (e) => handleMenuOpen(e, j) }>
<MenuIcon />
</IconButton>
But it does not fix the error, the last index is always returned.
Change handleMenuOpen(e, i) to handleMenuOpen(e, j)
i is the final value of your previous columns.map(x, i) whereas you are rendering using jobs.map( (x, j))
change <IconButton onClick={ (e) => handleMenuOpen(e, i) }> to <IconButton onClick={ (e) => handleMenuOpen(e, j) }>
change the function param <IconButton onClick={ (e) => handleMenuOpen(e, i) }>
to <IconButton onClick={ (e) => handleMenuOpen(e, j) }> because in map index value is setting into j, you need to access from j, in your example...
<TableBody>
{jobs.map( (x, j) => <TableRow key={x.id} >
<TableCell>
<Typography variant="body2">{ x.description }</Typography>
</TableCell>
<TableCell>
<IconButton onClick={ (e) => handleMenuOpen(e, i) }>
<MenuIcon />
</IconButton>
<Menu anchorEl={anchorEl} open={ Boolean(anchorEl) } onClose={() => handleMenuOpen(null)}>
<MenuItem onClick={ (ex) => { handleSeeApplicantsClick(i) }}>See applicants</MenuItem>
</Menu>
</TableCell>
</TableRow> )}
</TableBody>
or for best approch you can handle this through by your unique id {x.id} if its unique
I want to increments a counter when I click each name of the person in table. Each name has a counter and the counter is in different row.
I tried to follow a tutorial related to 'click counter'. When I click one of the name of the person, it effects to all counter. Not my needed. I share my code here. (Notes: I'm using Material-UI and React)
UserTable.js
export default function UserTable(props) {
const classes = useStyles();
const [count, setCount] = useState(0);
return (
<Grid item xs={6}>
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell align="center">Name</TableCell>
<TableCell align="center">Email</TableCell>
<TableCell align="center">Click Counter</TableCell>
<TableCell align="center">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.users.length > 0 ? (
props.users.map((user) => (
<TableRow key={user.id}>
<TableCell align="center">
<Button onClick={() => setCount(count + 1)}>
{user.name}
</Button>
</TableCell>
<TableCell align="center">{user.username}</TableCell>
<TableCell align="center">{count}</TableCell>
<TableCell align="center">
<Button
variant="contained"
color="primary"
onClick={() => {
props.editRow(user);
}}
>
Edit
</Button>
<Button
variant="contained"
color="secondary"
onClick={() => props.deleteUser(user.id)}
>
Delete
</Button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell align="center">No users</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
</Grid>
);
}
Home.js
export default function Home() {
const classes = useStyles();
const usersData = [
{ id: 1, name: "Tania", username: "floppydiskette" },
{ id: 2, name: "Craig", username: "siliconeidolon" },
{ id: 3, name: "Ben", username: "benisphere" },
];
const [users, setUsers] = useState(usersData);
const [editing, setEditing] = useState(false);
const initialFormState = { id: null, name: "", username: "" };
const [currentUser, setCurrentUser] = useState(initialFormState);
const updateUser = (id, updatedUser) => {
setEditing(false);
setUsers(users.map((user) => (user.id === id ? updatedUser : user)));
};
const editRow = (user) => {
setEditing(true);
setCurrentUser({ id: user.id, name: user.name, username: user.username });
};
const addUser = (user) => {
user.id = users.length + 1;
setUsers([...users, user]);
};
const deleteUser = (id) => {
setUsers(users.filter((user) => user.id !== id));
};
return (
<div className={classes.root}>
<Grid container spacing={3}>
<UserTable users={users} editRow={editRow} deleteUser={deleteUser} />
{editing ? (
<EditUserForm
setEditing={setEditing}
currentUser={currentUser}
updateUser={updateUser}
/>
) : (
<AddUserForm addUser={addUser} />
)}
</Grid>
</div>
);
}
you need to ( const [count, setCount] = useState(0); ) use something different form an integer value, because your counter is share from users in your table.
you can maybe use an object and when click for increment maybe you can save the counter under a property that is the name or id of custumer and do something like that
setCount(prevState => {
return {
...prevState,
[UserID] : prevState.UserID ? prevState.UserID++ : 1
}
})
and when you read data
couter[UserID]