React not re-rendering on change - javascript

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)

Related

React js constant object value isn't working with `useState()` as expected

I am working with input fields which is getting added dynamically. There is initial value for the input fields, which i have set in constant and using accordingly.
The issue i am facing is, whatever I added new items and type whatever it update the same value for all the inputs as well. I am sharing my script that i am trying.
constant file
src/constant/index.js
.
.
export const NEW_TAX_LINE = {taxTitle:'',taxType:'percentage',taxRate:10};
.
.
component file `src/components/taxOptions.js
import { NEW_TAX_LINE } from "../../../../constants";
import { useState, useEffect, lazy } from "react";
const TaxItems = lazy(()=> import('./TaxItems'));
function TaxOptions() {
const [taxLineItems, setTaxLineItems] = useState([NEW_TAX_LINE]);
const handleChangeTaxItems = (event, index, PROPKEY) => {
console.log({event, index, PROPKEY});
const _taxDetails = [...taxLineItems];
_taxDetails[index][PROPKEY] = event.target.value;
setTaxLineItems(_taxDetails);
}
function handleAddNewTax(){
setTaxLineItems( [...taxLineItems, NEW_TAX_LINE] )
}
return <>
.
.
{taxLineItems.map((item, index) => (
<TaxItems
key={`tax-item-${index}`}
item={item}
index={index}
onChange={handleChangeTaxItems}
/>
))}
.
.
<Button
variant="contained"
size="small"
startIcon={<AddCircleOutlineIcon sx={{fontSize:'16px !important'}} />}
className="add-tax-button"
onClick={()=>handleAddNewTax()}
>
Add new
</Button>
</>
}
export default TaxOptions;
TaxItems.js
function TaxItems(props){
return <>
<TableRow >
<TableCell className="tax-items" component="td" scope="row">
<InputBase
className="i-item-cls"
// sx={{ flex: 1, mt:0}}
placeholder="Tax type"
inputProps={{ 'aria-label': 'unit-price' }}
name="unitprice"
fullWidth
value={props.item.taxTitle}
onChange={(event) => props.onChange(event, props.index, "taxTitle")}
/>
</TableCell>
<TableCell className="tax-items" align='right'>
<Grid sx={{display: 'flex', alignItems: 'center'}}>
<InputBase
className="i-item-cls i-taxrate"
placeholder="Tax Rate"
inputProps={{ 'aria-label': 'Tax rate' }}
name="unitprice"
fullWidth
value={props.item.taxRate}
onChange={(event) => props.onChange(event, props.index, "taxRate")}
/>
<Typography sx={{fontSize:'16px',fontWeight:'bold'}} component="span">%</Typography>
</Grid>
</TableCell>
</TableRow>
</>
}
export default TaxItems;
here is the screenshot or refr.
Don't know what i am doing wrong.
function handleAddNewTax(){
setTaxLineItems( [...taxLineItems, NEW_TAX_LINE] )
}
NEW_TAX_LINE is the link to object, not the object itself. So you add the same link everytime you add new tax. You should add new object like this:
function handleAddNewTax(){
setTaxLineItems( [...taxLineItems, Object.assign({}, NEW_TAX_LINE)] )
}

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

Proper way to perform an API call inside div?

So I am currently trying to display data in a table. This data is from 2 separate tables in the database with foreign keys.
I get my list using this call:
useEffect(()=>{
axios.get("http://localhost:3001/stores").then((response)=>{
setlistofStores(response.data) //State which contains the response from the API request
});
}, []);
So I can get the list of Stores and can display them in the table with no issue using this code:
<TableBody>
{listofStores.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row) => (
<TableRow key={row.tenantName}>
<TableCell>
<Grid container>
<Grid item lg={2}>
<Avatar alt={row.unit} src='.' className={classes.avatar}/>
</Grid>
<Grid item lg={10}>
<Typography className={classes.name}>{row.unit}</Typography>
</Grid>
</Grid>
</TableCell>
<TableCell>{row.contactName}</TableCell>
<TableCell>
<Typography
className={classes.status}
style={{
flex: 'center',
backgroundColor:
((row.industry === 'Apparel' && 'purple') ||
(row.industry === 'F&B' && 'grey') ||
(row.industry === 'Admin' && 'red') ||
(row.industry === 'Tech' && 'blue'))
}}
>{row.industry}</Typography>
</TableCell>
<TableCell>{row.primaryEmail}</TableCell>
<TableCell>{row.primaryPhone}</TableCell>
<TableCell className={classes.stores}>1</TableCell>
<TableCell ><button className={classes.viewButton} onClick={()=>{navigate(`/store/${row.id}`)}}>View</button></TableCell>
</TableRow>
Now I want to run this API inside each row to use the Tenant to display its data:
useEffect(() => {
axios.get(`http://localhost:3001/store/byId/${id}`).then((response) => {
setTenant(response.data);
});
}, []);
What is the correct way to do this?
useEffect with empty dependencies is the good one for your situation. You can create a new component for details and by clicking, navigate the user to that component (page). And there you can call the API for details. (or it can be a pop-up. It really depends on your UI design)
const TenantDetails = ({ tenantId, ...props }) => {
const [tenantData, setTenantData] = useState(null);
useEffect(() => {
axios.get(`http://localhost:3001/store/byId/${tenantId}`).then((response) => {
setTenantData(response.data);
});
}, []);
return (
// your UI implementation
<>
tenantData ? <div> ... </div> : <div>loading</div>
</>
)
}

React re-render after API call

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.

React - You have provided an out-of-range value `undefined` for the select component

I'm creating a material-UI dialogue form that posts data to my API. One of the fields in my backend database is binary and takes in only two possible options. How can I reflect that in my dialogue code below?
Here is my Fulltrace Back error:
Material-UI: You have provided an out-of-range value undefined for
the select (name="category") component. Consider providing a value
that matches one of the available options or ''. The available values
are personal, social.
The possible options for this specific field are either personal or social.
I tried doing this, letting my dialogue push the correct responses:
<MenuItem value={'personal'}> personal </MenuItem>
<MenuItem value={'social'}> social </MenuItem>
But that does not work and I understand why. Just not sure how to solve the error now as I'm not too savvy with React/JS in general.
export default function CreateBucket() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const history = useHistory();
const initialFormData = Object.freeze({
name: '',
category: '',
about: '',
});
const [formData, updateFormData] = useState(initialFormData);
const handleChange = (e) => {
updateFormData({
...formData,
// Trimming any whitespace
[e.target.name]: e.target.value.trim(),
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
axiosInstance
.post(`create/bucket`, {
name: formData.name,
category: formData.category,
about: formData.about,
})
.then((res) => {
history.push('/');
console.log(res);
console.log(res.data);
});
};
const classes = useStyles();
return(
<Fragment>
<Fab color="primary" aria-label="add" onClick={handleClickOpen} variant="extended">
<AddIcon className={classes.extendedIcon}/>
Create Bucket
</Fab>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Create your Bucket</DialogTitle>
<DialogContent>
<DialogContentText>
Get started! Make your bucket by telling us a little bit more.
</DialogContentText>
<form>
<FormControl>
<InputLabel> What type of Bucket </InputLabel>
<Select
id="category"
label="Bucket Type"
name="category"
fullWidth
required
variant="filled"
onChange={handleChange}
margin="normal"
className={classes.formControl}
>
<MenuItem value={'personal'}> personal </MenuItem>
<MenuItem value={'social'}> social </MenuItem>
</Select>
</FormControl>
</form>
</DialogContent>
<DialogActions>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={handleSubmit}
>
Create Bucket
</Button>
</DialogActions>
</Dialog>
</Fragment>
);
}
How can I implement changes to solve my traceback error?
Make sure you provide value prop to <Select> component, put value="" in case you don't want any of the options to be selected by default or value="personal" in case you want personal to be selected by default.
Her is mine with value={formData.category} it takes the value selected from state.
export default function CreateBucket() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const history = useHistory();
const initialFormData = Object.freeze({
name: '',
category: '',
about: '',
});
const [formData, updateFormData] = useState(initialFormData);
const handleChange = (e) => {
updateFormData({
...formData,
// Trimming any whitespace
[e.target.name]: e.target.value.trim(),
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
axiosInstance
.post(`create/bucket`, {
name: formData.name,
category: formData.category,
about: formData.about,
})
.then((res) => {
history.push('/');
console.log(res);
console.log(res.data);
});
};
const classes = useStyles();
return(
<Fragment>
<Fab color="primary" aria-label="add" onClick={handleClickOpen} variant="extended">
<AddIcon className={classes.extendedIcon}/>
Create Bucket
</Fab>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Create your Bucket</DialogTitle>
<DialogContent>
<DialogContentText>
Get started! Make your bucket by telling us a little bit more.
</DialogContentText>
<form>
<FormControl>
<InputLabel> What type of Bucket </InputLabel>
<Select
id="category"
label="Bucket Type"
name="category"
fullWidth
required
variant="filled"
onChange={handleChange}
margin="normal"
value={formData.category}
className={classes.formControl}
>
<MenuItem value={'personal'}> personal </MenuItem>
<MenuItem value={'social'}> social </MenuItem>
</Select>
</FormControl>
</form>
</DialogContent>
<DialogActions>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={handleSubmit}
>
Create Bucket
</Button>
</DialogActions>
</Dialog>
</Fragment>
);
}
It is exactly what it says on the tin:
Material-UI: You have provided an out-of-range value undefined for the select (name="category") component. Consider providing a value that matches one of the available options or ''. The available values are personal, social.
When you create a Select it wants you to provide initial value, which can be personal, social or an empty string. You don't provide any value, so it get confused
<Select
id="category"
value={formData.category}
label="Bucket Type"
will make it stop complaining.
None of these solved my issue where I wanted to have a placeholder but I have a state value and an onChange event inside my textfield. Once you set the initial state you cannot have a placeholder.
My fix below is to have the value of 'place holder text' in initial state and inside the list of options. I disabled and grayed out the value once initially loaded so it looks like a placeholder.
<TextField
margin="dense"
select
fullWidth
placeholder='Fuel Type'
value={fuelType}
onChange={handleFuelChange}
// defaultValue="Fuel Type"
type="text"
name="fuelType"
>
{options3.map((option) => {
return (option.label === "Fuel Type" ? <MenuItem key={option.label} disabled value={option.label + "," + option.carbonFactor}>
<span style={{ color: "rgb(156, 156, 156)" }}>{option.label}</span>
</MenuItem> : <MenuItem key={option.label} value={option.label + "," + option.carbonFactor}>
{option.label}
</MenuItem>)
})}
</TextField>

Categories