Put results of a Get request from axios to an array - javascript

I don't understand a very simple task, I made a request from a API with axios in react.
If I console log the res.data, is like 170 result of single objects on my console.
I need to convert all these result in a single array of objects.
It's a basic task but I don't understand how to do it.
The application is a Trello Clone.
I have a variable called board that has all the data and with this list request, I grab all the column the the trello and append to ListObjects [] in newBoardData (it's a clone of board)
Here is my code:
//Get Request
const getList = async (id) => {
try {
return await axios.get(`${ENDPOINT}/lists/${id}`);
} catch (err) {
console.log(err);
}
};
const [loading, setLoading] = useState(false);
// Use Effect for grab the data with the listId
useEffect(() => {
(async () => {
setLoading(true);
const res = await (getList(listId));
//Loading up the listObjects
const oldList = board.board.listObjects
const newList = []
const payload = res.data;
//Adding all the old values to the new list (except for the current payload id)
for(let obj of oldList){
if(obj._id !== payload._id) newList.push(obj)
}
//Adding the current payload id
newList.push(payload)
const data = {
...board,
board: {...board.board, listObjects: newList}
};
setList(res.data);
// Here I put the data objects with the new ListObjects Array
setBoardNew(data);
setLoading(false);
})();
}, []);
Here is the console log of the get request res.data:
console.log of res.data
here is the board object:
board object
You can saw that there is a spam of result with the current res.data in ListObjects
I'think it make a request for every card in every list.
thank you very much!
UPDATE:
I will explain how the app works:
I have a file called Board.js, where I make this call (in the console log I have two call if I have two columns):
try {
return await axios.get(`${ENDPOINT}/boards/${id}`);
} catch (err) {
console.log(err);
}
};
useEffect(() => {
(async () => {
setLoading(true);
const res = await (getUserBoard(match.params.id));
if (res) {
axios.defaults.headers.common['boardId'] = match.params.id;
} else {
delete axios.defaults.headers.common['boardId'];
}
const payload = { ...res.data, listObjects: [], cardObjects: [] };
const data = {
...state,
board: { ...state.board, ...payload },
};
setBoardData(data);
setLoading(false);
})();
}, []);
Then I send the props data to the file List.js
{board.board.lists.map((listId, index) => (
<List key={listId} listId={listId} index={index} board={board} />
The list file send the data to
card.js
{list.cards.map((cardId, index) => (
<Card key={cardId} cardId={cardId} list={list} index={index} board={boardNew} />
The logic is: There is the board(board.js), in the board there are the lists (column)(list.js) in the lists there are the cards (card.js)
I hope it's more clear.

simple use this approach to add your new id value into the array state.
this.setState({ myArray: [...this.state.myArray, 'new value'] }) //simple value
this.setState({ myArray: [...this.state.myArray, ...[1,2,3] ] }) //another array

Related

Reactjs problem rendering binary image retrieved from mongodb

Hello so I have images in a mongodb database and I'm trying to render them on the client side however It's not working. I convert the buffer unit8 data into base64 so I can render it it seemd to work and been stored in the images state but images are not accessible by associative array.
useEffect( () => {
const getAllDoctors = async () => {
const result = await api.get('doctor/all')
const myImages = []
setDoctors(result.data)
await result.data.forEach(async doctor => {
myImages[doctor._id] = await base64_arraybuffer(doctor.photo.data.data)
})
setImages(myImages)
setLoading(false)
}
getAllDoctors()
}, [])
as for the render
return (
<div>
{
images.map((image, key) => {
console.log(doctors)
return (
<div key={key}>
<img alt={'image'} src={`data:image/png; base64, ${image}`}/>
<div>{`Doctor + ${images}`}</div>
</div>
)
})
}
</div>
);
converter (not mine):
const base64_arraybuffer = async (data) => {
const base64url = await new Promise((r) => {
const reader = new FileReader()
reader.onload = () => r(reader.result)
reader.readAsDataURL(new Blob([data]))
})
return base64url.split(",", 2)[1]
}
Two things
The way your array assignment is doesn't make sense. If _id is a ID string, then you are using a string to key an array, the data assigned at that key won't be included in loops. Use Array.prototype.push to add the item to the next available index.
Array.prototype.forEach and async / await don't play nice together due to the nature of callbacks. Try using a traditional loop.
useEffect(() => {
const getAllDoctors = async () => {
const result = await api.get("doctor/all");
const myImages = [];
setDoctors(result.data);
for (const doctor of result.data) {
const image = await base64_arraybuffer(doctor.photo.data.data);
myImages.push(image)
}
setImages(myImages);
setLoading(false);
};
getAllDoctors();
}, []);

Unable to access nested object from JSON data

I have the following function component in React:
function GetData() {
const [randomDataJSON, setRandomDataJSON] = useState('');
const url = 'https://randomuser.me/api/';
const getData = () => {
axios
.get(`${url}`)
.then((results) => {
const userData = results.data.results;
setRandomDataJSON(JSON.stringify(userData, null, 2));
})
.catch((err) => console.error(err));
};
return (
<div>
<h3>GetData Component</h3>
<pre>{randomDataJSON[0].name.first}</pre>
<button onClick={getData}>Get Data</button>
</div>
);
}
export default GetData;
The JSON data from the API is as follow:
[
{
"gender": "female",
"name": {
"title": "Miss",
"first": "Barbara",
"last": "Sullivan"
},
...
I would like to access and display the first name of the JSON data from the API by using {randomDataJSON[0].name.first in the <pre> tag. However, I keep getting the following error message: TypeError: Cannot read properties of undefined (reading 'name')
You are setting json string to randomDataJSON state variable, and trying use JSON string as an object. You can try to console.log(randomDataJSON) to confirm my suspicions.
I think you should not convert your data object to json in first place, so setRandomDataJSON(JSON.stringify(userData, null, 2)); will be setRandomDataJSON(userData);
function GetData() {
const [randomData, setRandomData] = useState('');
const url = 'https://randomuser.me/api/';
const getData = () => {
axios
.get(`${url}`)
.then((results) => {
const userData = results.data.results;
setRandomData(userData, null, 2);
})
.catch((err) => console.error(err));
};
return (
<div>
<h3>GetData Component</h3>
<pre>{randomData[0].name.first}</pre>
<button onClick={getData}>Get Data</button>
</div>
);
}
export default GetData;
At the time the page loads axios wouldn't have ran to request the array so randomDataJSON is a string at that time. You could do
const [randomDataJSON, setRandomDataJSON] = useState([]);
Above you set it to an empty array then check if it has length
<pre>{randomDataJSON.length > 0 && randomDataJSON[0].name.first}</pre>
Thank you very much to everyone pointing me in the right direction. My new code is as follow. My problem was I didn't know React doesn't allow you to render Javascript Object. To fix this, I just use the map() method to map through the data and display the properties in the object.
function GetData() {
const [randomDataJSON, setRandomDataJSON] = useState([]);
const url = 'https://randomuser.me/api/';
const getData = () => {
axios
.get(`${url}`)
.then((results) => {
const userData = results.data.results;
setRandomDataJSON(userData);
})
.catch((err) => console.error(err));
};
console.log('It is an', typeof randomDataJSON);
console.log('randomDataJSON is ', randomDataJSON);
return (
<div>
<h3>GetData Component</h3>
{randomDataJSON.map((data) => {
return <p>{data.name.first}</p>;
})}
<button onClick={getData}>Get Data</button>
</div>
);
}
export default GetData;

API search in javascript to return filtered list only

I am trying to make a filtered API search and only display the data(users) that has been filtered. However my search seems to return all the data and not just the data that has been filtered out. I can see the right data in the console log but can't seem to figure out how to get it to render on display.
For example, if I search for janet, I can see all the data that contains the name janet when I console log it but on display it still displays all the users. What am I doing wrong? Thanks
const UserList = document.getElementById('userList');
const searchBar = document.getElementById('searchBar');
let data = [];
searchBar.addEventListener('keyup', (e) => {
e.preventDefault();
const searchString = e.target.value;
console.log(e.target.value)
const filteredUsers = data.data.filter((user) => {
return (
user.first_name.includes(searchString) ||
user.email.includes(searchString)
);
});
console.log(filteredUsers)
displayUsers(filteredUsers);
});
const loadUsers = async () => {
try {
const res = await fetch('https://reqres.in/api/users');
data = await res.json();
displayUsers(data);
console.log(data)
} catch (err) {
console.error(err);
}
};
const displayUsers = (users) => {
const htmlString = data.data
.map((user) => {
return `
<li class="user">
<h2>${user.first_name}</h2>
</li>
`;
})
.join('');
userList.innerHTML = htmlString;
};
loadUsers();
On displayUsers function you are mapping on data.data variable. You should use users.map.
This might not be the problem, but you are defining UserList at the top and then using userList at the bottom, small typo maybe?

Array.map() doesn't render anything in React

I'm trying to make a list in my react app. I have retrieved data from my database, and pushed it into a list. I have doublechecked that the data shows up correctly in the console, and it does, but array.map() returns nothing. I think the problem might be that array.map() runs two times. I don't know why it runs two times.
function Dashboard() {
const user = firebase.auth().currentUser;
const [teams, setTeams] = useState([])
const history = useHistory();
useEffect(() => {
getTeams()
if (user) {
} else {
history.push("/")
}
}, [])
function Welcome() {
if (user) {
return <h1>Welcome, {user.displayName}</h1>
} else {
}
}
const getTeams = () => {
firebase.firestore().collectionGroup('members').where('user', '==', user.uid).get().then((snapshot) => {
const docList = []
snapshot.forEach((doc) => {
docList.push({
teamId: doc.data().teamId,
})
})
const teamslist = []
docList.forEach((data) => {
firebase.firestore().collection('teams').doc(data.teamId).get().then((doc) => {
teamslist.push({
name: doc.data().name,
teamId: doc.id,
})
})
})
setTeams(teamslist)
})
}
const openTeam = (data) => {
console.log(data.teamId)
}
return (
<div>
<Welcome />
<div>
<ul>
{console.log(teams)}
{teams.map((data) => {
return (
<li onClick={() => openTeam(data)} key={data.teamId}>
<h1>{data.name}</h1>
<p>{data.teamId}</p>
</li>
)
})}
</ul>
</div>
</div>
)
}
export default Dashboard
The getTeams function has a bug where it isn't waiting for the firebase.firestore().collection('teams').doc(data.teamId).get().then promises to finish before calling setTeams, so it is called with an empty array, causing React to trigger a render with the empty array.
As the promises for fetching each team resolve they will be pushed to the same array reference, but this won't trigger a rerender in React since you're not calling setTeams again when the array changes.
Try this code, which won't call setTeams until each team promise generated from docList has been resolved.
const getTeams = () => {
firebase.firestore().collectionGroup('members').where('user', '==', user.uid).get().then((snapshot) => {
const docList = []
snapshot.forEach((doc) => {
docList.push({
teamId: doc.data().teamId,
})
})
const teamslist = [];
Promise.all(docList.map((data) => {
return firebase
.firestore()
.collection('teams')
.doc(data.teamId)
.get()
.then((doc) => {
teamslist.push({
name: doc.data().name,
teamId: doc.id,
})
})
}))
.then(() => setTeams(teamslist));
})
}
A smaller edit would be to call setTeams after each separate team promise resolves, which will trigger a React render each time a new team is resolved:
.then((doc) => {
teamslist.push({
name: doc.data().name,
teamId: doc.id,
});
// create a new array, since using the same array
// reference won't cause react to rerender
setTeams([...teamslist]);
})
Many thanks to #martinstark who provided you an answer while I was unavailable.
However, there are some more things that need to be covered.
User State
In your current component, you pull the current user from Firebase Authentication, but don't handle the state changes of that user - signing in, signing out, switching user. If a user is signed in and they were to navigate directly to your dashboard, firebase.auth().currentUser could be momentarily null while it resolves the user's login state, which would incorrectly send them off to your login page.
This can be added using:
const [user, setUser] = useState(() => firebase.auth().currentUser || undefined);
const userLoading = user === undefined;
useEffect(() => firebase.auth().onAuthStateChanged(setUser), []);
Next, in your first useEffect call, you call getTeams() whether the user is signed in or not - but it should depend on the current user.
useEffect(() => {
if (userLoading) {
return; // do nothing (yet)
} else if (user === null) {
history.push("/");
return;
}
getTeams()
.catch(setError);
}, [user]);
// This getTeams() is a () => Promise<void>
const getTeams = async () => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', user.uid)
.get();
const docList = []
membersQuerySnapshot.forEach((doc) => {
docList.push({
teamId: doc.get("teamId"), // better perfomance than `doc.data().teamId`
});
});
const teamDataList = [];
await Promise.all(docList.map((data) => {
return firebase.firestore()
.collection('teams')
.doc(data.teamId)
.get()
.then(doc => teamDataList.push({
name: doc.get("name"),
teamId: doc.id
}));
}));
setTeams(teamDataList);
}
Optimizing getTeams() - Network Calls
The getTeams function in your question calls setTeams with the array [], which will be empty at the time of calling it as covered in #martinstark's answer. The "get team data" operations are asyncronous and you aren't waiting for them to resolve before updating your state and triggering a new render. While you are pushing data to them after the component has rendered, modifying the array won't trigger a new render.
While you could fetch the data for each team using db.collection("teams").doc(teamId).get(), each of these is requests is a network call, and you can only make a limited number of these in parallel. So instead of fetching 1 team per network call, you could fetch up to 10 teams per network call instead using the in operator and FieldPath.documentId().
Assuming the collectionGroup("members") targets the collections of documents at /teams/{aTeamId}/members which contain (at least):
"/teams/{aTeamId}/members/{memberUserId}": {
teamId: aTeamId,
user: memberUserId, // if storing an ID here, call it "uid" or "userId" instead
/* ... */
}
// this utility function lives outside of your component near the top/bottom of the file
function chunkArr(arr, n) {
if (n <= 0) throw new Error("n must be greater than 0");
return Array
.from({length: Math.ceil(arr.length/n)})
.map((_, i) => arr.slice(n*i, n*(i+1)))
}
// This getTeams() is a () => Promise<void>
const getTeams = async () => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', user.uid)
.get();
const teamIDList = []
membersQuerySnapshot.forEach((doc) => {
teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
})
const chunkedTeamIDList = chunkArr(teamIDList, 10) // split into batches of 10
const teamsColRef = firebase.firestore().collection('teams');
const documentId = firebase.firestore.FieldPath.documentId(); // used with where() to target the document's ID
const foundTeamDocList = await Promise
.all(chunkedTeamIDList.map((chunkOfTeamIDs) => {
// fetch each batch of IDs
return teamsColRef
.where(documentId, 'in', chunkOfTeamIDs)
.get();
}))
.then((arrayOfQuerySnapshots) => {
// flatten results into a single array
const allDocsList = [];
arrayOfQuerySnapshots.forEach(qs => allDocsList.push(...qs.docs));
return allDocsList;
});
const teamDataList = foundTeamDocList
.map((doc) => ({ name: doc.get("name"), teamId: doc.id }));
// sort by name, then by ID
teamDataList.sort((aTeam, bTeam) =>
aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
)
// update state & trigger render
setTeams(teamDataList);
}
You can also make use of this utility function to simplify & optimize the code a bit. Which gives:
// This getTeams() is a () => Promise<void>
const getTeams = async () => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', user.uid)
.get();
const teamIDList = []
membersQuerySnapshot.forEach((doc) => {
teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
})
const teamsColRef = firebase.firestore().collection('teams');
const teamDataList = [];
await fetchDocumentsWithId(
teamsColRef,
teamIDList,
(doc) => teamDataList.push({ name: doc.get("name"), teamId: doc.id })
);
// sort by name, then by ID
teamDataList.sort((aTeam, bTeam) =>
aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
)
// update state & trigger render
setTeams(teamDataList);
}
Optimizing getTeams() - Function Definition
As part of the last optimization, you could pull it out of your component or place it in its own file so that it's not redefined with every render:
// define at top/bottom of the file outside your component
// This getTeams() is a (userId: string) => Promise<{ name: string, teamId: string}[]>
async function getTeams(userId) => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', userId)
.get();
const teamIDList = []
membersQuerySnapshot.forEach((doc) => {
teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
})
const teamsColRef = firebase.firestore().collection('teams');
const teamDataList = [];
await fetchDocumentsWithId(
teamsColRef,
teamIDList,
(doc) => teamDataList.push({ name: doc.get("name"), teamId: doc.id })
);
// sort by name, then by ID
teamDataList.sort((aTeam, bTeam) =>
aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
)
// return the sorted teams
return teamDataList
}
and update how you use it:
useEffect(() => {
if (userLoading) {
return; // do nothing
} else if (user === null) {
history.push("/");
return;
}
getTeams(user.uid)
.then(setTeams)
.catch(setError);
}, [user]);

Axios GET request - how params should work

I would like to clarify the aim of "params" as an argument of axios.get.
I am implementing a search bar in my React App, but I am not able to filter the results through params.
Based on few tutorials I followed, params should help me filter through the responses I get from the API call. For example, considering one of the elements contained in the response from the GitHub Jobs API, I would expect that if I pass as params params: {location: "Austin, TX"} the final response should contain all the elements with that specific location.
The examples in the axios guide refer to a filtering based on the ID for example, so I infer this should be the same for any other key of the elements returned by the response.
In my app I have a select feature with all the locations available in the data. When one is selected it updates the filters variable which becomes, for example, {location: "Austin, TX"}. This is the value I pass, but it still does not filter the results.
As you can see below, I have a SearchForm.js where I select a location from a dropdown list, it goes into the handleParamChange in App.js which calls the useFetchJobs.js function with the filters and the result of the API call should come back.
useFetchJobs.js
const BASE_URL =
'https://api.allorigins.win/raw?url=https://jobs.github.com/positions.json';
function useFetchJobs(filters) {
const [state, dispatch] = useReducer(reducer, {
jobs: [],
locations: [],
loading: true,
});
useEffect(() => {
const cancelToken1 = axios.CancelToken.source();
axios
.get(BASE_URL, {
params: { ...filters },
})
.then((res) => {
var loc = res.data.map((item) => item.location);
loc = [...new Set(loc)];
loc.sort();
dispatch({
type: ACTIONS.GET_DATA,
payload: { jobs: res.data, locations: loc },
});
})
.catch((e) => {
if (axios.isCancel(e)) return;
dispatch({ type: ACTIONS.ERROR, payload: { error: e } });
});
return () => {
cancelToken1.cancel();
};
}, [filters, page]);
return state;
}
App.js
function App() {
const [filters, setFilters] = useState({});
const { jobs, locations, loading, error } = useFetchJobs(filters);
function handleParamChange(e) {
const param = e.target.name;
const value = e.target.value;
setFilters((prevFilters) => {
return { ...prevFilters, [param]: value };
});
}
return(stuff)
}
SearchForm.js
<Form.Group as={Col} lg={4}>
<Form.Label>Location</Form.Label>
<Form.Control
onChange={onParamChange}
name='location'
as='select'
size='lg'>
<option></option>
{locations !== undefined &&
locations.map((loc) => (
<option value={loc}>{loc}</option>
))}
</Form.Control>
</Form.Group>
Am I missing something? Thanks a lot.

Categories

Resources