How to invoke Axios synchronously in React? - reactjs

I'm using the Axios in React to register a user into MongoDb database.
But before I register a user, I check if that user already exists in the database, but since axios.post() is asynchronous, the rest of the code precending this response executes and user with same Id is regsitered again.
How do I solve this. PFB my code:
const validateRegister = (values) => {
let errors={};
const patternName = new RegExp('^[a-zA-Z ]{3,20}$')
const patternPhone = new RegExp('^[0-9]{9,10}$')
const patternEmail = new RegExp('^[a-zA-Z0-9._:$!%-]+#[a-zA-Z0-9.-]+.[a-zA-Z]$')
const patternPassword = new RegExp('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9])(?=.{8,})')
if(!values.name || !patternName.test(values.name)){
errors.name="Please enter a valid name"
}
if(!values.phone || !patternPhone.test(values.phone)){
errors.phone="Please enter a valid Phone number of 9-10 digits"
}
if(!values.email || !patternEmail.test(values.email)){
errors.email="Please enter a valid email address"
}
if(!values.password || !patternPassword.test(values.password)){
errors.password="Please enter a strong password to continue. A strong password has: Atleast 8 characters in length, 2 letters in upper case, 1 special character (!##$&*), 2 number (0-9), 3 letters in lower case"
}
if(!values.isTermsAndConditionsAccepted){
errors.isTermsAndConditionsAccepted = "Please Accept the Terms and conditions"
}
//Check if the user already exist
if(values.phone){
let formData = new FormData();
formData.append('phone', values.phone);
console.log('inside user check')
axios.post('http://localhost:3001/doesUserExistByPhone', formData).then(response => {
//Success - then create account
}).catch(errorResponse=>{
console.log(errorResponse)
if(errorResponse.response.status===409){
console.log('User already exist');
errors.phone="User Already exist. If you've already registered. Please try to Login.";
return errors;
}
else if(errorResponse.response.status===500){
errors.phone = "Unable to register user, contact SwapiFi Support";
return errors;
}
})
}
console.log('Errors found before creating user: ', errors);
return errors;
}
export default validateRegister
I invoke this Validator from another js file:
const useFormRegister = (submitForm) => {
const [errors, setErrors] = useState({});
const [dataIsCorrect, setDataIsCorrect] = useState(false);
const [values, setValues] = useState({
name: "",
phone: "",
email: "",
password: "",
isTermsAndConditionsAccepted: false
})
const handleValueChangeEvent = (event) => {
setValues({
...values,
[event.target.name] : event.target.value,
})
}
const handleRegisterEvent = (event) => {
console.log('Register button clicked');
event.preventDefault();
setErrors(validation(values));
console.log('Errors-Phone:', errors)
setDataIsCorrect(true);
}
useEffect(() => {
console.log('No. of errors:', Object.keys(errors).length)
{Object.entries(errors).map(([key, value]) => (
console.log("Error, ", key, ':', value)
))}
if(Object.keys(errors).length === 0 && dataIsCorrect){
submitForm(true);
let formData = new FormData();
{Object.entries(values).map(([key, value]) => (
formData.append(key, value)
))}
console.log(formData)
axios.post('http://localhost:3001/registerUser', formData).then(response => {console.log(response)}).catch(error=>{console.log(error)})
}
}, [errors])
return {handleValueChangeEvent, handleRegisterEvent, values, errors};
}
export default useFormRegister

You probably don't want to fire off a check synchronously. Look into async/await syntax. You can write code that "looks" synchronous but will actually execute asynchronously. This will allow you to do something like:
const checkUserExists = async (user) => {
const repsonse = await axios('/check/user/endpoint');
const user = await response.json();
return !!user;
}
const registerUser = async (user) => {
const repsonse = await axios('/register/user/endpoint');
const data = await response.json();
// do stuff here
}
and now you can implement whatever logic you need around these functions

useEffect(()=>{
async function deal(){
let data = await axios.get("http://localhost:8000")
setDeal(..)
}
deal()
},[])

Related

Using setState inside a arrow function resets state

I am trying to modify a state when a users input fields on my dashboard is changed. This is how the handler is intended to work:
If the state is empty. Create a user with the standard values and change its values to the changed inputs
If the user exists in the state, change the changed field in the state to the new value.
If the user does not exist. Add the user to the state and change the changed field to the new value.
I am doing this by calling this function on a change of any inputs:
const handleInputChange = (event, person) => {
let new_form_val = {
objectId: person._id,
name: person.name,
role: person.role,
privilege: person.privilege,
group: person.group
};
console.log("handle change function called")
if (formValues.length == 0)
{
console.log("formValues is empty")
new_form_val[event.target.name] = event.target.value
console.log("adding", new_form_val)
setFormValues([...formValues, new_form_val])
}
// console.log(event.target.value)
console.log("Change target id", event.target.id)
console.log("current formvalue before change", formValues)
let form_val = formValues.find((item) => item.objectId == event.target.id)
if (form_val) {
console.log("person found in formValues", form_val)
let index = formValues.indexOf(form_val)
formValues[index][event.target.name] = event.target.value
console.log("Changed already existing formvalue", formValues)
setFormValues(formValues)
}
else {
new_form_val[event.target.name] = event.target.value
console.log("new person in form value", new_form_val)
setFormValues([...formValues, new_form_val])
}
}
Later on I am using that function as an onChange event handler
useEffect(() => {
// GARL: https: //bobbyhadz.com/blog/react-push-to-state-array
setPeople([])
console.log("get users effetct ran")
axios.get('/api/getusers').then((response) => {
response.data.forEach((item, index) => {
setPeople(oldStatusArray => {
return [...oldStatusArray, <Person
key={index}
id={index+1}
_id={item._id}
name={item.name}
role={item.role}
privilege_id={item.privilege}
group_id={item.group}
onChange={(event) => handleInputChange(event, item)}
/>]
})
});
})
}, []);
The problem I am facing though is whenever the onChange function is called. The whole formValues sate is reset and replaced with the new changed state. For exmpale: I change user A to a new name and role and the change is logged to the console. I also Change User B and then C to a new group. Finally the state only has the changes made from C.
Here is the full code:
import Link from 'next/link';
import axios from 'axios';
import React, { useState, useEffect } from "react";
import Person from '../components/person' // Not actually a import
const Dashboard = () => {
const [people, setPeople] = useState([]);
const [formValues, setFormValues] = useState([]);
const handleInputChange = (event, person) => {
let new_form_val = {
objectId: person._id,
name: person.name,
role: person.role,
privilege: person.privilege,
group: person.group
};
console.log("handle change function called")
if (formValues.length == 0)
{
console.log("formValues is empty")
new_form_val[event.target.name] = event.target.value
console.log("adding", new_form_val)
setFormValues([...formValues, new_form_val])
}
// console.log(event.target.value)
console.log("Change target id", event.target.id)
console.log("current formvalue before change", formValues)
let form_val = formValues.find((item) => item.objectId == event.target.id)
if (form_val) {
console.log("person found in formValues", form_val)
let index = formValues.indexOf(form_val)
formValues[index][event.target.name] = event.target.value
console.log("Changed already existing formvalue", formValues)
setFormValues(formValues)
}
else {
new_form_val[event.target.name] = event.target.value
console.log("new person in form value", new_form_val)
setFormValues([...formValues, new_form_val])
}
}
useEffect(() => {
setPeople([])
console.log("get users effetct ran")
axios.get('/api/getusers').then((response) => {
response.data.forEach((item, index) => {
setPeople(oldStatusArray => {
return [...oldStatusArray, <Person
key={index}
_id={item._id}
name={item.name}
role={item.role}
privilege_id={item.privilege}
group_id={item.group}
onChange={(event) => handleInputChange(event, item)}
/>]
})
});
})
}, []);
const submit = (values) => {
// Submits state to backend for handling
}
return (
<div id="main">
<h1>Administration</h1>
{(people.length == 0) ?
<h1>Laddar innehållet..</h1> : people }
</div>
);
}
export default Dashboard;
Here is the output after changing the input fields a couple of times:
>> handle change function called
>> formValues is empty
>> adding - Object { objectId: "634ea9b368bd856cebfdddc0", name: "RADICATED", role: "...", privilege: "634ff6d42c7b67c5708e901b", group: "634ff7322c7b67c5708e901d" }
>> change target id 634ea9b368bd856cebfdddc0
>> current formvalue before change - Array []
>> new person in form value - Object { objectId: "634ea9b368bd856cebfdddc0", name: "RADICATED", role: "....", privilege: "634ff6d42c7b67c5708e901b", group: "634ff7322c7b67c5708e901d" }
>> CURRENT formvalues - Array [ {…} ] (len: 1)
I have also tried to adding formValues as a dependency to useEffect however, this results in a rerender of the users if I change any of the inputs as the setPeople is called in the useEffect.
How can I achieve a handleInputChange function that works as intended without updating the renderer or reseting the state?
I noticed the step 1 and 3 are actually the same so I put those together. The itemExists check if the person is already in the state. If the state is empty itemExists is false and if the person does not exists itemExists is also false.
When false we just update the field and return the previous and the new new_form_val.
When true we loop over all the current values until we find the one we want to edit, and then update the field we want to update.
const handleInputChange = (event, person) => {
const new_form_val = {
objectId: person._id,
name: person.name,
role: person.role,
privilege: person.privilege,
group: person.group,
};
// check if the item already exists
const itemExists =
formValues.find((item) => item.objectId == event.target.id) !== undefined;
if (itemExists) {
setFormValues((prevFormValues) => {
// map current values
const newValues = prevFormValues.map((item) => {
// if its not the item we're editing just return the item
if (item.objectId !== event.target.id) return item;
// if it is, update the item
const updatedItem = {
...item,
[event.target.name]: event.target.value,
};
return updatedItem;
});
return newValues;
});
} else {
// update the field with the new value
new_form_val[event.target.name] = event.target.value;
// add to the values
setFormValues((prevFormValues) => [...prevFormValues, new_form_val]);
}
};
I also updated the way the people were set. Now we first loop over all the data received from the api and create an array of Person components and set that array to the state, instead of setting the state for every result in the api data.
useEffect(() => {
// no need to set the people to an empty array since the default state is already an empty array
// setPeople([]);
console.log("get users effetct ran");
axios.get("/api/getusers").then((response) => {
const peopleFromApi = response.data.map((item, index) => (
<Person
key={index}
_id={item._id}
name={item.name}
role={item.role}
privilege_id={item.privilege}
group_id={item.group}
onChange={(event) => handleInputChange(event, item)}
/>
));
setPeople(peopleFromApi);
});
}, []);
I hope this helps you continue your project!

React Custom Hook always behind with one value

Code below is a simplified version of the reality.
It almost works. The issue is in my AddSomething component the value of the snackbar is always one value behind. I've used console logs as you can see in the code.
It goes like this:
"inside validateName"
"error is set to .... " (inside validateName)
"ELSE" (inside addSomething)
So it does everything in correct order. But the error in AddSomething is always one value behind.
So first time I input an empty string and the error message displayed is the default ''
(It should display 'Name can not be below 1')
Second time I try with a string above > 50 characters and it displays the old value of 'Name can not be below 1' but it should be: 'Name can not be above 50'
And it keeps going like this. How can I change the code to actually get the current value?
const AddSomething = () => {
const [error, validate] = useValidation()
const handleSave = async () => {
if(await validate(valuesToBeSaved){
saveItems()
}else {
console.log("ELSE")
setSnackBarError({
message: error
})
}
}
}
function useValidation() {
const [error, setError] = useState('')
let valid = true
async function validate(valuesToBeSaved: string[] | number[]) {
for(const value in valuesToBeSaved) {
if(typeof value === 'string'){
valid = await validateName(value)
}
if(valid === false) return false
}
}
async function validateName(name: string): Promise<boolean> {
console.log("inside validateName")
let valid = true
await nameSchema
.validate({ name })
.then((err) => {
valid = true
})
.catch((err) => {
console.log("error is set to: " + err.mesage)
setError(err.message)
valid = false
})
return valid
}
return [error, validate]
}
const nameSchema = yup.object({
name: yup
.string()
.min(1, 'Name can not be below 1')
.max(50, 'Name can not be above 50')
.required('Name is required'),
})

Uncaught TypeError: Cannot read properties of undefined but is defined

The purpose of this application is to make an API call to google places API, gather info about a restaurant, then display to the user.
The application works for the most part but every maybe 5-10 API calls on average the app crashes.
The error:
The code:
// State and global variables //
const [searchResponse, setSearchResponse] = useState("");
const [secondarySearchResponse, setsecondarySearchResponse] = useState("");
const [information, setInformation] = useState("");
const [secondaryInformation, setSecondaryInformation] = useState("");
const [itemFilter, setFilter] = useState("");
const [place_id, setPlaceId] = useState("");
const [dataReady, setDataReady] = useState(false);
const [locationHad, setLocationHad] = useState(false);
const pos = useRef(null);
const key = "AIzaSyD1ZTsmbDBBlMpmaogO_hlj93zzbDDtAoc";
var num = Math.floor(Math.random() * 20 + 1);
// Use Effects
// Gets users current location
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
pos.current = position;
console.log("Location had. Ready for API call.");
setLocationHad(true);
});
}, []);
// Once we have clicked our button and made our api call, we get the place_id and save it so we can make a secondary api call using place_id
useEffect(() => {
if (
searchResponse !== "" &&
searchResponse.results[num].place_id !== undefined) {
setPlaceId(searchResponse.results[num].place_id);
console.log("place_id set");
} else {
console.log("error in setting place_id");
}
}, [searchResponse]);
// One place_id is set we make secondary api call
useEffect(() => {
if (place_id !== "") {
fetchSecondaryInfo();
} else {
console.log("no place id!");
}
}, [place_id]);
// Now that we have made both api calls we save the relavent info into state that we will pass down to child components
useEffect(() => {
if (searchResponse !== "") {
console.log(searchResponse.results[num].name);
setInformation({
name: searchResponse.results[num].name,
open_now: searchResponse.results[num].opening_hours.open_now,
rating: searchResponse.results[num].rating,
price: searchResponse.results[num].price_level,
location: {
lat: searchResponse.results[num].geometry.location.lat,
lng: searchResponse.results[num].geometry.location.lng,
},
});
console.log("info set!");
} else {
console.log("no info to set!");
}
}, [searchResponse]);
// And again for the secondary info (I broke this dwown into to seperate useEffects trying to figure out what was causing my error...)
useEffect(() => {
if (secondarySearchResponse !== "") {
setSecondaryInformation({
phone_number: secondarySearchResponse.result.formatted_phone_number,
daily_hours: secondarySearchResponse.result.opening_hours.weekday_text,
address: secondarySearchResponse.result.formatted_address,
});
setDataReady(true);
console.log("secondary info set!");
} else {
console.log("no secondary info to set!");
}
}, [secondarySearchResponse]);
// Function that makes api call
async function fetchInfo() {
if (locationHad) {
if (itemFilter === "") {
var url = `https://secure-dawn-88985.herokuapp.com/https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${pos.current.coords.latitude},${pos.current.coords.longitude}&radius=12000&type=restaurant&key=${key}`;
} else {
var url = `https://secure-dawn-88985.herokuapp.com/https://maps.googleapis.com/maps/api/place/nearbysearch/json?keyword=${itemFilter[0]}&location=${pos.current.coords.latitude},${pos.current.coords.longitude}&radius=12000&type=restaurant&key=${key}`;
}
await fetch(url)
.then((response) => response.json())
.then((data) => setSearchResponse(data))
.then(console.log("api request fired."));
} else {
console.log("location not yet identified!");
}
}
// Function that makes secondary api call
async function fetchSecondaryInfo() {
if (place_id !== "") {
const secondary_url = `https://secure-dawn-88985.herokuapp.com/https://maps.googleapis.com/maps/api/place/details/json?fields=formatted_phone_number,opening_hours,formatted_address&place_id=${place_id}&key=${key}`;
await fetch(secondary_url)
.then((response) => response.json())
.then((data) => setsecondarySearchResponse(data))
.then(console.log("secondary api request fired."));
} else {
console.log("place_id not had in secondary fetch.");
}
}
As for the place_id error I put in the a specific line of code to avoid this error:
useEffect(() => {
if (
searchResponse !== "" &&
searchResponse.results[num].place_id !== undefined
) {
console.log(searchResponse.results[num].place_id);
setPlaceId(searchResponse.results[num].place_id);
console.log("place_id set");
} else {
console.log("error in setting place_id");
}
}, [searchResponse]);
So I do not understand how its possible to even throw this error with that line in there.
As for the name error I put in a specific line to console log the object before it reads the properties but it doesn't print in the console before throwing the error:
useEffect(() => {
if (searchResponse !== "") {
console.log(searchResponse.results[num].name);
setInformation({
name: searchResponse.results[num].name,
open_now: searchResponse.results[num].opening_hours.open_now,
rating: searchResponse.results[num].rating,
price: searchResponse.results[num].price_level,
location: {
lat: searchResponse.results[num].geometry.location.lat,
lng: searchResponse.results[num].geometry.location.lng,
},
});
console.log("info set!");
..........
I appreciate any input, suggestions, critiques, etc.
The error message shows that the error is being thrown on this line:
searchResponse.results[num].place_id !== undefined
This will throw if searchResponse.results[num] doesn't exist.
To be concise, try using optional chaining (and initialize searchResponse to undefined or null). Do
const [searchResponse, setSearchResponse] = useState();
and change
if (
searchResponse !== "" &&
searchResponse.results[num].place_id !== undefined) {
setPlaceId(searchResponse.results[num].place_id);
to
const possiblePlaceId = searchResponse?.results[num]?.place_id;
if (possiblePlaceId) {
setPlaceId(possiblePlaceId);

(Refactor/Improve) Loop to make API calls and manupilate Array following the "no-loop-func"

Despite looking and following numerous answers here at stackoverflow,I have still failed to refactor this code to abide by the ESLint no-loop-func.
I keep getting the following warning, despite my efforts to refactor the code:
Compiled with warnings.
Function declared in a loop contains unsafe references to variable(s) 'lastResult', 'biologyBooks', 'page' no-loop-func
Here's the code:
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({ total: 0, biologyBooksByAuthor: [] });
let isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async() => { // fetch items
let page = 1;
let scienceBooks, biologyBooks;
// create empty arrays to store book objects for each loop
let scienceBooks = biologyBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do { // the looping - this is what I have failed to refactor
try {
await apiFullCall( // Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`
).then(res => {
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
body &&
body.results &&
body.results.map(eachBook => { // we map() over the returned "results" array
// the author with queried "author_id" writes science books;
// so we add each book (an object) into the science category
scienceBooks.push(eachBook);
// We then filter the author's biology books (from other science books)
biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof(is_biology) === "boolean" && is_biology === true
);
return null;
}
);
// increment the page with 1 on each loop
page++;
}
}
}).catch(error => console.error('Error while fetching data:', error));
} catch (err) { console.error(`Oops, something went wrong ${err}`); }
// keep running until there's no next page
} while (lastResult.next !== null);
// update the state
setState(prevState => ({
...prevState, total: scienceBooks.length, biologyBooksByAuthor: biologyBooks,
}));
};
React.useEffect(() => { // fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
};
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
}
Please note that I actually declared the said variables lastResult, biologyBooks and page outside the "do-while".
Any help or clues will be greatly appreciated.
The function the warning is referring to is the .then callback, if you're using async/await stick to it, try removing the .then part by assigning the result to a variable instead and remove the unnecessary .map, you can concatenate previous results with spread operator or .concat.
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({
total: 0,
scienceBooksByAuthor: [],
});
const isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async () => {
// fetch items
let page = 1;
let scienceBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do {
// the looping - this is what I have failed to refactor
try {
const res = await apiFullCall(
// Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`,
);
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
// concatenate new results
scienceBooks = [
...scienceBooks,
...((lastResult && lastResult.results) || []),
];
// increment the page with 1 on each loop
page += 1;
}
}
} catch (err) {
console.error(`Oops, something went wrong ${err}`);
}
// keep running until there's no next page
} while (lastResult.next !== null);
const biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof is_biology === 'boolean' && is_biology === true,
);
// update the state
setState(prevState => ({
...prevState,
total: scienceBooks.length,
scienceBooksByAuthor: scienceBooks,
}));
};
React.useEffect(() => {
// fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
}
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
};

Why when the state is updated the changes in the rendering are not updated?

I have created a hook to access the collections of the database and its methods.
import { remote } from 'electron'
import { useState, useEffect } from "react"
function useCollections(collections = []) {
let [dbInstances, setDbInstances] = useState(null)
let [data, setData] = useState(null)
// DB METHODS
// Create
let create = async (doc, dbName) => {
await dbInstances[dbName].create(doc)
let newData = await dbInstances[dbName].readAll()
setData({ ...data, [dbName]: newData })
}
// Get details
let getDetails = async (id, dbName) => {
let doc = await dbInstances[dbName].read(id)
return doc
}
// Delete
let deleteOne = async (id, dbName) => {
await dbInstances[dbName].deleteOne(id)
let newData = await dbInstances[dbName].readAll()
setData({ ...data, [dbName]: newData })
}
// Update
let updateOne = async (id, updatedDoc, dbName) => {
await dbInstances[dbName].archive(id, updatedDoc)
let newData = await dbInstances[dbName].readAll()
setData({ ...data, [dbName]: newData })
}
// EFFECTS
useEffect(() => {
console.log('mounting component')
let newDBIs = {}
collections.forEach(col => newDBIs[col] = remote.getGlobal(col))
console.log('db instances settted', newDBIs)
setDbInstances(newDBIs)
}, [])
// When DBs are instantiated, request all docs and set data with response
useEffect(() => {
if (
dbInstances !== null &&
data === null &&
Object.keys(dbInstances).length === collections.length)
{
console.log('setting data')
let newData = {}
collections.forEach(async col => newData[col] = await dbInstances[col].readAll())
console.log('data setted => ', newData)
setData(newData)
}
}, [dbInstances])
return {
data,
create,
getDetails,
deleteOne,
updateOne
};
}
export default useCollections;
In the component where the data returned by the hook is consumed, even though the variable data contains the expected data, these are not rendered.
import WindowsLayout from "../../components/layout/WindowsLayout"
import { useState, useEffect } from "react"
import { remote } from "electron"
import useCollections from "../../hooks/useCollections"
const EditWorkWindow = ({ workId }) => {
let { data, deleteOne, updateOne } = useCollections([
'workDB',
'studioDB',
'rateDB'
])
useEffect(() => {
if (data !== null) console.log(data)
}, [data])
return (
<WindowsLayout title="Edit work window">
<div style={{ height: 243 }} className="window-content">
<div className="padded-more bg-gray-200">
<h2>{JSON.stringify(data)}</h2>
<button onClick={() => console.log(data)}>CLG</button>
</div>
</div>
</WindowsLayout >
)
}
export default EditWorkWindow
The effect hook shows the expected data by console.
<h2>{JSON.stringify(data)}</h2> = {}
When the button is clicked, the expected data is displayed on the console.
I can't understand why if data contains properties, they are not shown in {JSON.stringify(data)}
This is what is shown by the console after clicking on the button
console.log(data) image
And this is example data and their its properties
{
"workDB":[
{
"product":"Work name 1",
"amounts_rates":[
{
"rate":"EflcQflqu2oWWVk2",
"amount":6
},
{
"rate":"FeMIX00pwpmZwoVW",
"amount":1
}
],
"date":"2020-08-31",
"studio":"BCvPeWzMiS8fZsmS",
"_id":"2ZvHMWFODBHYWEBo",
"createdAt":"2020-08-31T09:39:21.077Z",
"updatedAt":"2020-08-31T09:39:21.077Z"
},
{
"product":"Work name 2",
"amounts_rates":[
],
"date":"2020-09-02",
"director":"",
"_id":"PRpp1OQcJnkFKeR0",
"createdAt":"2020-09-01T19:56:33.201Z",
"updatedAt":"2020-09-01T19:56:33.201Z"
}
],
"studioDB":[
{
"name":"Studio name 1",
"_id":"0J1AVXtgDjwBjRS9",
"createdAt":"2020-08-25T10:18:40.004Z",
"updatedAt":"2020-08-25T10:18:40.004Z"
},
{
"name":"Studio name 2",
"_id":"8sFH7gncaM6V7lHh",
"createdAt":"2020-08-25T10:19:45.232Z",
"updatedAt":"2020-08-25T10:19:45.232Z"
}
],
"rateDB":[
{
"name":"Rate name 1",
"value":4.1,
"_id":"EflcQflqu2oWWVk2",
"createdAt":"2020-08-25T10:24:17.357Z",
"updatedAt":"2020-08-25T10:24:17.357Z"
},
{
"name":"Rate name 1",
"value":34,
"_id":"FeMIX00pwpmZwoVW",
"createdAt":"2020-08-25T10:24:25.628Z",
"updatedAt":"2020-08-25T10:24:25.628Z"
}
]
}
Async problem it is.
// When DBs are instantiated, request all docs and set data with response
useEffect(() => {
if (
dbInstances !== null &&
data === null &&
Object.keys(dbInstances).length === collections.length)
{
console.log('setting data')
let newData = {}
collections.forEach(async col => newData[col] = await dbInstances[col].readAll())
console.log('data setted => ', newData)
setData(newData) // <-- 👋LOOK HERE
}
}, [dbInstances])
So you let newData = {} empty object, and send it off to trigger an update re-render by calling setData(), but newData is empty at the moment of calling.
In your rendering function JSON.stringify(data) pickup the data, but it's still empty at the moment of rendering!
It's only when async col => newData[col] = await someValue call is resolved, that your newData's properties will get assigned new values, the newData object stays the same. But by the time it's resolved, the rendering is done already.
Solution: wait till async function call is resolved, then you call setData()
useEffect(() => {
// ...
const promises = collections.map(async col => {
newData[col] = await dbInstances[col].readAll())
})
Promise.all(promises).then(() => { setData(newData) })
})
The reason why you see the updated value when inspecting in console, is because you didn't inspect "quick enough". By the time you mouse-click to expand the object in console, its properties are already assigned values. But if you change
console.log('data setted => ', newData)
// to
console.log('data setted => ', JSON.stringify(newData))
you'll see an empty object.

Resources