I have a React App, that talks to several REST APIs.
I have refactored my app from redux-thunks to use react-query for the business logic of calling the APIs.
Watching videos on react-query, it was advised to abstract this into a custom hook.
So, for example:
//
// useTodos.js
import { useQuery } from 'react-query';
import { TodoApi } from 'my-api-lib';
import config from '../config';
const todoApi = new TodoApi(config.TODO_API_BASE_URL);
const useTodos = (params) =>
useQuery(
[todo, params],
() => todoApi.fetchTodos(params)
);
I have another App where I could use these hooks to also talk to the REST APIs. So I'd like to move the hooks into a common library. But the config is provided by the client. How do I get the config (TODO_BASE_API_URI) or even the "todoApi" instance, to the custom hook from the client?
In Redux I essentially dependency-injected the TodoApi instance at startup with "thunk with extra argument"
Is there a "hooky" way to get the global config to my custom hook?
The library (I assume it's my-api-lib) should export a function that expects the url (or any other config), and returns the useTodoApi hook.
In your common library:
import { useQuery } from 'react-query';
import { TodoApi } from './TodoApi';
export const createUseTodoApi = url => {
const todoApi = new TodoApi(url);
return params =>
useQuery(
[todo, params],
() => todoApi.fetchTodos(params)
);
}
In your apps:
import { createTodoApi } from 'my-api-lib';
import config from '../config';
export const useTodoApi = createUseTodoApi(config.TODO_API_BASE_URL);
Related
axios code:
import Axios from "axios";
export const getBlogPosts = async (setter) => {
try {
const res = await Axios.get(`https://jsonplaceholder.typicode.com/posts/1`);
if (res.status === 200 && res?.data) {
setter(res?.data);
}
} catch (error) {
console.log(error.message);
}
};
this is my app code :
import React, { useEffect, useState } from "react";
import { getBlogPosts } from "./_helper";
export function TestCustomerCreate() {
const [gridData, setGridData] = useState();
useEffect(() => {
getBlogPosts(setGridData);
}, []);
console.log(gridData);
return (
<div>
<h1>This is test create form</h1>
</div>
);
}
error msg: Request failed with status code 404
but when i'm using fetch its working fine (i'm using "axios": "0.19.2" with Metronic theme react (7.0.8)
Reason for this is in Metronic use axios-mock-adapter for demo purpose, it intercepts axios requests and redirects to mocked handlers. Mock Back-end
To use real REST APIs need to do 2 things.
remove mock initialization. For that remove mock initialization in
the src/index.js or src/main.js file.
Remove API initialization from the src/index or src/main.js
// Remove this to disable mock API
MockService.init();
// API service init
ApiService.init();
I am working on a React v16 app and need to load a heavy (IMO) library for xlsx export of data.
I'm using functional components/hooks.
I understand and have used the <Suspense /> component and lazy for lazy loading modules. but since this item is not a component, it is simply a library function i need to run with an onClick event, i can't use lazy/suspense.
How can i lazy load this function only when needed? (writeXlsxFile)
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
//...
import writeXlsxFile from "write-excel-file";
//...
const fileCreate = async () => {
await writeXlsxFile(exportData, {
schema,
fileName: `file.xlsx`,
});
};
return(//...
JavaScript, and by direct association, React, supports Dynamic Imports.
So,
const fileCreate = async () => {
const {default: writeXlsxFile} = await import ('write-excel-file')
void await writeXlsxFile(exportData, {
schema,
fileName: `file.xlsx`,
});
}
I am building an app where the user connects to a server by entering the URL when logging in.
Each server provides an API and I can't hard-code the value for the BaseURL when creating the Axios client instance. I somehow need to globally store the URL and set it based on the user input in the login form.
So I would like to know what is a proper way to do this. I am really not sure what is the best approach to dynamically store a value that I can access when creating the API connection. Any ideas and best practice suggestions are welcome.
Here is the code for creating the API connection: client.js
import { create } from 'apisauce';
const api = create({
baseURL: "BASE_URL_FROM_USER_INPUT" + "server/odata/"
});
export default api;
This is how I use the API: users.js
import api from './client';
const getUsers = () => api.get("/users");
export default {
getUsers
}
This is how I will get my data:
import React, { useState, useEffect } from "react";
import usersApi from '../api/users';
const TestScreenAuthenticated = ({navigation}) => {
const [users, setUsers] = useState([]);
useEffect(() => {
loadUsers();
});
const loadUsers = async () => {
const response = await usersApi.getUsers();
setUsers(response);
}
...
};
export default TestScreenAuthenticated;
you can use the localStoreto store the baseUrl.
//save the value
localStore.setItem('baseUrl', value)
//read the value
localStore.getItem('baseUrl')
If you can use the .env file in your project and put the web service address there. This file is out of reach of the user viewing the site and will not be able to edit it
this link:
https://create-react-app.dev/docs/adding-custom-environment-variables/
I have a reactjs app
In MyApp component I use an import at top like this:
import { ProvideAuth } from "util/auth.js";
Internally this file util/auth.js I have this code (I import another js file at top like this):
import analytics from "./analytics";
export function ProvideAuth({ children }) {
.....
}
How can I make this import analytics from "./analytics" dynamically depending on a cookie value?.
I made this code, but it doesn't work:
function loadLazyModule() {
console.log("loadLazyModule");
const _module = React.lazy(() =>
import("./analytics.js")
);
return _module;
}
// Provider hook that creates auth object and handles state
export function ProvideAuth({ children }) {
if (statisticsCookie == 'Y') {
console.log("statisticsCookie", statisticsCookie);
loadLazyModule();
}
.....
}
Finally my analytics.js has this code:
// Initialize analytics and plugins
// Documentation: https://getanalytics.io
const analytics = Analytics({
debug: process.env.NODE_ENV !== "production",
plugins: [
googleAnalyticsPlugin({
trackingId: process.env.NEXT_PUBLIC_GA_TRACKING_ID,
}),
],
});
....
export default analytics;
I need to this file import only if my cookie is enabled (has value 'Y'):
import analytics from "./analytics";
Help me please!
Thanks!
you can do it like this.
you can also use, .than().catch() function after that.
async function load() {
let say = await import('./say.js');
say.hi(); // Hello!
say.bye(); // Bye!
say.default(); // Module loaded (export default)!
}
or via import module
let modulePath = prompt("Which module to load?");
import(modulePath)
.then(obj => <module object>)
.catch(err => <loading error,e.g. if no such module>)
it's described here
https://javascript.info/modules-dynamic-imports
I have a React container in which I am making the API call and would like to be able to test this using jest and enzyme but unsure how to.
This is my code:
import React from "react";
import Search from "../../components/Search";
import { API_KEY } from "../../../config";
class SearchContainer extends React.Component {
state = {
articles: []
};
performSearch = event => {
fetch(
`http://content.guardianapis.com/search?q=${event}&api-key=${API_KEY}`
)
.then(response => response.json())
.then(data => this.setState({ articles: data.response.results }));
};
render() {
return (
<Search
performSearch={this.performSearch}
articles={this.state.articles}
/>
);
}
}
export default SearchContainer;
That's a great thing about unit testing, they force you to write better code. So to properly test this component, you should do the following:
Extract performSearch from The component into a separate file e.g. api.js
Mock performSearch in your api.js file (jest: mock a module)
Now you can test that the fetch function was called.
Note that with this code organization you could separately test your API calls and your SearchContainer without calling your API service.
I would approach this by extracting performSearch out into a module that wraps fetch. See this great article on testing things you don't own.
After that, you may not need SearchContainer any more if you store the articles state within the Search component. Since you're already using dependency injection with the performSearch property, you can pass in a mock object in place of it and use jest.fn() to ensure it is called.
For example:
const fakePerformSearch = jest.fn();
const component = Shallow(<Search performSearch={fakePerformSearch}/>);
expect(fakePerformSearch).toHaveBeenCalled();
And then test your new fetch wrapper as you would any JavaScript.
A lot of the other answers recommend using Jest's import mocker or a mock function, however, this tests implementation over behavior.
It's better to stub the environment instead of the tools. Let's write a test using an HTTP interceptor like nock. The beauty of this is you can migrate to different data fetching tools or make changes the fetch behavior and get feedback from your tests.
// src/SearchContainer/SearchContainer.test.js
import React from "react";
import nock from "nock";
import {mount} from "enzyme";
import Search from "../../components/Search";
import { API_KEY } from "../../../config";
describe('<SearchContainer />', async () => {
it('searches for articles', () => {
const scope = nock('http://content.guardianapis.com')
.get('/search')
.query({'api-keys': API_KEY, {q: 'some article'}})
.reply(200, {
results: [...]
})
const wrapper = mount(<SearchContainer />);
const searchInput = wrapper.find('[data-test-id="search-input"]');
await searchInput.simulate('change', { target: { value: 'some article' } });
const articles = wrapper.find('[data-test-id="articles"]');
expect(articles.length > 0).toBe(true);
expect(scope.isDone()).toBe(true);
});
});
For a deeper dive on testing API calls, I wrote a blog post Testing Components that make API calls.