How to call useRouter inside useFetch's onFetchError by VueUse? - javascript

I need to create a custom fetch composable from VueUse using createFetch() and I want to check if a request returns 401 status, I'd like the route to be redirected to the login route.
export const useApiFetch = createFetch({
baseUrl: import.meta.env.VITE_API_BASE_URL,
options: {
beforeFetch({ options }) {
const { user } = useSessionStore()
if (user)
options.headers.Authorization = `Basic ${user.user_id}:${user.password}`
return { options }
},
onFetchError(response) {
const route = useRoute()
const router = useRouter()
if (route.name !== 'login' && response.status === 401)
return router.push('/login')
}
}
})
But everytime it hits the error, useRoute and useRouter are undefined, and yes.. I have checked that it runs in setup
<script setup>
const submit = async () => {
const { error, data } = await useApiFetch('/login').post(form)
}
</script>
Did I miss something or is there a better way to do this? thanks

Vue composables are primarily expected to be called in setup block. Other usages depend on their implementation and needs to be confirmed. The main restriction is that a composable is linked to specific component instance, in this case useRouter uses provide/inject to get router instance through component hierarchy; this often can be be deduced without checking the actual implementation.
It's possible to directly import router instance instead of using useRouter but this may result in module circular dependencies and this may not be work for other composables.
createFetch wasn't designed for this usage and needs to be wrapped with custom composable that guarantees that other composables will be called in time:
let useApiFetchFn;
const useApiFetch = (...args) => {
if (!useApiFetchFn) {
const sessionStore = useSessionStore()
const route = useRoute()
const router = useRouter()
useApiFetchFn = createFetch(...)
}
return useApiFetchFn(...args);
}
Whether it's correct to cache the result to useApiFetchFn depends on the implementation, in this case it's acceptable. At this point it may be more straightforward to use useFetch directly and compose the options similarly to how createFetch does that, most of its code is dedicated to TS support and variadic arguments that may not be needed in this case.

Related

How to Export a Variable to Another file?

const ForgotPassword = ({ ...others }) => {
const theme = useTheme();
const { token } = useParams()
const handleSubmit = async(values) => {
console.log('load')
try {
const response = await axios.post(`http://localhost:3001/auth/reset/${token}`, {
password: values.password
});
if (response.data.msg === 'Password reset token is invalid or has expired') {
console.log(response)
} else {
// success message
}
} catch (err) {
console.error(err);
console.log("Something went wrong. Please try again later.")
}
console.log('not load')
};
return (
//...Content...
);
};
export default ForgotPassword;
I need to export token to use in other files how to do ?
I tried like this
export const token = useParams().token;
export const Token = token
etc ..
I'm new to this, could anyone tell me how to export token?
You can't export token directly because it's a local variable and is not available to export at the start of your app. Depending on where token needs to be accessed, you can pass it to a child component with a prop. If this fits your use case, it'll be the easiest method.
If you instead need to access the data from adjacent or parent components, you can set up an application store and access it from parent components. Something like React Redux will do this for you. Take a look at the React Redux Quick Start page for details on how that might work. This is my preferred method, but you can also use React Context instead of an external library to accomplish the same task.

Trying call useQuery in function with react-apollo-hooks

I want to call useQuery whenever I need it,
but useQuery can not inside the function.
My trying code is:
export const TestComponent = () => {
...
const { data, loading, error } = useQuery(gql(GET_USER_LIST), {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
...
...
const onSaveInformation = async () => {
try {
await updateInformation({...})
// I want to call useQuery once again.
} catch (e) {
return e
}
}
...
How do I call useQuery multiple times?
Can I call it whenever I want?
I have looked for several sites, but I could not find a solutions.
From apollo docs
When React mounts and renders a component that calls the useQuery hook, Apollo Client automatically executes the specified query. But what if you want to execute a query in response to a different event, such as a user clicking a button?
The useLazyQuery hook is perfect for executing queries in response to
events other than component rendering
I suggest useLazyQuery. In simple terms, useQuery will run when your component get's rendered, you can use skip option to skip the initial run. And there are some ways to refetch/fetch more data whenever you want. Or you can stick with useLazyQuery
E.g If you want to fetch data when only user clicks on a button or scrolls to the bottom, then you can use useLazyQuery hook.
useQuery is a declarative React Hook. It is not meant to be called in the sense of a classic function to receive data. First, make sure to understand React Hooks or simply not use them for now (90% of questions on Stackoverflow happen because people try to learn too many things at once). The Apollo documentation is very good for the official react-apollo package, which uses render props. This works just as well and once you have understood Apollo Client and Hooks you can go for a little refactor. So the answers to your questions:
How do I call useQuery multiple times?
You don't call it multiple times. The component will automatically rerender when the query result is available or gets updated.
Can I call it whenever I want?
No, hooks can only be called on the top level. Instead, the data is available in your function from the upper scope (closure).
Your updateInformation should probably be a mutation that updates the application's cache, which again triggers a rerender of the React component because it is "subscribed" to the query. In most cases, the update happens fully automatically because Apollo will identify entities by a combination of __typename and id. Here's some pseudocode that illustrates how mutations work together with mutations:
const GET_USER_LIST = gql`
query GetUserList {
users {
id
name
}
}
`;
const UPDATE_USER = gql`
mutation UpdateUser($id: ID!, $name: String!) {
updateUser(id: $id, update: { name: $name }) {
success
user {
id
name
}
}
}
`;
const UserListComponen = (props) => {
const { data, loading, error } = useQuery(GET_USER_LIST);
const [updateUser] = useMutation(UPDATE_USER);
const onSaveInformation = (id, name) => updateUser({ variables: { id, name });
return (
// ... use data.users and onSaveInformation in your JSX
);
}
Now if the name of a user changes via the mutation Apollo will automatically update the cache und trigger a rerender of the component. Then the component will automatically display the new data. Welcome to the power of GraphQL!
There's answering mentioning how useQuery should be used, and also suggestions to use useLazyQuery. I think the key takeaway is understanding the use cases for useQuery vs useLazyQuery, which you can read in the documentation. I'll try to explain it below from my perspective.
useQuery is "declarative" much like the rest of React, especially component rendering. This means you should expect useQuery to be called every render when state or props change. So in English, it's like, "Hey React, when things change, this is what I want you to query".
for useLazyQuery, this line in the documentation is key: "The useLazyQuery hook is perfect for executing queries in response to events other than component rendering". In more general programming speak, it's "imperative". This gives you the power to call the query however you want, whether it's in response to state/prop changes (i.e. with useEffect) or event handlers like button clicks. In English, it's like, "Hey React, this is how I want to query for the data".
You can use fetchMore() returned from useQuery, which is primarily meant for pagination.
const { loading, client, fetchMore } = useQuery(GET_USER_LIST);
const submit = async () => {
// Perform save operation
const userResp = await fetchMore({
variables: {
// Pass any args here
},
updateQuery(){
}
});
console.log(userResp.data)
};
Read more here: fetchMore
You could also use useLazyQuery, however it'll give you a function that returns void and the data is returned outside your function.
const [getUser, { loading, client, data }] = useLazyQuery(GET_USER_LIST);
const submit = async () => {
const userResp = await getUser({
variables: {
// Pass your args here
},
updateQuery() {},
});
console.log({ userResp }); // undefined
};
Read more here: useLazyQuery
You can create a reusable fetch function as shown below:
// Create query
const query = `
query GetUserList ($data: UserDataType){
getUserList(data: $data){
uid,
first_name
}
}
`;
// Component
export const TestComponent (props) {
const onSaveInformation = async () => {
// I want to call useQuery once again.
const getUsers = await fetchUserList();
}
// This is the reusable fetch function.
const fetchUserList = async () => {
// Update the URL to your Graphql Endpoint.
return await fetch('http://localhost:8080/api/graphql?', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query,
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
}).then(
response => { return response.json(); }
).catch(
error => console.log(error) // Handle the error response object
);
}
return (
<h1>Test Component</h1>
);
}
Here's an alternative that worked for me:
const { refetch } = useQuery(GET_USER_LIST, {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
}
);
const onSaveInformation = async () => {
try {
await updateInformation({...});
const res = await refetch({ variables: { ... }});
console.log(res);
} catch (e) {
return e;
}
}
And here's a similar answer for a similar question.
Please use
const { loading, data, refetch } = useQuery(Query_Data)
and call it when you need it i.e
refetch()

Sinon Spy never called but it should be

Problem
I'm testing a custom redux middleware using Jest and SinonJS and more precisely I want to test if some functions are called on special conditions inside the middleware.
I use SinonJS for creating the spies and I run my tests with Jest. I initialised the spies for the specific functions I want to track and when I check if the spies has been called, the spies has not been even if it should be (manually tested).
Code
Here is the middleware I want to test :
import { Cookies } from 'react-cookie';
import setAuthorizationToken from './setAuthorizationToken';
let cookies = new Cookies();
export const bindTokenWithApp = (store) => (next) => (action) => {
// Select the token before action
const previousToken = getToken(store.getState());
// Dispatch action
const result = next(action);
// Select the token after dispatched action
const nextToken = getToken(store.getState());
if (previousToken !== nextToken) {
if (nextToken === '') {
setAuthorizationToken(false);
cookies.remove(SESSION_COOKIE_NAME, COOKIE_OPTIONS);
} else {
cookies.set(SESSION_COOKIE_NAME, nextToken, COOKIE_OPTIONS);
setAuthorizationToken(nextToken);
}
}
return result;
};
Here is my actual test
import { bindTokenWithApp } from './middleware';
import { Cookies } from 'react-cookie';
import sinon, { assert } from 'sinon';
import setAuthorizationToken from './setAuthorizationToken';
describe('bindTokenWithApp', () => {
const next = jest.fn();
const action = jest.fn();
let cookies = new Cookies();
it('removes cookies when there is no token', () => {
// My actual not working spies
const cookieSpy = sinon.spy(cookies.remove);
const authSpy = sinon.spy(setAuthorizationToken);
// Stub for the specific case. This code works,
// I console.logged in the middleware and I'm getting the below values
const getState = sinon.stub();
getState.onFirstCall().returns({ auth: { token: 'a token' } });
getState.onSecondCall().returns({ auth: { token: '' } });
const store = { getState: getState };
bindTokenWithApp(store)(next)(action);
assert.calledOnce(cookieSpy);
assert.calledOnce(authSpy);
// Output : AssertError: expected remove to be called once but was called 0 times
// AssertError: expected setAuthorizationToken to be called once but was called 0 times
cookieSpy.restore(); // <= This one works
authSpy.restore(); // TypeError: authSpy.restore is not a function
});
});
I've read SinonJS doc and a few StackOverFlow posts but without solutions. I also can't call authSpy.restore();. I think I do not initialise spies the right way and I'm misunderstanding a concept in SinonJS but I can't find which one !
The setAuthorizationToken signature is
(alias) const setAuthorizationToken: (token: any) => void
import setAuthorizationToken
I think it's a classical module so I can't figure out why I struggle with authSpy.restore();
The two spies you have actually have two different fixes, both with the same underlying problem. sinon.spy(someFunction) doesn't actually wrap someFunction itself, it returns a spy for it but doesn't perform any replacement.
For the first spy, there exists a shorthand to automatically wrap an object method: sinon.spy(cookie, 'remove') should do what you need.
For the second spy, it is more complicated as you need to wrap the spy around the default export of setAuthorizationToken. For that you will need something like proxyquire. Proxyquire is a specialized require mechanism that allows you to replace imports with your desired test methods. Here's a brief of what you'll need to do:
const authSpy = sinon.spy(setAuthorizationToken);
bindTokenWithApp = proxyquire('./middleware', { './setAuthorizationToken': authSpy});

set data only on first load with nuxt.js

I'm new to nuxt.js so I'm wondering what could be the best way to set up some data via REST api.
I have a store folder like this:
store
-posts.js
-categories.js
-index.js
I've tried to set the data with nuxtServerInit actions in the index.js:
export const actions = {
async nuxtServerInit({ dispatch }) {
await dispatch('categories/setCategories')
await dispatch('posts/loadPosts','all')
}
}
But doesn't works: actions are dispatched (on the server) but data are not set.
So I've tried with fetch but this method is called every time the page where I have to display posts is loaded. Even if, in the general layout, I do this:
<template>
<div>
<Header />
<keep-alive>
<nuxt/>
</keep-alive>
</div>
</template>
So my solution, for now, is to use fetch in this way,
In the page component:
async fetch({store}){
if(store.getters['posts/getPosts'].length === 0 && store.getters['categories/getCategories'].length === 0 ){
await store.dispatch('categories/setCategories')
await store.dispatch('posts/loadPosts','all')
}
}
Also, one thing I noted is that fetch seems not working on the root page component (pages/index.vue)
My solution seems works, but there is maybe another better way to set the data?
There's no out of the box solution for this as it's specific to your requirements/needs. My solution is very similar to yours but instead of checking the size of data array I introduced additional variable loaded in every store module. I only fetch data if loaded is false. This approach is more suitable in apps that have user generated content and require authentication. It will work optimally with SSR and client-side, and it won't try to fetch data on every page visit if user has no data.
You could also simplify your fetch method like this:
async fetch()
{
await this.$store.dispatch('posts/getOnce')
}
Now your posts.js store module will look something like this:
export const state = () => ({
list: [],
loaded: false
})
export const actions = {
async getOnce({ dispatch, state }) {
if (!state.loaded) {
dispatch('posts/get')
}
},
async get({ commit, state }) {
await this.$axios.get(`/posts`)
.then((res) => {
if (res.status === 200) {
commit('set', res.data.posts)
}
})
}
}
export const mutations = {
set(state, posts) {
state.list = posts
state.loaded = true
}
}

Dependency Injection in a redux action creator

I'm currently building a learner React/Redux Application and I can not wrap my head around how to do dependency injection for services.
To be more specific: I have a BluetoothService (which abstracts a 3rd Party Library) to scan for and connect to other devices via bluetooth. This service gets utilized by the action creators, something like this:
deviceActionCreators.js:
const bluetoothService = require('./blueToothService')
function addDevice(device) {
return { type: 'ADD_DEVICE', device }
}
function startDeviceScan() {
return function (dispatch) {
// The Service invokes the given callback for each found device
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
module.exports = { addDevice, startDeviceScan };
(I am using the thunk-middleware)
My Problem however is: how to inject the service itself into the action-creator?
I don't want that hard-coded require (or importin ES6) as I don't think this is a good pattern - besides making testing so much harder. I also want to be able to use a mock-service while testing the app on my work station (which doesn't have bluetooth) - so depending on the environment i want another service with the same interface injected inside my action-creator. This is simply not possible with using a static import.
I already tried making the bluetoothService a parameter for the Method itself (startDeviceScan(bluetoothService){}) - effectively making the method itself pure - but that just moves the problem to the containers using the action. Every container would have to know about the service then and get an implementation of it injected (for example via props).
Plus when I want to use the action from within another action I end up with the same problem again.
The Goal:
I want to decide on bootstrapping time which implemenation to use in my app.
Is there a good way or best practice for doing this?
React-thunk supports passing an arbitrary object to a thunk using withExtraArgument. You can use this to dependency-inject a service object, e.g.:
const bluetoothService = require('./blueToothService');
const services = {
bluetoothService: bluetoothService
};
let store = createStore(reducers, {},
applyMiddleware(thunk.withExtraArgument(services))
);
Then the services are available to your thunk as a third argument:
function startDeviceScan() {
return function (dispatch, getstate, services) {
// ...
services.bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
This is not as formal as using a dependency-injection decorator in Angular2 or creating a separate Redux middleware layer to pass services to thunks---it's just an "anything object" which is kind of ugly---but on the other hand it's fairly simple to implement.
You can use a redux middleware that will respond to an async action. In this way you can inject whatever service or mock you need in a single place, and the app will be free of any api implementation details:
// bluetoothAPI Middleware
import bluetoothService from 'bluetoothService';
export const DEVICE_SCAN = Symbol('DEVICE_SCAN'); // the symbol marks an action as belonging to this api
// actions creation helper for the middleware
const createAction = (type, payload) => ({
type,
payload
});
// This is the export that will be used in the applyMiddleware method
export default store => next => action => {
const blueToothAPI = action[DEVICE_SCAN];
if(blueToothAPI === undefined) {
return next(action);
}
const [ scanDeviceRequest, scanDeviceSuccess, scanDeviceFailure ] = blueToothAPI.actionTypes;
next(createAction(scanDeviceRequest)); // optional - use for waiting indication, such as spinner
return new Promise((resolve, reject) => // instead of promise you can do next(createAction(scanDeviceSuccess, device) in the success callback of the original method
bluetoothService.startDeviceSearch((device) => resolve(device), (error) = reject(error)) // I assume that you have a fail callback as well
.then((device) => next(createAction(scanDeviceSuccess, device))) // on success action dispatch
.catch((error) => next(createAction(scanDeviceFailure, error ))); // on error action dispatch
};
// Async Action Creator
export const startDeviceScan = (actionTypes) => ({
[DEVICE_SCAN]: {
actionTypes
}
});
// ACTION_TYPES
export const SCAN_DEVICE_REQUEST = 'SCAN_DEVICE_REQUEST';
export const SCAN_DEVICE_SUCCESS = 'SCAN_DEVICE_SUCCESS';
export const SCAN_DEVICE_FAILURE = 'SCAN_DEVICE_FAILURE';
// Action Creators - the actions will be created by the middleware, so no need for regular action creators
// Applying the bluetoothAPI middleware to the store
import { createStore, combineReducers, applyMiddleware } from 'redux'
import bluetoothAPI from './bluetoothAPI';
const store = createStore(
reducers,
applyMiddleware(bluetoothAPI);
);
// Usage
import { SCAN_DEVICE_REQUEST, SCAN_DEVICE_SUCCESS, SCAN_DEVICE_FAILURE } from 'ACTION_TYPES';
dispatch(startDeviceScan([SCAN_DEVICE_REQUEST, SCAN_DEVICE_SUCCESS, SCAN_DEVICE_FAILURE]));
You dispatch the startDeviceScan async action, with the action types that will be used in the creation of the relevant actions. The middleware identifies the action by the symbol DEVICE_SCAN. If the action doesn't contain the symbol, it dispatches it back to the store (next middleware / reducers).
If the symbol DEVICE_SCAN exists, the middleware extracts the action types, creates and dispatches a start action (for a loading spinner for example), makes the async request, and then creates and dispatches a success or failure action.
Also look at the real world redux middle example.
Can you wrap your action creators into their own service?
export function actionCreatorsService(bluetoothService) {
function addDevice(device) {
return { type: 'ADD_DEVICE', device }
}
function startDeviceScan() {
return function (dispatch) {
// The Service invokes the given callback for each found device
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
return {
addDevice,
startDeviceScan
};
}
Now, any clients of this service will need to provide an instance of the bluetoothService. In your actual src code:
const bluetoothService = require('./actual/bluetooth/service');
const actionCreators = require('./actionCreators')(bluetoothService);
And in your tests:
const mockBluetoothService = require('./mock/bluetooth/service');
const actionCreators = require('./actionCreators')(mockBluetoothService);
If you don't want to specify the bluetooth service every time you need to import the action creators, within the action creators module you can have a normal export (that uses the actual bluetooth service) and a mock export (that uses a mock service). Then the calling code might look like this:
const actionCreators = require('./actionCreators').actionCreators;
And your test code might look like this:
const actionCreators = require('./actionCreators').mockActionCreators;
I created a dependency-injecting middleware called redux-bubble-di for exactly that purpose. It can be used to inject an arbitrary number of dependencies into action creators.
You can install it by npm install --save redux-bubble-di or download it.
Your example using redux-bubble-di would look like this:
//import { DiContainer } from "bubble-di";
const { DiContainer } = require("bubble-di");
//import { createStore, applyMiddleware } from "redux";
const { createStore, applyMiddleware } = require("redux");
//import reduxBubbleDi from "redux-bubble-di";
const reduxBubbleDi = require("redux-bubble-di").default;
const bluetoothService = require('./blueToothService');
DiContainer.setContainer(new DiContainer());
DiContainer.getContainer().registerInstance("bluetoothService", bluetoothService);
const store = createStore(
state => state,
undefined,
applyMiddleware(reduxBubbleDi(DiContainer.getContainer())),
);
const startDeviceScan = {
bubble: (dispatch, bluetoothService) => {
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
},
dependencies: ["bluetoothService"],
};
// ...
store.dispatch(startDeviceScan);

Categories

Resources