What is the Typescript type for AbortController? - abortcontroller

I can't find any reference about how to define the type for AbortController. I am using it in a React component like so:
const abortController = useRef();
const doRequest = () => {
abortController.current = new AbortController();
window
.fetch(url, {
method: 'GET',
signal: abortController.current.signal,
})
.then(res => res.json());
};
const cancelRequest = () => {
abortController.current.abort();
};

Related

useEffect => .then returns undefined

I try to fetch the current offer price for my NFT project but i currently get undefined in this function
useEffect(() => {
returnsCurrentOfferPrice(NFT.tokenId)
.then((offer) => {
console.log(offer);
setReturnCurrentOfferPrice(offer);
})
.catch((error) => {
console.log('Current offer price error', error);
});
}, [NFT.tokenId]);
This is my use State Snippet
const [returnCurrentOfferPrice, setReturnCurrentOfferPrice] = useState(null);
This is how i retrieve the function into my UI
const returnsCurrentOfferPrice = async (tokenId) => {
await getCurrentOfferPrice(tokenId)
}
And finally this is how i retrieve the data from the blockchain
const getCurrentOfferPrice = async (tokenId) => {
const web3Modal = new Web3Modal();
const connection = await web3Modal.connect();
const provider = new ethers.providers.Web3Provider(connection);
const contract = signerOrProvider(provider);
const currentOfferPrice = await contract.getCurrentOfferAmount(tokenId);
const bigNumber = ethers.BigNumber.from(currentOfferPrice);
const currentOfferPriceInEther = ethers.utils.formatEther(bigNumber)
console.log('Current offer price', currentOfferPriceInEther );
return currentOfferPriceInEther;
}

Argument of type 'Promise<T>' is not assignable to parameter of type 'SetStateAction<T>'

I'm trying to make an api call with an async function. The function is in a useEffect hook so that every time the "input" is changed by the user, it searches for a new array.
When the api function is called, it returns Promise
I though I had resolved this Promise when using async await and .then(res) ?
Here is the code:
export type SearchResults = {
art_type: string
Title: string
Culture: string
Nation: string
Nationality: string
artist_type: string
Artist: string
source_type: string
Abbr: string
Source: string
}[]
const App: NextPage = () => {
const [input, setInput] = useState('Gogh')
const [results, setResults] = useState<SearchResults | undefined>(undefined)
// const [errors, setErrors] = useState<Error | null>(null)
useEffect(() => {
const fetchData = async (input: string) => {
const data: SearchResults = await fetch(`http://localhost:8080/search/${input}`).then(res => {
return res.json()
})
return data
}
const data = fetchData(input)
setResults(data)
}, [input])
const searchHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target !== null) setInput(e.target.value.toLowerCase())
}
const deboucedSearchHandler = useMemo(() => debounce(searchHandler, 500), [])
Solved it by moving my fetchData declaration outside useEffect. Then I made the anon func used by .then() asynchronous and has the res.json() assigned to a variable with the await keyword.
const [input, setInput] = useState('Gogh')
const [results, setResults] = useState<SearchResults | undefined>(undefined)
// const [errors, setErrors] = useState<Error | null>(null)
const fetchData = async (input: string) => {
await fetch(`http://localhost:8080/search/${input}`).then(async res => {
const response: SearchResults = await res.json()
setResults(response)
})
}
useEffect(() => {
fetchData(input)
}, [input])
const searchHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target !== null && e.target.value !== '') setInput(e.target.value.toLowerCase())
}
const deboucedSearchHandler = useMemo(() => debounce(searchHandler, 450), [])

How to getSelectors through passing arg in endpoint.select() in Redux RTK

I currently have this piece of code.
const initialState = documentsAdapter.getInitialState()
export const setupsApiSlice = apiSlice.injectEndpoints({
tagTypes: ['Setup'],
endpoints: builder => ({
getSetups: builder.query({
query: (documentId) => ({
url: `/documents/${documentId}/setups`,
method: 'GET'
}),
providesTags: ['Setup']
}),
})
})
export const {
useGetSetupsQuery,
useAddSetupsMutation,
useUpdateSetupsMutation,
useDeleteSetupsMutation
} = apiSlice
And now I want to make use of the of the getSelector and do something like this (not implemented).
export const selectSetupsResult = setupsApiSlice.endpoints.getSetups.select()
// Creates memoized selector
const selectSetupsData = createSelector(
selectSetupsResult,
setupsResult => setupsResult.data // normalized state object with ids & entities
)
export const {
selectAll: selectAllSetups,
selectById: selectSetupById,
selectIds: selectSetupIds,
} = setupsAdapter.getSelectors(state => selectSetupsData(state) ?? initialState)
The problem that I encounter is that endpoint.select() needs an argument in my case so that I can call setups on the correct documentId. I know I could just call all the setups and then filter out the ones that have the same documentId, but I was wondering if there is any other way. Even if it means not calling the endpoints.select() and still being able to use the getSelectors().
const initialState = documentsAdapter.getInitialState()
export const setupsApiSlice = apiSlice.injectEndpoints({
tagTypes: ['Setup'],
endpoints: builder => ({
getSetups: builder.query({
query: (documentId) => ({
url: `/documents/${documentId}/setups`,
method: 'GET'
}),
// Add transformResponse
transformResponse: (responseData) => {
return documentsAdapter.setAll(initialState, responseData)
},
providesTags: ['Setup']
}),
})
})
// Define function to get selectors based on arguments (query) of getSetups
export const getSelectors = (
query,
) => {
const selectSetupsResult = setupsApiSlice.endpoints.getSetups.select(query)
const adapterSelectors = createSelector(
selectSetupsResult,
(result) => documentsAdapter.getSelectors(() => result?.data ?? initialState)
)
return {
selectAll: createSelector(adapterSelectors, (s) =>
s.selectAll(undefined)
),
selectEntities: createSelector(adapterSelectors, (s) =>
s.selectEntities(undefined)
),
selectIds: createSelector(adapterSelectors, (s) =>
s.selectIds(undefined)
),
selectTotal: createSelector(adapterSelectors, (s) =>
s.selectTotal(undefined)
),
selectById: (id) => createSelector(adapterSelectors, (s) =>
s.selectById(s, id)
),
}
}
Then you can use it in a component as:
const { isFetching } = useGetSetupsQuery({id: 1})
// Dinamically get selectors based on parent query
const { selectAll: selectAllFromId1, selectById: selectByIdFromId1 } = getSelectors({id: 1})
// Use selectors based on parent id 1
const allFromId1 = useSelector(selectAllFromId1)
const setup1fromId1 = useSelector(selectByIdFromId1(5)) // get id 5

How to mock useRouter parameters for react-hooks-testing-library?

I have a custom hook, which has structure of:
const urlHook = () => {
const router = useRouter();
const read = () => {
return validate(router.query.param);
}
const write = (params) => {
router.push(
{
query: {
param: params,
},
},
undefined,
{shallow: true},
)
}
const validate = (params) => {}
}
I want to test this hook using react-hooks-testing-library but I'm not sure how to setup for router.query.param to read values that I want or how to check if function write() will create correct url?
To mock entire hook - jest.requireActual:
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useParams: () => ({
blogId: 'company1',
articleId: 'blog1',
}),
useRouteMatch: () => ({ url: '/blog/blog1/article/article1' }),
}));
To mock history/routing state - MemoryRouter:
import {Route, MemoryRouter} from 'react-router-dom';
...
const renderWithRouter = ({children}) => (
render(
<MemoryRouter initialEntries={['blogs/1']}>
<Route path='blogs/:blogId'>
{children}
</Route>
</MemoryRouter>
)
)
Helpful example with explanations:
https://v5.reactrouter.com/web/guides/testing

How can I test Observable.ajax (redux-observable)?

I have been playing with rxjs and redux-observable for the last few days and have been struggle to find a way to a test for Observable.ajax. I have the following epic which create a request to https://jsonplaceholder.typicode.com/,
export function testApiEpic (action$) {
return action$.ofType(REQUEST)
.switchMap(action =>
Observable.ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
)
}
where,
export const REQUEST = 'my-app/testApi/REQUEST'
export const SUCCESS = 'my-app/testApi/SUCCESS'
export const FAILURE = 'my-app/testApi/FAILURE'
export const CLEAR = 'my-app/testApi/CLEAR'
export function requestTestApi () {
return { type: REQUEST }
}
export function successTestApi (response) {
return { type: SUCCESS, response }
}
export function failureTestApi (error) {
return { type: FAILURE, error }
}
export function clearTestApi () {
return { type: CLEAR }
}
The code works fine when runs in browser but not when testing with Jest.
I have try,
1) Create a test based on https://redux-observable.js.org/docs/recipes/WritingTests.html. The store.getActions() returns only { type: REQUEST }.
const epicMiddleware = createEpicMiddleware(testApiEpic)
const mockStore = configureMockStore([epicMiddleware])
describe.only('fetchUserEpic', () => {
let store
beforeEach(() => {
store = mockStore()
})
afterEach(() => {
epicMiddleware.replaceEpic(testApiEpic)
})
it('returns a response, () => {
store.dispatch({ type: REQUEST })
expect(store.getActions()).toEqual([
{ type: REQUEST },
{ type: SUCCESS, response }
])
})
})
2) Create a test based on Redux-observable: failed jest test for epic. It returns with
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('returns a response', (done) => {
const action$ = ActionsObservable.of({ type: REQUEST })
const store = { getState: () => {} }
testApiEpic(action$, store)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
{ type: SUCCESS, response }
])
done()
})
})
Can someone point me out what is the correct way to test Observable.ajax ?
I would follow the second example, from StackOverflow. To make it work you'll need to make some minor adjustments. Instead of importing Observable.ajax in your epic file and using that reference directly, you need to use some form of dependency injection. One way is to provide it to the middleware when you create it.
import { ajax } from 'rxjs/observable/dom/ajax';
const epicMiddleware = createEpicMiddleware(rootEpic, {
dependencies: { ajax }
});
The object we passed as dependencies will be give to all epics as the third argument
export function testApiEpic (action$, store, { ajax }) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Alternatively, you could not use the dependencies option of the middleware and instead just use default parameters:
export function testApiEpic (action$, store, ajax = Observable.ajax) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Either one you choose, when we test the epic we can now call it directly and provide our own mock for it. Here are examples for success/error/cancel paths These are untested and might have issues, but should give you the general idea
it('handles success path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
successTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('handles error path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.throw({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
failureTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('supports cancellation', (done) => {
const action$ = ActionsObservable.of(requestTestApi(), clearTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method }).delay(100)
};
const onNext = chai.spy();
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe({
next: onNext,
complete: () => {
onNext.should.not.have.been.called();
done();
}
});
});
For the first way:
First, use isomorphic-fetch instead of Observable.ajax for nock support, like this
const fetchSomeData = (api: string, params: FetchDataParams) => {
const request = fetch(`${api}?${stringify(params)}`)
.then(res => res.json());
return Observable.from(request);
};
So my epic is:
const fetchDataEpic: Epic<GateAction, ImGateState> = action$ =>
action$
.ofType(FETCH_MODEL)
.mergeMap((action: FetchModel) =>
fetchDynamicData(action.url, action.params)
.map((payload: FetchedData) => fetchModelSucc(payload.data))
.catch(error => Observable.of(
fetchModelFail(error)
)));
Then, you may need an interval to decide when to finish the test.
describe("epics", () => {
let store: MockStore<{}>;
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
nock.cleanAll();
epicMiddleware.replaceEpic(epic);
});
it("fetch data model succ", () => {
const payload = {
code: 0,
data: someData,
header: {},
msg: "ok"
};
const params = {
data1: 100,
data2: "4"
};
const mock = nock("https://test.com")
.get("/test")
.query(params)
.reply(200, payload);
const go = new Promise((resolve) => {
store.dispatch({
type: FETCH_MODEL,
url: "https://test.com/test",
params
});
let interval: number;
interval = window.setInterval(() => {
if (mock.isDone()) {
clearInterval(interval);
resolve(store.getActions());
}
}, 20);
});
return expect(go).resolves.toEqual([
{
type: FETCH_MODEL,
url: "https://test.com/assignment",
params
},
{
type: FETCH_MODEL_SUCC,
data: somData
}
]);
});
});
enjoy it :)

Resources