use route parameter in the Redux Saga file - javascript

How to pass current route parameters to the saga function?
// Saga
function* findOneReleaseWithFilesSaga() {
const { productName, releaseId } = useParams()
try {
const releaseResponse: AxiosResponse = yield call(findOneWithFiles, releaseId)
^^^Error Here^^^
yield put(getAllReleasesSuccessAction(releaseResponse))
} catch (error) {
yield put(getAllReleasesErrorAction(error))
}
}
// Fetch Data
export const findOneWithFiles = async (releaseId:string) => {
return await Api.get(`/releases/${releaseId}/?populate=data_files`)
.then((res) => res.data.data)
.catch((err) => err)
}

I think the issue in the type of releaseId you get from useParams. Make sure it is a string. Also the desctructuring might be a problem, there were some fixes for that in TS4.6, but assuming you are on older version I would try to assign to a variable instead:
const params = useParams()
yield call(findOneWithFiles, params.releaseId)
On a side note, I would avoid prefixing a function with use unless it is a react hook.

Related

Is it possible to call to APIs inside a react-router loader function

I'd like to know if it's possible to make 2 API calls inside a loader function if I am using react-router 6. My ideas was to create an object based on these 2 calls and destruct the object in the rendering component like this:
function MainComponent (){
const {data , reservation} = useRouteLoaderData('room-details');
..
..
}
export default MainComponent;
export async function loader({request, params}) {
const id = params.roomId;
const response = await fetch ('http://localhost:8080/rooms/' + id);
const response2 = await fetch('http://localhost:8080/rooms/reservation/' + id)
const megaResponse = {
data: response, //i tried data:{respose} It ain't work
reservation: response2,
};
if (!response.ok) {
throw json({message: 'Something Wrong'}, {status: 500});
}
else {
return megaResponse;
}
}
But i have no success output.
I'd really want to make these 2 call in one place, otherwise I will have to use useEffect in a child component. Not a good Idea I think.
Thanks
I suspect you are not returning the unpacked response, i.e. JSON. I suggest surrounding the asynchronous code in a try/catch and simply try to process the requests/responses. Unpack the JSON value from the response objects. Since it doesn't appear the requests are dependent on one another I recommend loading them into an array of Promises that can be run concurrently and awaited as a whole. If during any part of the processing a Promise is rejected or an exception thrown, the catch block will return the JSON error response to the UI, otherwise, the { data, reservation } object is returned.
const loader = async ({ request, params }) => {
const { roomId } = params;
try {
const [data, reservation] = await Promise.all([
fetch("http://localhost:8080/rooms/" + roomId),
fetch("http://localhost:8080/rooms/reservaton/" + roomId)
]).then((responses) => responses.map((response) => response.json()));
return { data, reservation };
} catch {
throw json({ message: "Something Wrong" }, { status: 500 });
}
};
I found the solution, I tried it and it worked. It is as follow:
function MainComponent (){
const [data , reservation] = useRouteLoaderData('room-details');
..
..
}
export default MainComponent;
export async function loader({request, params}) {
const id = params.roomId;
return Promise.all([
fetch ('http://localhost:8080/rooms/' + id),
fetch('http://localhost:8080/rooms/reservation/' + id)
])
.then(
([data, reservation]) =>
Promise.all([data.json(), reservation.json()]),
error => {throw json({message: 'Something Wrong'}, {status: 500});}
)
.then(([data, reservation]) => {
return [data, reservation];
});
}
Thanks

How to extract data from api function with a promise in react

Im using react-redux, and in my saga file where I have implemented logic for new/edit page, I need to implement an API for getting some codes for customer.
const getCodesById = (Id) => get(`${BASE_URL}/${companyId}/codes`);
export function* getTableById(action) {
const Id = yield select(getCurrentCustomeId);
getEarningCodesForCompany(companyId).then((response) => {
console.log(response) //It shows correct array of objects from api
return response;
});
}
in console.log(response) I can see the data properly.
However, I dont know how can I extract that response in some variable outside that function to be able to use it along in the function getTableById.
I tried with const request = yield call(getCodesById(Id)); but with yield my program is crashing.
How can I do this to, get response and use it elsewhere?
export function* getTableById(action) {
const Id = yield select(getCurrentCustomeId);
const codes = yield call(getEarningCodesForCompany, companyId);
console.log(codes)
}
Did you try adding a return statement before getEarningCodesForCompany:
const getCodesById = (Id) => get(`${BASE_URL}/${companyId}/codes`);
export function* getTableById(action) {
const Id = yield select(getCurrentCustomeId);
return getEarningCodesForCompany(companyId).then((response) => {
console.log(response) //It shows correct array of objects from api
return response;
});
}

Can't access lexical definition of response before initialization within a React hook

I'm trying to make the home page send one API call on load and display all results on screen. It seems to send the call and receive response fine, although despite receiving the response from the server it can't pass the contents of the payload within the code, which is a JSON.
useEffect(() => {
const localUser = localStorage.getItem("user");
if (localUser) {
const foundUser = localUser;
setUser(foundUser);
} else {
const newUser = uuidv1();
localStorage.setItem(newUser, user);
setUser(newUser);
}
console.log(user);
async function fetchPosts() {
try {
let tempPosts = [];
const response = await fetch('http://localhost:3000/posts')
.then(response => response.json())
.then(response.payload.forEach(object => tempPosts.push(object.post)))
.then(setPosts((posts) => [tempPosts]));
console.log(posts);
} catch (err) {
console.log(err)
}
}
fetchPosts();
}, [user, posts]);
Somehow React is trying to access the response without the declaration and I have no idea how, which in result stops the function from executing.
Take a look at this line:
const response = await fetch('http://localhost:3000/posts')
.then(response => response.json())
You're combining two paradigms - asynchronous programming using Promises and callbacks with then, and asynchronous programming using async/await. You'll usually want to pick one or the other for use in a single function, and you definitely cannot combine them in a single line (or at least, not like this).
If you want to use async (and I would recommend this approach), you'll probably want something like this:
async function fetchPosts() {
let tempPosts = [];
const response = await fetch('http://localhost:3000/posts');
const data = await response.json();
data.payload.forEach(object => tempPosts.push(object.post))
return tempPosts;
}
I don't know what data looks like, so you may have to play with the 4th line of the function, but hopefully you get the gist. You'll probably want to define this function outside of your component, and certainly not within the useEffect hook.
An example of how you could use this to fetch posts on the first render of your component is
useEffect(() => {
fetchPosts().then(data => setPosts(data));
}, []);
assuming you have a relevant useState hook.

How to make other function gets called after first is executed?

I am working on a react app where I am using redux for state management and I have 2 functions to call and I want them to run only after 1st function is executed.
Here's a snippet of whats I am doing:
if (this.props.page_num < this.props.numPages) {
this.props.fetchCode(params, isFiltered, isSearched).then(() => {
this.props.setPageNumber(this.props.page_num + 1);
});
}
Here I am getting a error stating:
CodeTable.jsx?2468:132 Uncaught TypeError: this.props.fetchCode(...).then is not a function
fetchCode function:
export function* fetchCode(action) {
try {
const response = yield call(Services.fetchCode, action.params);
const { dtoList } = response.data.pagedList;
const num_pages = response.data.pagedList.numPages;
const total_records = response.data.pagedList.totalRecords;
const page_number = response.data.pagedList.pageNumber;
const postCodeSetsData = dtoList.map(({
}) => ({
}));
yield put(ActionCreator.setCodes(dtoList, num_pages, total_records, postCodeData, page_number, action.isFiltered, action.isSearched));
} catch (error) {
sagaException(error);
}
}
Since you are using redux saga, I believe the most appropriate thing to do is compose another saga.
export function* fetchCodeSetsAndSetPage(action) {
try {
yield put (ActionCreator.fetchCodes(...));
yield put (ActionCreator.setPageNumber(...));
} catch (error) {
sagaException(error);
}
}
And then call that one instead in your component.
Some docs.
make fetchCodeSets function async. You can write promise like then only with an async functions. also use a return statement inside the function. The declaration of the fetchCodeSets function should be like this
const fetchCodeSets = async (params) => {
//codes here
return;
}

testing redux-saga inside if statement and using real values

How do I test a function inside an if statement or try/catch? For instance,
export function* onFetchMessages(channel) {
yield put(requestMessages())
const channel_name = channel.payload
try {
const response = yield call(fetch,'/api/messages/'+channel_name)
if(response.ok){
const res = yield response.json();
const date = moment().format('lll');
yield put(receiveMessages(res,channel.payload,date))
}
} catch (error){
yield put(rejectMessages(error))
}
}
I need to input a real channel name that actually exist in the database for it to return a valid response for the yields that follow to execute, otherwise it will throw an error. In addition, I will get an error message, cannot read property json of undefined, so the yield after that cannot be reached due to this error message.
So my first problem is 'if(response.ok)' but even if I remove it, yield response.json() would return an error and in addition the yield after that wont be executed.
If anyone can show me how to test these, would be much appreciated.
Pass the response object to the previous execution and test conditional, I would do it like this, hope this helps:
export function* onFetchMessages(channel) {
try {
yield put(requestMessages())
const channel_name = channel.payload
const response = yield call(fetch,'/api/messages/'+channel_name)
if(response.ok){
const res = yield response.json();
const date = moment().format('lll');
yield put(receiveMessages(res,channel.payload,date))
}
} catch (error){
yield put(rejectMessages(error))
}
}
describe('onFetchMessages Saga', () => {
let output = null;
const saga = onFetchMessages(channel); //mock channel somewhere...
it('should put request messages', () => {
output = saga.next().value;
let expected = put(requestMessages()); //make sure you import this dependency
expect(output).toEqual(expected);
});
it('should call fetch...blabla', ()=> {
output = saga.next(channel_name).value; //include channel_name so it is avaiable on the next iteration
let expected = call(fetch,'/api/messages/'+channel_name); //do all the mock you ned for this
expect(output).toEqual(expected);
});
/*here comes you answer*/
it('should take response.ok into the if statemenet', ()=> {
//your json yield is out the redux-saga context so I dont assert it
saga.next(response).value; //same as before, mock it with a ok property, so it is available
output = saga.next(res).value; //assert the put effect
let expected = put(receiveMessages(res,channel.payload,date)); //channel should be mock from previous test
expect(output).toEqual(expected);
});
});
Notice your code probably does more stuff I'm not aware of, but this at least should put u in some line to solve your problem.
You might want to use an helper library for that, such as redux-saga-testing.
Disclaimer: I wrote this library to solve that exact same problem
For your specific example, using Jest (but works the same for Mocha), I would do two things:
First, I would separate the API call to a different function
Then I would use redux-saga-testing to test your logic in a synchronous way:
Here is the code:
import sagaHelper from 'redux-saga-testing';
import { call, put } from 'redux-saga/effects';
import { requestMessages, receiveMessages, rejectMessages } from './my-actions';
const api = url => fetch(url).then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error(response.status); // for example
}
});
function* onFetchMessages(channel) {
try {
yield put(requestMessages())
const channel_name = channel.payload
const res = yield call(api, '/api/messages/'+channel_name)
const date = moment().format('lll');
yield put(receiveMessages(res,channel.payload,date))
} catch (error){
yield put(rejectMessages(error))
}
}
describe('When testing a Saga that throws an error', () => {
const it = sagaHelper(onFetchMessages({ type: 'foo', payload: 'chan1'}));
it('should have called the API first, which will throw an exception', result => {
expect(result).toEqual(call(api, '/api/messages/chan1'));
return new Error('Something went wrong');
});
it('and then trigger an error action with the error message', result => {
expect(result).toEqual(put(rejectMessages('Something went wrong')));
});
});
describe('When testing a Saga and it works fine', () => {
const it = sagaHelper(onFetchMessages({ type: 'foo', payload: 'chan2'}));
it('should have called the API first, which will return some data', result => {
expect(result).toEqual(call(api, '/api/messages/chan2'));
return 'some data';
});
it('and then call the success action with the data returned by the API', result => {
expect(result).toEqual(put(receiveMessages('some data', 'chan2', 'some date')));
// you'll have to find a way to mock the date here'
});
});
You'll find plenty of other examples (more complex ones) on the project's GitHub.
Here's a related question: in the redux-saga docs, they have examples where take is listening for multiple actions. Based on this, I wrote an auth saga that looks more or less like this (you may recognize that this is a modified version of an example from the redux-saga docs:
function* mySaga() {
while (true) {
const initialAction = yield take (['AUTH__LOGIN','AUTH__LOGOUT']);
if (initialAction.type === 'AUTH__LOGIN') {
const authTask = yield fork(doLogin);
const action = yield take(['AUTH__LOGOUT', 'AUTH__LOGIN_FAIL']);
if (action.type === 'AUTH__LOGOUT') {
yield cancel(authTask);
yield call (unauthorizeWithRemoteServer)
}
} else {
yield call (unauthorizeWithRemoteServer)
}
}
}
I don't think this is an anti-pattern when dealing with Sagas, and the code certainly runs as expected outside the test environment (Jest). However, I see no way to handle the if statements in this context. How is this supposed to work?

Categories

Resources