Employee.map is not a function - reactjs

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.

Related

Type Error "Map" is not a function while using react hooks

I am new to react and I am building a component thats going to be displayed within the profile page for a specific type of user of the app that I am working on "podcaster". I am trying to display their podcasts in a responsive list but I keep getting this typeError. I have looked at different examples here on SO but I have not been able to find the problem.
I am using 2 hooks to get this info [useState & useEffect] yet I am not able to fully understand why I am getting this error.
import { FC, useState, useEffect, Fragment } from 'react'
import ResponsiveList from '../ResponsiveList'
const UserProfilePodcaster: FC = () => {
const {
user: { id, accountType },
} = useAuth()
const [podcasts, setPodcasts] = useState<any[]>([])
const [showModal, setShowModal] = useState(false)
const [currentPodcast, setCurrentPodcast] = useState<any>({})
const [isLoading, setIsLoading] = useState(true)
const [categories, setCategories] = useState<{ name: string; code: string }[]>([])
useEffect(() => {
;(async function () {
const categories = await getCategories()
const podcasts = await getPodcast(id)
setCategories(categories)
setPodcasts(podcasts)
setIsLoading(false)
})()
}, [id])
<ResponsiveList
data={podcasts.map((podcast: any) =>
podcast.name({
...podcast,
edit: (
<Button
variant="contained"
color="primary"
fullWidth
onClick={() => handleEditPodcast(podcast)}
>
Edit
</Button>
),
})
)}
keys={[
{ key: 'podcasts', label: 'Podcast Name' },
{ key: 'categories', label: 'Podcast Categories' },
{ key: 'description', label: 'Podcast Niche' },
{ key: 'Reach', label: 'Podcast Reach' },
{
key: 'edit',
label: 'Edit',
},
]}
emptyMessage="It seems that you have not created a podcast yet, go out and start one 😢"
/>
map is a function of the Array prototype. If map is not a function, it means your podcasts is not an array. This might happen due to bugs in programming or as a result of podcasts being the result of e.g. an API call which is not there yet (is undefined) on first render as the API call has not resolved yet.
There are many ways to go about the latter case. You could e.g. write
data={podcasts?.map(...)}
and make sure the <ResponsiveList/> handles undefined data well, or you could render a loader for as long as the data is not there yet.
You can use lodash package. lodash will handle this type of errors
import _ from 'lodash';
.
.
.
data={_.map(podcasts, pod => {pod['key_name']})}

Update data from array with multiple objects using Axios PUT request

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);
});
};

API call with React custom hook not taking in updated parameter

I am using a custom hook to call the OpenRouteService API and retrieve the safest route from point A to point B. I'm now trying to switch between vehicles (which should give different routes), but the vehicle is not updating in the API call even though the parameter has been updated if I log it. See code below and attached screencaps.
Why is my API call not taking in the updated parameter?
Routehook.js
import { useState, useEffect } from 'react';
import Directions from '../apis/openRouteService';
import _ from 'lodash'
const useRoute = (initialCoordinates, avoidPolygons, vehicle) => {
const [route, setRoute] = useState({route: {}})
const [coordinates, setCoordinates] = useState(initialCoordinates);
const [routeLoading, setRouteLoading] = useState(true);
const [routeError, setRouteError] = useState(false);
useEffect(() => {
const fetchRoute = async () => {
setRouteError(false);
setRouteLoading(true);
try {
// Swap coordinates for openrouteservice
const copy = _.cloneDeep(coordinates)
let startLocation = copy[0];
let endLocation = copy[1];
console.log(vehicle);
// Logs 'cycling-regular' or 'driving-car'
// depending on the parameter when I call the hook
// Call openrouteservice api
const result = await Directions.calculate({
coordinates: [
[startLocation.latLng.lng, startLocation.latLng.lat],
[endLocation.latLng.lng, endLocation.latLng.lat],
],
profile: vehicle,
format: 'geojson',
avoid_polygons: avoidPolygons
})
console.log(result)
// When I check the result the query profile does not contain
// the updated parameter (see screencaps below).
setRoute(result);
} catch (error) {
setRouteError(true);
}
setRouteLoading(false);
}
fetchRoute();
}, [coordinates, avoidPolygons, vehicle]);
return [{ route, routeLoading, routeError }, setCoordinates];
}
export default useRoute;
To give a full overview. In the main component (VanillaMap.js) I have these related snippets:
const [vehicle, setVehicle] = useState('cycling-regular')
const [{ route, routeLoading, routeError }, setCoordinates] = useRoute([startLocation, endLocation], avoidPolygons, vehicle);
const updateVehicle = (state) => {
setVehicle(state);
}
<RouteInfo
route={route}
routeLoading={routeLoading}
routeError={routeError}
vehicle={vehicle}
updateVehicle={updateVehicle}
/>
I then update the vehicle through the updateVehicle function in the RouteInfo.js component.
Solved by creating a new instance of the Directions object each call:
const Directions = new openrouteservice.Directions({
api_key: "XXXXX"
});

How to display comments coming from Redux store on individual component

I have created a basic single page app, on initial page there is some dummy data and on click of each item I direct user to individual details page of that item. I wanted to implement comment and delete comment functionality which I successfully did but now when I comment or delete the comment it doesn't only happen at that individual page but in every other page too. Please see the sandbox example for better clarify.
https://codesandbox.io/s/objective-feistel-g62g0?file=/src/components/ProductDetails.js
So once you add some comments in individual page, go back and then click to another products, apparently you will see that the comments you've done in other pages are also available there. What do you think causing this problem ?
The same state being reused by all the different pages.
Try to load dynamically load reducers for each page/router differently to use distinct state values.
You can start from here
Redux modules and code splitting
I found my own logical solution. You probably might find a better solution but this works pretty well too. I thought of passing another property in the object with the params I get from url and then filter the comments by their url params. So that I could do filtering based on the url parameters and display the comments only made on that specific page.
So ProductDetails.js page should be looking like this:
import React, { useState, useEffect } from 'react';
import { Input, Button } from 'semantic-ui-react'
import { connect } from 'react-redux';
const ProductDetails = (props) => {
const [commentObject, setCommentObject] = useState({
text: "",
date: "",
id: ""
});
const clickHandler = () => {
if (!commentObject.text.trim()) {
return
}
props.addNewComment(commentObject)
setCommentObject({
...commentObject,
text: ""
})
console.log(commentObject.id);
}
useEffect(() => {
}, []);
return (
<div>
{props.posts ? props.posts.text : null}
{props.comments.filter(comment => {
return comment.postId === props.match.params.slug
}).map(({ text, id }) => {
return (<div key={id}>
<p>{text}</p>
<Button onClick={() => props.deleteComment(id)} >Delete comment</Button></div>)
})}
<Input value={commentObject.text}
onChange={comment => setCommentObject({ text: comment.target.value, date: new Date(), id: Date.now(), postId: props.match.params.slug })}
/>
<Button onClick={clickHandler} >Add comment</Button>
</div>
);
}
const mapStateToProps = (state, ownProps) => {
let slug = ownProps.match.params.slug;
return {
...state,
posts: state.posts.find(post => post.slug === slug),
}
}
const mapDispatchToProps = (dispatch) => {
return {
addNewComment: (object) => { dispatch({ type: "ADD_COMMENT", payload: { comment: { text: object.text, date: object.date, id: object.id, postId: object.postId } } }) },
deleteComment: (id) => { dispatch({ type: "DELETE_COMMENT", id: id }) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductDetails);

React: An component attribute is not properly storing the data (from a query) that I want

I recently started learning react and I have encountered something that I do not understand. So when I declare a component I also declare an attribute in the constructor. Then, after executing the first query (I am using Apollo client - GraphQL ) I want to store the result (which I know that will be always an email) in the attribute declared so I can use it as a parameter in the second query.
The app logic is that I want to show all the orders of a given email, but first I get the email with a query.
Here is the code:
export default class Orders extends Component {
constructor(){
super();
this.email = '';
}
render() {
return (
<div>
<Query query = { GET_MAIL_QUERY }>
{({data, loading}) => {
if (loading) return "Loading...";
this.email = data.me.email;
return <h1>{this.email}</h1>
}}
At this point a header containing the email is returned, so all good. But when I execute the second query (or try to display the email in the second header for that matter) it seems that the value is not properly stored.
</Query>
<h1>{this.email}</h1>
<Query query = { GET_ORDERS_QUERY }
variables = {{
email: this.email
}}>
{({data, loading}) => {
if (loading) return "Loading...";
console.log(data);
let orders = data.ordersByEmail.data;
console.log(orders);
return orders.map(order =>
<div>
<h1>{order.date}</h1>
<h1>{order.price}</h1>
<h1>{order.conference.conferenceName}</h1>
<h1>{order.user.email}</h1>
<br></br>
</div>)
}}
</Query>
</div>
)
}
}
const GET_MAIL_QUERY = gql`
query getMyMail{
me{
email
}
}
`;
const GET_ORDERS_QUERY = gql`
query getOrdersByEmail($email: String!) {
ordersByEmail(email: $email) {
data {
gid
date
price
user {
email
}
conference{
conferenceName
}
}
}
}
`;
I would love an explanation for this and maybe a solution (to store a value returned from a query to use it in another)
Thanks in anticipation :)
In my experience, you should use useQuery imported from #apollo/react-hooks with functional component because it's easy to use, it makes your code more cleaner
If your want to use <Query/> component with class component, it's ok. But, if you want to store data received from server, you should create a variable in state of constructor and when you want to update to state, you should use this.setState({email: data.me.email}). Don't use this.state.email = data.me.email, it's anti-pattern, React will not trigger re-render when you use it to update your state.
This is the code:
import React, { useState } from 'react'
import gql from 'graphql-tag'
import { useQuery, useMutation } from '#apollo/react-hooks'
const GET_MAIL_QUERY = gql`
query getMyMail {
me {
email
}
}
`
const GET_ORDERS_QUERY = gql`
query getOrdersByEmail($email: String!) {
ordersByEmail(email: $email) {
data {
gid
date
price
user {
email
}
conference {
conferenceName
}
}
}
}
`
const Orders = () => {
const [email, setEmail] = useState('')
const { data: getMailQueryData, loading, error } = useQuery(GET_MAIL_QUERY, {
onCompleted: data => {
setEmail(data.me.email)
},
onError: err => alert(err),
})
const { data: getOrdersQueryData } = useQuery(GET_ORDERS_QUERY, {
variables: { email: email },
})
if (loading) return <div>Loading...</div>
if (error) return <div>Error...</div>
return ...
}

Resources