React re-render after API call - javascript

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.

Related

Checkbox check not toggling in nextJS app

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.

There is a problem with fetching and parsing data with React and Javascript

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([]);

React not re-rendering on change

So I've been beating my head against a wall for a couple days now scouring through Google and questions on here. I've done quite a bit of changes to the code I've written and re-written. The problem I'm getting is that the POST request to the DB works fine. If I refresh the page, the new entry is there. Somewhere in my code, I know I'm not properly updating the state. From what I've gathered, an issue like mine happens when you're not passing a new array into state in order for a re-render to trigger.
At this point I'm lost, and any insight on to what I'm doing wrong would be appreciated.
Here's the code:
export default function Form () {
const [chatData, setChatData] = useState([{
chat: Number,
isConverted: Boolean,
}])
console.log(chatData)
const changeHandler = name => (e) => {
setChatData({...chatData, [name]: e.target.value})
}
const createChat = (e) => {
e.preventDefault()
const data = { ...chatData }
const postData = () => {
try {
axios.post("/api/create", data)
} catch (error) {
console.error(error)
}
}
postData()
}
return (
<div>
<Box sx={{maxWidth: 200}} display="flex" alignItems="center" justifyContent="center" margin="auto">
<form onSubmit={createChat}>
<FormControl fullWidth sx={{ m: 1, minWidth: 120 }}>
<InputLabel id="chat_number_label">Chat Number</InputLabel>
<Select
labelId="chat_number_label"
id="chat_input_select"
defaultValue=""
value={chatData.chat}
onChange={changeHandler("chat")}
name="chat">
<MenuItem value={1}>1</MenuItem>
<MenuItem value={2}>2</MenuItem>
<MenuItem value={3}>3</MenuItem>
<MenuItem value={4}>4</MenuItem>
<MenuItem value={5}>5</MenuItem>
<MenuItem value={6}>6</MenuItem>
<MenuItem value={7}>7</MenuItem>
<MenuItem value={8}>8</MenuItem>
<MenuItem value={9}>9</MenuItem>
<MenuItem value={10}>10</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth sx={{ m: 1, minWidth: 120 }}>
<InputLabel id="is_converted_label">Converted to Ticket</InputLabel>
<Select
labelId="is_converted_label"
id="is_converted_select"
defaultValue=""
value={chatData.isConverted}
onChange={changeHandler("isConverted")}
name="isConverted">
<MenuItem value={true}>True</MenuItem>
<MenuItem value={false}>False</MenuItem>
</Select>
</FormControl>
<Button color="primary" variant="contained" type="submit" value="Submit">Submit</Button>
</form>
</Box>
</div>
)
}
EDIT: Per request, this is where all my chats are displayed on the page
export default function AllChats () {
const [chatList, setChatList] = useState([])
useEffect(() => {
const fetchAllChats = async () => {
try {
const res = await axios.get("/api")
setChatList(res.data)
} catch (error) {
console.error(error)
}
}
fetchAllChats()
}, [])
const headers = [
{
label: "Chat Number", key: "chat"
},
{
label: "Date", key: "createdAt"
},
{
label: "Converted into Ticket", key:"isConverted"
}
]
const csvLink = {
filename: "chat_data.csv",
headers: headers,
data: chatList
}
return (
<Container maxWidth="md">
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }}>
<TableHead>
<TableRow>
<TableCell>Chats Number</TableCell>
<TableCell align="right">Date</TableCell>
<TableCell align="right">Converted into ticket</TableCell>
</TableRow>
</TableHead>
<TableBody>
{ chatList.map((val, _key) => {
return (
<TableRow key={val.chat}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell omponent="th" scope="row">{val.chat}</TableCell>
<TableCell align="right">{val.createdAt}</TableCell>
<TableCell align="right"> {String(val.isConverted)} </TableCell>
</TableRow>
)})}
</TableBody>
</Table>
</TableContainer>
<CSVLink {...csvLink}>Export to CSV</CSVLink>
</Container>
)
}
Here's my App.js
function App() {
return (
<>
<AllChats />
<Form />
</>
);
}
export default App;
You are initiating chatData State with an OBJECT, but with-in an array like this:
const [chatData, setChatData] = useState([{
chat: Number,
isConverted: Boolean,
}])
So when you are retrieving the chatData, it is returning the array, not the object.
You need to remove the array while initiating it, and directly initiate it with an OBJECT like this:
const [chatData, setChatData] = useState({
chat: Number,
isConverted: Boolean,
})
It will solve the problem.
And if don't, then you can contact me through my profile.
setChatList is only called on first render in fetchAllChats. Your chatList is never being updated after the post request in createChat. You are updating your database but your frontend has no knowledge that your chats were updated. You need to update your chatList with setChatList upon a successful post. For instance:
axios.post("/api/create", data).then(response => {
setChatList([...chatList, response.data]);
})
For the above to work, your backend should return the chat in the response in the same format as in the get request in fetchAllChats(). Or the response could contain the entire updated chat list. If you don't have control over the backend, you could run fetchAllChats() after a successful POST, but this might not be ideal as it is additional load in terms of network and database operations.
In either case, you would need access to functions from <AllChats /> like setChatList in your <Form /> component, so you should lift state up to a common ancestor (<App /> in this case)

How to handle null state exception for map in reactJs

I have this code which his basic todo list that I am trying to build in react
const Home = (props) => {
const classes = useStyles();
const [addNewTask, setAddNewTask] = useState(false);
const [formData, setFormData] = React.useState({ taskText: "" });
const dispatch = useDispatch();
useEffect(() => {
dispatch(getTasks());
}, [dispatch]);
const todos = useSelector((state) => state.todos);
const handleAddNew = () => {
setAddNewTask(addNewTask ? false : true);
};
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
dispatch(createTask(formData));
dispatch(getTasks())
};
return (
<Grow in>
<Container>
<Grid
className={classes.adjustTop}
container
justify="space-between"
alignItems="stretch"
spacing={3}
>
<Grid item xs={2} sm={2}></Grid>
<Grid item xs={8} sm={8}>
<Typography variant="h2">TODO LIST</Typography>
{todos==null?
<Typography>Loading...</Typography>
:
<>
{todos?.tasks?.map((todo) => (
<Task id={todo.task} todo={todo} />
))}
</>
}
<div style={{ textAlign: "center" }}>
{addNewTask ? (
<>
<Button
variant="contained"
size="large"
color="error"
style={{ marginTop: "10px" }}
onClick={handleAddNew}
>
Cancel Adding task
</Button>
<form style={{ paddingTop: "20px" }} onSubmit={handleSubmit}>
<Input
name="taskText"
label="Task Text"
handleChange={handleChange}
type="text"
placeholder="text"
/>
<br />
<Button type="submit">Submit</Button>
</form>
</>
) : (
<Button
variant="contained"
size="large"
color="primary"
style={{ marginTop: "10px" }}
onClick={handleAddNew}
>
+ Add new task
</Button>
)}
</div>
</Grid>
<Grid item xs={2} sm={2}></Grid>
</Grid>
</Container>
</Grow>
);
};
export default Home;
This is the reducer
import { FETCH_ALL,CREATE } from '../constants/actionTypes';
export default (state = {tasks:null}, action) => {
switch (action.type) {
case FETCH_ALL:
return {...state,tasks:action.payload,errors:null}
case CREATE:
return {...state,tasks:action.payload,errors:null}
default:
return state;
}
};
& actions
import { FETCH_ALL,CREATE} from '../constants/actionTypes';
import * as api from '../api/index.js';
export const getTasks = () => async (dispatch) => {
try {
const { data } = await api.fetchTasks();
console.log(data);
dispatch({ type: FETCH_ALL, payload: data });
} catch (error) {
console.log(error.message);
}
};
export const createTask = (taskText) => async (dispatch) => {
try {
const { data } = await api.createTask(taskText);
dispatch({type:CREATE,payload:data})
} catch (error) {
console.log(error.message);
}
};
I am able to add the data to database on submit using handleSubmit tot the database but the issue is after each submit its giving me an error TypeError: _todos$tasks.map is not a function
I tried to handle this by using ternary operator & rendering Loading text on null & also have used chaining operator,but still getting same error
You are trying to get todos from your redux store
const todos = useSelector((state) => state.todos);
But there is no todos in your state and plus you are updating another state tasks in your reducer:
export default (state = {tasks:null}, action) => {
switch (action.type) {
case FETCH_ALL:
return {...state,tasks:action.payload,errors:null}
case CREATE:
return {...state,tasks:action.payload,errors:null}
default:
return state;
}
};

increment counter in the row

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]

Categories