use async await in handleSubmit react redux-toolkit - reactjs

I have a final form in a react page that i need to show me a global success if all the data have been sotored in their database.
I have two partial forms (variations and generalInfo) that store the data in two tables in sql.
I use this handlesubmit from last step in the form to store this data.
const handleSubmit = (e) => {
e.preventDefault();
dispatch(setDesignResponse(dataResponse));
dispatch(createNewVariations(variations));
dispatch(createNewJsonResponse(newJsonResponse));
};
i have my slices that return me info if the store is succesfully.
export const createNewJsonResponse = createAsyncThunk (
"new-json-response/post",
async (newData) => {
const { accesToken, newDataResponse } = newData;
const res = await FaqsService.createNewResponse(newDataResponse, accesToken);
return res.data;
}
);
export const createNewVariations = createAsyncThunk (
"new-variations/post",
async (variations) => {
try {
console.log(variations);
const { token, variationsData } = variations;
const res = await FaqsService.createNewVariations(variationsData, token);
console.log(res.data);
alert('VARIACIONES CREADAS PERFECTAMENTE');
return res.data;
} catch(error) { console.log(error);}
}
);
If just i have one call to one api i know how to show and alert to inform the user. Like in the above code (createNewVariations).
But i need to check if both are ok, and then show the alert to the user.
I think that i can do something in the component, in the handlesubmit, to send the info to the slices, store the data in the data bases, return de result succesfuly (with an state that return a true or false from slices (with extrarducer like:
extraReducers: {
[createNewJsonResponse.fulfilled]:(state, action) => {
state.isCreated = action.payload.message; // initial state is: isCreated:false
}
}
// (the same for the other slice).
),
and then pop-up the alert.
Is it possible?
thanks

Yes you can have isCreated in both of the state slices and then have if condition on your compoment which show success alert when both of the isCreated Flag is 1
I have create a Github example for this I am using a counter example,
from # Redux + Plain JS template
npx create-react-app my-app --template redux
and modified the code to demo that how you can achieve it.
You would need to look on the src/feature/counter/Counter.js File
There I have below logic, this is the not full code of the component, you can look that in the Github repo. and yes you can have isCreated on multiple slices and have if condition on the component, that will work for which you are looking for.
export function Counter() {
const count = useSelector(selectCount);
const incrementStatus = useSelector(incrementCountStatus);
const incrementStatusNew = useSelector(incrementCountStatusNew);
const dispatch = useDispatch();
const [incrementAmount, setIncrementAmount] = useState('2');
console.log(`Increment Status:`, incrementStatus);
console.log(`Increment Status New:`, incrementStatusNew);
const incrementValue = Number(incrementAmount) || 0;
const handleAsyncSubmit = () => {
dispatch(incrementByAmount(incrementValue))
dispatch(incrementAsyncNew(incrementValue))
}
if (incrementStatus === 'success' && incrementStatusNew === 'success') {
alert('Data have been saved successfully.');
}
GitHub Repo

Related

How to check if redux actionReducer axios request is successful or not

I'm creating a page login with OTP, so first I sent request for login. If it's successful then my model would open for entering the otp.
But in my case model will open always because im unable to figure out how check if my request is successfult or not
I can manage state but I don't want to manage state for this simple task. I just want that the request in action reducer is successful or not. This will surely solves my problem easily.
const handleSubmit = async (event) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
let email= data.get('email');
let password= data.get('password');
await props.otpRequest(email,password);
handleOTPModelOpen();
};
Action Reducer
export const otpRequest = (email,password) => {
return (dispatch) => {
const url = process.env.REACT_APP_DEV_BACKEND
dispatch(getOTPRequest())
axios
.post(`${url}/api/get_otp`,{
email: email,
password: password
})
.then(response => {
dispatch(getOTPSuccess())
dispatch(showAlert(handleAxiosError(response),"success"));
})
.catch(error => {
dispatch(showAlert(handleAxiosError(error),"error"));
dispatch(getOTPFailure())
throw OTPRequestFailed()
})
}
}
and using mapDispatchToProps
const mapDispatchToProps = dispatch => {
return {
fetchToken: (email,otp) => dispatch(fetchToken(email,otp)),
otpRequest: (email,password) => dispatch(otpRequest(email,password))
}
}
I just want to check if otpRequest is successful or not. Like that we checkedd in axios request in action reducer.
All request are successful no error is coming
One nice way of handling this for most libraries is a try / catch block.
You could do this:
try {
await props.otpRequest(email,password);
handleOTPModelOpen();
} catch (e) {
// handle error here
console.log(e);
handleOTPFailed();
}
EDIT 1
I don't see a selector or use of mapStateToProps callback. You use mapDispatchToProps which is great for the actions, however, in order to access the resulting state, you must also add mapStateToProps. Here's an example from one of my apps:
const mapStateToProps = (state) => {
return {
success: state.data.success;
}
}
Think of state as a whole pie. A Redux selector allows you to take a slice of that pie and return it to the React component props for use, instead of the entire state (response from the API dispatch).

How to use API Route in next js?

I am learning how to design API and at the same time how to use next.js API route.
I have set my first route api/property/[propertyId] that returns the specific property detail.
Now I am trying to set a dynamic route for the specific property id in the page folder page/property/[propertyId]. My issue is when I am getting directed on the specific page the data is not there as expected. I am receiving a response for error message.
Can someone point out what I did wrong, please?
pages>api>property>[propertyId]
export default function propertyHandler ({query : {propertyId} } ,res) {
var parser = new xml2js.Parser({explicitArray : false});
const data = fs.readFileSync(path.join(process.cwd(),'listing.xml'))
parser.parseString(data,function (err, results){
results = results.client.secondhandListing.property
const filteredProp = results.filter((property) => property.id === propertyId)
filteredProp.length > 0 ? res.status(200).json(filteredProp[0]) : res.status(404).json({message: `Property with id: ${propertyId} not found.` })
})
}
pages>property>[id].js
export const getDetails = async() => {
const res = await fetch(`${baseUrl}/api/property/[property.Id]}`)
const data = res.json()
return data
}
export async function getServerSideProps({params: { id } }) {
const data = await getDetails(`${baseUrl}/api/property/${id}`)
return {
props: {
propertyDetails: data
}
}
}
I got the answer to my mistake from somewhere else. It was my getdetails function that was wrong.
I have amended it to:
export const getDetails = async(baseUrl)=>{
const res = await fetch(baseUrl)
const data = await res.json()
return data
};
and it worked.

How to display all successful and unsuccessful request to an API in Next.js?

I have an input in which I write the names of the characters through a comma Ricky, Marty, etc.
Accordingly, on each of the heroes, I make requests in a database and show results.
How do I display a list of successful and unsuccessful requests if the hero is not found?
export const getServerSideProps: GetServerSideProps = async (context) => {
const { name } = context.query;
const nameArray = (name as string).split(',');
const allRequest = nameArray.map((el) => axios.get(`https://rickandmortyapi.com/api/character/?name=${el}`));
const charactersList = await axios.all(allRequest)
.then(axios.spread((...response) => response.map((e) => e.data.results)));
return ({
props: {
charactersList,
},
});
};
With this code, I just get the data from the database. And I need it
Ricky (data from input) --- data from database Morty (data from input --- data from database)
, etc and the list of which was not found.
You probably want to use Promise.allSettled() to wait for all promises to either resolve or reject (and avoid rejecting everything if one of them rejects).
export const getServerSideProps: GetServerSideProps = async (context) => {
const { name } = context.query;
const nameArray = Array.isArray(name) ? name : [name];
const allRequest = nameArray.map((el) =>
axios.get(`https://rickandmortyapi.com/api/character/?name=${el}`)
);
const charactersList = await Promise.allSettled(allRequest).then((res) => {
// Iterate over all results, both successful or unsuccessful
return res.map((result) => {
// Returns response data if successful, or `undefined` otherwise
// Handle this however you like
return result.value?.data.results;
});
});
//...
}
Note that you should avoid using axios.all/axios.spread as they've been deprecated.

Check setState has run before executing API call in React

I have a form with a text field and a form input which accepts multiple files. OnSubmit the files are sent to Firebase Storage, which sends back a URL for each file. These URLs are then stored in a 'photosURL' array in the form object, which is then posted to MongoDB.
The problem is, every time I post the form object data to Mongo, the photos array is empty, despite the console log showing it to be populated before I call the post-to-Mongo code. This leads me to think the post-to-Mongo code is using the form object value before it has been populated with the photo URLs.
The question is, how do I check that the photo array has been populated before I run the code to push the data to MongoDB? I'm already using a Promise.all to in theory wait for all the files to be sent and the URLs returned, but I can't work out why else the photoURLs array is empty every time data is sent to Mongo.
Here's the code:
const [form, setForm] = useState({
userId: '',
post: '',
createdAt: createdAt,
photoURLs: [],
})
const handleSubmit = (e) => {
e.preventDefault()
newPost ? postData(form) : ...
}
// SEND FILE TO FIREBASE AND GET BACK THE URL
async function handleUpload(file) {
const storageRef = useStorage.ref("PostImages");
const fileRef = storageRef.child(`${nanoid()}`);
return fileRef.put(file).then(() => {
return fileRef.getDownloadURL().then(function (url) {
photoArray.push(url);
setForm(prevState => ({ ...prevState, photos: photoArray }))
});
});
}
// POST FUNCTION
const postData = async (form) => {
setLoading(true)
let thisFileArray = fileInput.current.files;
const uploadTasks = [];
for (let i = 0; i < thisFileArray.length; i++) {
uploadTasks.push(handleUpload(thisFileArray[i]));
}
Promise.all(uploadTasks).then(() => {
axios.post('/api/posts', form)
.then(response => {
...
})
.catch(error => {
...
})
})
}
Can anyone see what's going wrong, please?
EDIT: This is a consolel log of the form object, called before the axios.post code (it's showing the photosURL as populated):
createdAt: 1630072305502
photos:
0: "https://firebasestorage.googleapis.com/..."
1: "https://firebasestorage.googleapis.com/..."
post: "sample text"
userId: "1iNGV..."
I think that you are running into a timing issue.
Don't forget that React state updates are asynchronous, as described here.
I suggest to pass your URLs directly instead of going through your component's state:
async function handleUpload(file) {
const storageRef = useStorage.ref("PostImages");
const fileRef = storageRef.child(`${nanoid()}`);
await fileRef.put(file);
const url = await fileRef.getDownloadURL();
return url; // Send back the download URL
}
const postData = async (form) => {
setLoading(true);
let thisFileArray = fileInput.current.files;
const uploadTasks = [];
for (let i = 0; i < thisFileArray.length; i++) {
uploadTasks.push(handleUpload(thisFileArray[i]));
}
const photos = await Promise.all(uploadTasks); // Get all URLs here
await axios.post('/api/posts', {...form, photos}); // Send URLs to your server
setLoading(false);
}
If I understood correct, You want to upload files first and when you get your urls array populated then only you want to call postData function?
If that's the case, then you can use useEffect to detect the urls change.
useEffect(() => {
// Here you call your postData function.
postData();
}, [form.photoURLs])
What this will do is, Whenever your form.photoURLs gets populated this useEffect will run and will make the request to the server with proper data.

How do I load firebase data into react-redux asynchronously?

I am currently trying to load my product data into redux, but so far I cant seem to pass the product information returned from firestore into the reducer.
Index.js -> load first 10 products from firestore soon after store was created.
store.dispatch(getAllProducts)
action/index.js
import shop from '../api/shop'
const receiveProducts = products => ({
type: types.RECEIVE_PRODUCTS
products
})
const getAllProducts = () => dispatch => {
shop.getProducts(products => {
dispatch(receiveProducts)
})
}
shop.js
import fetchProducts from './firebase/fetchProducts'
export default {
getProducts: (cb) => cb(fetchProducts())
}
fetchProducts.js
const fetchProducts = async() => {
const ProductList = await firebase_product.firestore()
.collection('store_products').limit(10)
ProductList.get().then((querySnapshot) => {
const tempDoc = querySnapshot.docs.map((doc) => {
return { id: doc.id, ...doc.data() }
})
}).catch(function (error) {
console.log('Error getting Documents: ', error)
})
}
In product reducers
const byId = (state={}, action) => {
case RECEIVE_PRODUCTS:
console.log(action); <- this should be products, but it is now promise due to aysnc function return?
}
I can get the documents with no issues (tempDocs gets the first 10 documents without any issue.) but I am not able to pass the data back into my redux. If I were creating normal react app, I would add a loading state when retrieving the documents from firestore, do I need to do something similar in redux as well ?
Sorry if the code seems messy at the moment.
fetchProducts is an async function so you need to wait for its result before calling dispatch. There are a few ways you could do this, you could give fetchProducts access to dispatch via a hook or passing dispatch to fetchProducts directly.
I don't quite understand the purpose of shop.js but you also could await fetchProducts and then pass the result of that into dispatch.
A generalized routine I use to accomplish exactly this:
const ListenGenerator = (sliceName, tableName, filterArray) => {
return () => {
//returns a listener function
try {
const unsubscribe = ListenCollectionGroupQuery(
tableName,
filterArray,
(listenResults) => {
store.dispatch(
genericReduxAction(sliceName, tableName, listenResults)
);
},
(err) => {
console.log(
err + ` ListenGenerator listener ${sliceName} ${tableName} err`
);
}
);
//The unsubscribe function to be returned includes clearing
// Redux entry
const unsubscriber = () => {
//effectively a closure
unsubscribe();
store.dispatch(genericReduxAction(sliceName, tableName, null));
};
return unsubscriber;
} catch (err) {
console.log(
`failed:ListenGenerator ${sliceName} ${tableName} err: ${err}`
);
}
};
};
The ListenCollectionGroupQuery does what it sounds like; it takes a tableName, an array of filter/.where() conditions, and data/err callbacks.
The genericReduxAction pretty much just concatenates the sliceName and TableName to create an action type (my reducers de-construct action types similarly). The point is you can put the dispatch into the datacallback.
Beyond this, you simply treat Redux as Redux - subscribe, get, etc just as if the data were completely local.

Resources