Not sure is there any way to set default request headers in rxjs like we do with axios js as-
axios.defaults.headers.common['Authorization'] = 'c7b9392955ce63b38cf0901b7e523efbf7613001526117c79376122b7be2a9519d49c5ff5de1e217db93beae2f2033e9';
Here is my epic code where i want to set request headers -
export default function epicFetchProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.mergeMap(action =>
ajax.get(`http://localhost/products?${action.q}`)
.map(response => doFetchProductsFulfilled(response))
);
}
Please help.
It's not possible to set default headers for all ajax requests using RxJS's ajax utilities.
You can however provide headers in each call, or create your own simple wrapper that provides them by default.
utils/ajax.js
const defaultHeaders = {
Authorization: 'c7b9392955ce63b38cf090...etc'
};
export const get = (url, headers) =>
ajax.get(url, Object.assign({}, defaultHeaders, headers));
my-example.js
import * as ajax from './utils/ajax';
// Usage is the same, but now with defaults
ajax.get(`http://localhost/products?${action.q}`;)
I'm using redux-observable but this applies to rxjs; maybe the next answer its too over-engineered, but I needed to get dinamically the headers depending of certain factors, without affecting the unit testing (something decoupled from my epics too), and without changing the sintax of ajax.get/ajax.post etc, this is what I found:
ES6 has proxies support, and after reading this and improving the solution here, I'm using a High Order Function to create a Proxy in the original rxjs/ajax object, and return the proxified object; below is my code:
Note: I'm using typescript, but you can port it to plain ES6.
AjaxUtils.ts
export interface AjaxGetHeadersFn {
(): Object;
}
// the function names we will proxy
const getHeadersPos = (ajaxMethod: string): number => {
switch (ajaxMethod) {
case 'get':
case 'getJSON':
case 'delete':
return 1;
case 'patch':
case 'post':
case 'put':
return 2;
default:
return -1;
}
};
export const ajaxProxy = (getHeadersFn: AjaxGetHeadersFn) =>
<TObject extends object>(obj: TObject): TObject => {
return new Proxy(obj, {
get(target: TObject, propKey: PropertyKey) {
const origProp = target[propKey];
const headersPos = getHeadersPos(propKey as string);
if (headersPos === -1 || typeof origProp !== 'function') {
return origProp;
}
return function (...args: Array<object>) {
args[headersPos] = { ...args[headersPos], ...getHeadersFn() };
// #ts-ignore
return origProp.apply(this, args);
};
}
});
};
You use it this way:
ConfigureAjax.ts
import { ajax as Ajax } from 'rxjs/ajax'; // you rename it
// this is the function to get the headers dynamically
// anything, a function, a service etc.
const getHeadersFn: AjaxGetHeadersFn = () => ({ 'Bearer': 'BLABLABLA' });
const ajax = ajaxProxy(getHeadersFn)(Ajax); // proxified object
export default ajax;
Anywhere in you application you import ajax from ConfigureAjax.ts and use it as normal.
If you are using redux-observable you configure epics this way (injecting ajax object as a dependency more info here):
ConfigureStore.ts
import ajax from './ConfigureAjax.ts'
const rootEpic = combineEpics(
fetchUserEpic
)({ ajax });
UserEpics.ts
// the same sintax ajax.getJSON, decoupled and
// under the covers with dynamically injected headers
const fetchUserEpic = (action$, state$, { ajax }) => action$.pipe(
ofType('FETCH_USER'),
mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe(
map(response => ({
type: 'FETCH_USER_FULFILLED',
payload: response
}))
)
);
Hope it helps people looking for the same :D
Related
Imagine a case where an editor adds a “Latest Products” component to a page using dynamic zone: they add a title, a summary, and then the latest products will automatically be fetched to be available in the response. How can I add this data to the response of the component?
I know we can override the response for content types using a custom controller, but I can't find anything for how to modify the response for a component.
Maybe there's an alternative approach I haven't thought of, but coming from a Drupal preprocess-everything background this is all I can think of.
Any help appreciated!
I'm sure this isn't the best way, but I created a service for components that can be used in the content type controller to modify the response. Any improvements appreciated!
/api/custom-page/controllers/custom-page.js
'use strict';
/**
* custom-page controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::custom-page.custom-page', ({ strapi }) => ({
async find(ctx) {
const componentService = strapi.service('api::components.components');
let { data, meta } = await super.find(ctx);
data = await Promise.all(data.map(async (entry, index) => {
if(entry.attributes.sections){
await Promise.all(entry.attributes.sections.map(async (section, index) => {
const component = await componentService.getComponent(section);
entry.attributes.sections[index] = component;
}));
}
return entry;
}));
return { data, meta };
},
}));
/components/services/components.js
'use strict';
/**
* components service.
*/
module.exports = () => ({
getComponent: async (input) => {
// Latest products
if(input.__component === 'sections.latest-products'){
input.products = 'customdatahere';
}
return input;
}
});
I am trying to unit test a function which makes an async call using an Axios helper instance. I have attempted multiple ways of trying to unit test this but I can not seem to find any material online which has helped. I've been stuck on this problem for a few days which is frustrating so any help would be appreciated! Below are the Axios Helper file (api.js)
api.js
import axios from 'axios'
const API = (token = null) => {
let headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-key': process.env.NEXT_PUBLIC_API_HEADER_SUBSCRIPTION_KEY
}
if (token) {
const tokenHeader = { Authorization: 'Bearer ' + token }
headers = { ...headers, ...tokenHeader }
}
const url = process.env.NEXT_PUBLIC_API_BASE_URL
const API = axios.create({
baseURL: url,
headers
})
return API
}
export default API
mocked API
export default {
post: jest.fn(() =>
Promise.resolve({
data: {}
})
),
get: jest.fn(() =>
Promise.resolve({
data: {}
})
)
}
action file
export const initiate2FA = (destinationValue) => async () => {
const twoFactorAuth = destinationValue
const res = await API().post('/foo', {
Destination: twoFactorAuth
})
return res
}
Action.test.js
import API from 'api/api'
import { initiate2FA } from 'actions/userActions'
jest.mock('api/api')
const mockedAxios = API
const dispatch = jest.fn()
describe('Initiate2FA function', () => {
it('bar', async () => {
mockedAxios.get.mockImplementationOnce(() => Promise.resolve({ status: 200 }))
const t = await dispatch(initiate2FA('test#test.com'))
console.log(t)
})
})
My issue with the above test file is that it returns an anonymous function and I do not know how to handle this to pass the unit test. The goal of the test is to make sure the function is called. I am not sure if I am approaching this the correct way or should change my approach.
Again, any suggestions would be great!
Mocking an API call is something you can mock on your own React component, instead of a function, and the best option would be to not mock anything on your component. Here you can read all about why you should not mock your API functions. At the end of the article, you're going to find a library called Mock Service Worker which you can use for your purpose.
The way you declare you have an actual HTTP called that needs to be mocked would be something like this:
rest.get('/foo', async (req, res, ctx) => {
const mockedResponse = {bar: ''};
return res(ctx.json(mockedResponse))
}),
If you just need to unit test a function, you can still use Mock Service Worker to resolve the HTTP request, and then test what happens after that. This would still be your first choice. And the test would look like:
// this could be in another file or on top of your tests.
rest.get('/foo', async (req, res, ctx) => {
const mockedResponse = {bar: ''};
return res(ctx.json(mockedResponse))
}),
// and this would be your test
describe('Initiate2FA function', () => {
it('bar', async () => {
const res = await initiate2FA('test#test.com');
expect(res).toBe({bar: '');
})
})
I'm trying to use useEffect in my React app but also refactor things more modularly. Shown below is the heart of actual working code. It resides in a Context Provider file and does the following:
1. Calls AWS Amplify to get the latest Auth Access Token.
2. Uses this token, in the form of an Authorization header, when an Axios GET call is made to an API Endpoint.
This works fine but I thought it would make more sense to move Step #1 into its own useEffect construct above. Furthermore, in doing so, I could then also store the header object as its own Context property, which the GET call could then reference.
Unfortunately, I can now see from console log statements that when the GET call starts, the Auth Access Token has not yet been retrieved. So the refactoring attempt fails.
useEffect(() => {
const fetchData = async () => {
const config = {
headers: { "Authorization":
await Auth.currentSession()
.then(data => {
return data.getAccessToken().getJwtToken();
})
.catch(error => {
alert('Error getting authorization token: '.concat(error))
})
}};
await axios.get('http://127.0.0.1:5000/some_path', config)
.then(response => {
// Process the retrieved data and populate in a Context property
})
.catch(error => {
alert('Error getting data from endpoint: '.concat(error));
});
};
fetchData();
}, [myContextObject.some_data]);
Is there a way of refactoring my code into two useEffect instances such that the first one will complete before the second one starts?
You could hold the config object in a state. This way you can separate both fetch calls and trigger the second one once the first one finished:
const MyComponent = props => {
const myContextObject = useContext(myContext);
const [config, setConfig] = useState(null);
useEffect(() => {
const fetchData = async () => {
const config = {
headers: {
Authorization: await Auth.currentSession()
.then(data => {
return data.getAccessToken().getJwtToken();
})
.catch(error => {
alert("Error getting authorization token: ".concat(error));
})
}
};
setConfig(config);
};
fetchData();
}, [myContextObject.some_data]);
useEffect(() => {
if (!config) {
return;
}
const fetchData = async () => {
await axios
.get("http://127.0.0.1:5000/some_path", config)
.then(response => {
// Process the retrieved data and populate in a Context property
})
.catch(error => {
alert("Error getting data from endpoint: ".concat(error));
});
};
fetchData();
// This should work for the first call (not tested) as it goes from null to object.
// If you need subsequent changes then youll have to track some property
// of the object or similar
}, [config]);
return null;
};
I have a problem with updating props in my test after some code refactor. I use custom render and mock axios request but my component doesn't rerender (?). In my component in async ComponentDidMount() I do POST request. When I do manual test in browser everything works fine.
I receive exception produced by getByText():
Unable to find an element with the text: /Tasty Metal Keyboard/i. This
could be because the text is broken up by multiple elements. In this
case, you can provide a function for your text matcher to make your
matcher more flexible.
/** import React, mockAxios etc. */
const middleware = applyMiddleware(thunk);
const inputRootPath = document.createElement('input');
inputRootPath.id = 'rootPath';
inputRootPath.hidden = true;
inputRootPath.value = 'http://localhost/';
/**
*
* #param {*} ui komponent
* #param {*} param { initialState, store }
*/
export function renderWithRedux(
ui,
{ initialState, store = createStore(rootReducer, initialState, compose(middleware)) } = {},
) {
return {
...render(
<Provider store={store}>
{ui}
</Provider>,
{ container: document.body.appendChild(inputRootPath) }
),
store,
};
}
test('should render annex list', async () => {
const agBuilder = () => {
return {
ID: faker.random.number(),
NM: faker.commerce.productName(),
};
};
const agreements = [agBuilder(), agBuilder(), agBuilder(), agBuilder()];
mockAxios.post.mockResolvedValueOnce({ data: { ANLST: agreements } });
const { getByText, } = await renderWithRedux(<ConnectedAgreements />);
const optionRE = new RegExp(`${agreements[0].NM}`, 'i');
expect(getByText(optionRE)).toBeInTheDocument();
mockAxios.post.mockClear();
});
mocks/axios.js
export default {
get: jest.fn().mockResolvedValue({ data: {} }),
post: jest.fn().mockResolvedValue({ data: {} }),
};
I found solution. It turns out that after some code refactor I have another reducer which takes dispatch action invoked in CDM. It destructure axios response so my test code should have:
mockAxios.post.mockResolvedValueOnce({ data: { ANLST: agreements, CLS: {}, EXLDT: {} } });
Missing CLS and EXLDT properties casue test fail. Jest however doesn't print error that something is missing or undefined ¯_(ツ)_/¯ . Exception produced by getByText() was misleading.
first func:
updateMark(item: MarkDTO) {
this.service
.put(item, this.resource)
.subscribe(() => this.markEdit = null);
}
second func:
put(item: MarkDTO, rcc: string): Observable<MarkDTO> {
const rdto = new MarkRDTO(item);
const url = `${this.getUrl('base')}${rcc}/marks/${rdto.rid}`;
const obs = this.http.put<MarkDTO>(url, rdto, { withCredentials: true })
.pipe(map((r: MarkDTO) => new MarkDTO(r)))
.share();
obs.subscribe(newMark => this.storage.update(newMark, rcc));
return obs;
}
in service i need to update data after request
but also in same time i need to clear current editItem
all of it must be done after i subscribe to one httpRequest
.share() - suport from rxjs-compat package (i want to remove this dep in closest time)
without .share() - work only 1 of 2 steps
current rxjs version is 6.3.3
Help who can...
There is a pipeable share operator, that you would use the same way you use map() (i.e. inside pipe()) and thus doesn't need rxjs-compat.
But you don't need share() here. All you need is the tap() operator:
put(item: MarkDTO, rcc: string): Observable<MarkDTO> {
const rdto = new MarkRDTO(item);
const url = `${this.getUrl('base')}${rcc}/marks/${rdto.rid}`;
return this.http.put<MarkDTO>(url, rdto, { withCredentials: true })
.pipe(
map(r => new MarkDTO(r)),
tap(newMark => this.storage.update(newMark, rcc))
);
}