Update data from array with multiple objects using Axios PUT request - reactjs

I need to update data in an array that has multiple objects where a user will input a new balance that will update the old balance state. The array consists of a company name with an array called details, and that array holds objects containing employee information(name, balance, notes), for this question I am just using notes to simplify things. I am using Axios PUT to access the id of the nested object, I get the id from a Link that is passed via useParams hook.
My issue is in the Axios PUT request. Before I had a schema that was just a data object (no arrays were in it) and the PUT req was working fine. Then I needed to change the schema to an array with multiple objects and now I cannot seem to update the data. I am able to target the data through the console log but when I take that code from the console and apply it, the state still doesn't change. Even in Postman, the only way for me to successfully update is to get the Shema from a GET request and paste that schema in the PUT request and change some data in it, then I hit send and it updates, but to get it to update again I need to hit send twice (this shouldn't be, no? ).
I am able to access the data and render it in other components by mapping it twice as shown below:
setBalance(res.data.data.details.map((r) => r.balance));
My question: How can I edit the below code to update the state correctly?
setNotes([...details, res.data.data.details.map((r) => r.notes )]);
However, I am really struggling with how to do this in the Axios PUT request.
Here is my code:
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { useParams } from "react-router-dom";
import axios from "axios";
const AddForm = () => {
const [newBalance, setNewBalance] = useState("");
const [details, setDetails] = useState([]);
const [notes, setNotes] = useState("");
const [total, setTotal] = useState("");
const { id } = useParams();
const history = useHistory();
//update balance
const updateBal = () => {
// function to calculate balance
};
const updateBalHandler = (e) => {
e.preventDefault();
axios({
method: "PUT",
url: `http://localhost:5000/update-snapshot-test/${id}`,
data: {
balance: total
notes: notes
},
}).then((res) => {
history.push(`/success/` + id);
setNotes([...details, res.data.data.details.map((r) => r.notes )]); //this code isolates the notes state but does not update it
});
};
return (
<form
action="/update-snapshot/:id"
method="post"
onSubmit={updateBalHandler}
>
<Input
setInputValue={setNewBalance}
inputValue={newBalance}
inputType={"number"}
/>
<Input
setInputValue={setTotal}
inputValue={total}
inputType={"number"}
/>
<TextArea
setInputValue={setNotes}
inputValue={notes}
inputType={"text"}
/>
<Button onClick={() => { updateBal(); }} >
Update
</Button>
<Button type="submit">
Save
</Button>
</form>
);
};
export default AddForm;
Here is my data structure from Mongo DB
{
"message": "Post found",
"data": {
"company": "Riteaid",
"_id": "1",
"details": [
{
"employee": "jane doe",
"balance": "3",
"notes": "some notes",
"_id": "2"
},
{
"employee": "john doe",
"balance": "1",
"notes": "some more notes",
"_id": "3"
}
],
}
}

You have the id, so you have to search for the relevant object, update it and pass it to the setNotes() setter.
let localNotes = res.data.data.details.map((responseDetail) => {
if (detail._id === id){
let newNotes = [...responseDetail.notes, ...details];
return {
...responseDetail,
notes: newNotes
};
}
return responseDetail;
});
if (localNotes.length){
setNotes(localNotes);
}
Does this solve your problem?

The answer was in the backend, the front end was fine, the code did not need any of the additions, it should just be:
const addBalHandler = (e) => {
e.preventDefault();
axios({
method: "PUT",
url: `http://localhost:5000/endpoint${id}`,
data: {
balance: total,
notes: notes,
date: date,
},
}).then((res) => {
history.push(`/success/` + id);
console.log(res.data);
});
};

Related

Why my setState returns an array, but the state is a promise when the component rerender?

The code below try to check if an url is reachable or not.
The urls to check are stored in a state called trackedUrls
I update this state with an async function checkAll.
The object just before being updated seems fine, but when the component rerender, it contains a promise !
Why ?
What I should change to my code ?
import React from "react"
export default function App() {
const [trackedUrls, setTrackedUrls] = React.useState([])
// 1st call, empty array, it's ok
// 2nd call, useEffect populate trackedUrls with the correct value
// 3rd call, when checkAll is called, it contains a Promise :/
console.log("trackedUrls :", trackedUrls)
const wrappedUrls = trackedUrls.map(urlObject => {
return (
<div key={urlObject.id}>
{urlObject.label}
</div>
)
})
// check if the url is reachable
// this works well if cors-anywhere is enable, click the button on the page
async function checkUrl(url) {
const corsUrl = "https://cors-anywhere.herokuapp.com/" + url
const result = await fetch(corsUrl)
.then(response => response.ok)
console.log(result)
return result
}
// Checks if every url in trackedUrls is reachable
// I check simultaneously the urls with Promise.all
async function checkAll() {
setTrackedUrls(async oldTrackedUrls => {
const newTrackedUrls = await Promise.all(oldTrackedUrls.map(async urlObject => {
let isReachable = await checkUrl(urlObject.url)
const newUrlObject = {
...urlObject,
isReachable: isReachable
}
return newUrlObject
}))
// checkAll works quite well ! the object returned seems fine
// (2) [{…}, {…}]
// { id: '1', label: 'google', url: 'https://www.google.Fr', isReachable: true }
// { id: '2', label: 'whatever', url: 'https://qmsjfqsmjfq.com', isReachable: false }
console.log(newTrackedUrls)
return newTrackedUrls
})
}
React.useEffect(() => {
setTrackedUrls([
{ id: "1", label: "google", url: "https://www.google.Fr" },
{ id: "2", label: "whatever", url: "https://qmsjfqsmjfq.com" }
])
}, [])
return (
<div>
<button onClick={checkAll}>Check all !</button>
<div>
{wrappedUrls}
</div>
</div>
);
}
Konrad helped me to grasp the problem.
This works and it's less cumbersome.
If anyone has a solution with passing a function to setTrackedUrls, I'm interested just for educational purpose.
async function checkAll() {
const newTrackedUrls = await Promise.all(trackedUrls.map(async urlObject => {
let isReachable = await checkUrl(urlObject.url)
const newUrlObject = {
...urlObject,
isReachable: isReachable
}
return newUrlObject
}))
setTrackedUrls(newTrackedUrls)
}
You can only put data into setState.

React with easy-peasy

My route:
const id = req.params.id
const event = await events.findByPk(id, {
include: [clubs]
})
return res.json(event)
})
Result in Postman:
{
"id": 12,
"title": "Title",
"description": "Short description",
"clubId": 1,
"club": {
"id": 1,
"clubname": "My Club"
}
}
On my page I'm getting data using useEffect calling my action and updating "currentEvent" in state:
const currentEvent = useStoreState(state => state.currentEvent)
const fetchEventById = useStoreActions(actions => actions.fetchEventById)
const { id } = useParams()
useEffect(() => {
fetchEventById(id)
}, [])
Destructuring data:
const { title, description, club } = currentEvent
This is working well, and state is updated:
<h1>{title}</h1>
This is not working at all. State will not be updated and the Console says "Cannot read properties of undefined (reading 'clubname')":
<h1>{title}</h1>
<h2>{club.clubname}</h2>
Any ideas?
I think you should wait data fetched correctly and then you can see your updated.
Please refactore your code by using ? like this club?.clubname

dynamic URL with API paramethers

I'm new in nextjs and I need to do a dynamic URL using one API parameter
I have some like this data for the API
[
{
"categories" : {
"department": "HR"
"location": "xxxx"
},
"id": "4d2f341-k4kj6h7g-juh5v3",
},
{
"categories" : {
"department": "Operarions"
"location": "xxxx"
},
"id": "4qd3452-fsd34-jfd3453",
},
{
"categories" : {
"department": "Marketing"
"location": "xxxx"
},
"id": "4d2f341-k4kwer37g-juh5v3",
},
]
And I need to do something like that in my index.js
return(
<h1>More details</h1>
<a href="https://page.com/${id}"> link <a>
)
If someone can help me pls
Yes, will try to help you
Imagine you have a folder with the name records, so your url will be /records
In data you provided, you have several categories with ids which ones you like to have as links.(generated records dynamically)
According to nextjs docs, you can create getStaticProps page and generate static pages (it means it's very good for SEO, so use it on visible by SEO pages)
Imagine you have the next structure:
/records
[pid].tsx (or js)
index.tsx
This structure means that if you go to /records URL your index file will be triggered. And for /records/4d2f341-k4kj6h7g-juh5v3 your [pid] file will be triggered.
in index.tsx
import Link from 'next/link'
const Records= ({ records }) => {
return(
<div className="container">
<h1>Records</h1>
{records.map(record=> (
<div>
<Link href={'/records/' + record.id} key={record.id}>
<a>
{record.categories.department}
</a>
</Link>
</div>
))}
</div>
);
}
export const getServerSideProps = async () => {
//your API path here
const res = await fetch('replace_by_your_api_route');
const data = await res.json()
return {
props: {records: data}
}
}
export default Records
in your [pid].tsx
import GeneralUtils from "lib/generalUtils";
const RecordDetails= ({record}) => {
return(
<div>
{record?
<div >
{record.categories.department} {record.categories.location}
</div>
: ''}
</div>
)
}
export async function getStaticProps(context) {
const pid = context.params.pid;
//your api path to get each record where pid it is your parameter you are passssing to your api.
const res = await fetch(process.env.APIpath + '/api/public/getRecord?pid=' + (pid));
const data = await res.json();
return {
props: {record: data}
}
}
export async function getStaticPaths() {
const res = await fetch('your_api_path_same_as_in_index_tsx')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
//your id of each record, for example 4d2f341-k4kj6h7g-juh5v3
params: { pid: post.id.toString() },
}))
// We'll pre-render only these paths at build time.
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return { paths, fallback: 'blocking' }
}
export default ProductDetails;
Output: - user types
/records - he will have list of recods with links
/records/4d2f341-k4kj6h7g-juh5v3 - he will have page with info of d2f341-k4kj6h7g-juh5v3
Check this info very carefully and this video

Employee.map is not a function

I have a component where am getting data using Axios HTTP request via companyId useParams react hook. The response is already mapped to show in ListResults.js and those results are used as props in EmpDetailsList.js to render data. In my data structure, I have an Array that holds multiple objects. My goal is to render the items from the objects to the user, for example. Company HIJ has two objects of employee details, and I want to list those details, specifically employee:
Company Name: HIJ
-Employee: Lesley Peden
-Employee: Wayne Smith
I have tried to map the result of employee in EmpDetailsList.js to show them, however, I get an error employee.map is not a function. Is this because I have already mapped the result in the previous component via Axios then response, preventing me to do it again? The only way I know of Listing these names is to map. Am I missing something?
Data Structure
{
company: "HIJ",
address: "7890 street HIJ Road",
_id: "610ae597597656758575",
details: [
{
employee: "Lesley Peden",
_id: "610ae55757579885595"
},
{
employee: "Wayne Smith",
_id: "610aeaec67575785858"
}
],
__v: 0
}
]);
ListResults.js
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import EmpDetailsList from "./EmpDetailsList";
const ListResults = () => {
const [company, setCompany] = useState("");
const [employee, setEmployee] = useState("");
const { companyId } = useParams();
useEffect(() => {
axios
.get(`http://localhost:5000/url/${companyId}`)
.then((res) => {
setCompany(res.data.data.company);
setEmployee(res.data.data.details.map((r) => r.employee));
});
}, [
setCompany,
setEmployee,
companyId,
]);
return (
<>
<h2>Company Name: {company} </h2>
<EmpDetailsList employee={employee} />
</>
);
};
export default ListResults;
EmpDetailsList.js
const EmpDetailsList = ({ employee }) => {
return (
<Row>
{employee.map((emp, id) => (
<h5 key={emp.id}>{emp}:</h5>
))}
</Row>
);
};
export default EmpDetailsList;
Initialize your state with an empty array but not a string:
const [employee, setEmployee] = useState([]);
You are doing right at the setEmployee in Axios fetch but the issue is laying on your initial render.
Because in the initial render, employee is a string that you could not map through it.

How to chain actions in ReactJS?

How to chain actions to get the url of image property when I fetch the post list.
I've made a request that fetch all posts, and it gives me a link for the property "image".
mywebsite/api/recipes?_page=1 :
{
"#context": "/api/contexts/Recipes",
"#id": "/api/recipes",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/recipes/524",
"#type": "Recipes",
"id": 524,
"category": "/api/categories/11",
"title": "NewPost",
"content": "This is a new post",
"time": "50 minutes",
"image": [
"/api/images/37"
],
"slug": "new-post",
"createdAt": "2020-06-30T10:26:00+00:00",
"comments": [
"/api/comments/1359",
"/api/comments/1360"
]
},
........
and the result for mywebsite/api/images/37 is :
{
"url": "/images/5efbe9a4a1404818118677.jpg"
}
now in my actions i have
export const recipesListError = (error) => ({
type: RECIPES_LIST_ERROR,
error
});
export const recipesListReceived = (data) => ({
type: RECIPES_LIST_RECEIVED,
data
});
export const recipesListFetch = (page = 1) => {
return (dispatch) => {
dispatch(recipesListRequest());
return requests.get(`/recipes?_page=${page}`)
.then(response => dispatch(recipesListReceived(response)))
.catch(error => dispatch(recipesListError(error)));
}
};
so the first request is recipesListFetch, now what is missing is the second request to get the image and then return the url so i can directly have access to the image for each post
the easy solution would have been to use normalization_context groups has
i'm working with symfony api platform but it still gives me a link for the image property, I think because it's a ManyToMany relation
There don't seem to have the need for normalisation. The images and comments are specific to the recipe.
Make the then block callback as async fun and inside then block loop thru the recipes array first and then loop thru the image array and make api call for the image and await for it.
export const recipesListFetch = (page = 1) => {
return (dispatch) => {
dispatch(recipesListRequest());
return requests
.get(`/recipes?_page=${page}`)
.then(async (response) => {
//make then callback as async fun
const recipes = response["hydra:member"];
const imagesForTheRecipie = [];
for (let i = 0; i < recipes.length; i++) {//loop thru recipies
for (let j = 0; j < recipes[i].image.length; j++) {//loop thru images for each recipie
const imageUrl = recipes[i].image[j];//grab the image url
const image = await requests.get(`/${imageUrl}}`);
imagesForTheRecipie.push(image);
}
recipes[i].image = imagesForTheRecipie; //mutate the object which will directly update the response
}
dispatch(recipesListReceived(response));
})
.catch((error) => dispatch(recipesListError(error)));
};
};
Note - If you want to normalise then you can choose to nomalise data for the categories as the same category will be used by many recipes. In that case you will have to re-structure your reducers.

Resources