I'm trying to implement Bullet train API in a React web app. According to their node client documentation, I have setup the following function:
export const isFeatureEnabled = async (nameOfTheFeature) => {
return new Promise((resolve) => {
bulletTrain.init({
environmentID: BULLET_TRAIN_ENV_ID
});
bulletTrain.hasFeature(nameOfTheFeature)
.then((featureFlag) => {
if (featureFlag[nameOfTheFeature].enabled) {
resolve(true);
}
})
.catch(err => resolve(false));
});
}
This is called in regular components like this:
render() {
return (<div>{await isFeatureEnabled('feature1') && <p>feature1 is enabled</p>}</div>)
};
which throws this:
Parsing error: Can not use keyword 'await' outside an async function
If we add the async keyword, with a proper return statement:
async render() {
return (<div>{await isFeatureEnabled('feature1') && <p>feature1 is enabled</p>}</div>)
};
Then it throws:
Your render method should have return statement
So what is the correct way to use this promised function inside a react app?
I would suggest you not to use await keyword in render instead use componentDidMount and constructor for this and use state object to check:
constructor(props){
super(props);
this.state = { isFeatEnabled: false };
}
componentDidMount(){
this.setState({isFeatEnabled:isFeatureEnabled('feature1')})
}
Now in the render:
render() {
return (<div>{this.state.isFeatEnabled && <p>feature1 is enabled</p>}</div>)
};
And remove the async from the method.
call function isFeatureEnabled inside an async function during mount (before/after your wish)
example -
export const isFeatureEnabled = async (nameOfTheFeature) => {
return new Promise((resolve) => {
bulletTrain.init({
environmentID: BULLET_TRAIN_ENV_ID
});
bulletTrain.hasFeature(nameOfTheFeature)
.then((featureFlag) => {
if (featureFlag[nameOfTheFeature].enabled) {
resolve(true);
}
})
.catch(err => resolve(false));
});
}
...
componentDidMount() {
this.checEnabled();
}
...
const checkEnabled = async () => {
const flag = await isFeatureEnabled('feature1');
this.setState({f1enabled: flag});
}
...
render() {
return (<div>{this.state.f1enabled ? <p>feature1 is enabled</p> : null}</div>)
}
If isFeatureEnabled is in the same file keep it outside class component or else keep it in another file and export the function.
You can't use promise at there, the proper way:
import React, { useEffect, useState } from 'react'
import bulletTrain from '../somewhere'
import BULLET_TRAIN_ENV_ID from '../somewhere'
export default function featureComponent({ featureName }) {
const [featureEnabled, setFeatureEnabled] = useState(false)
useEffect(() => {
bulletTrain.init({
environmentID: BULLET_TRAIN_ENV_ID
})
bulletTrain
.hasFeature(featureName)
.then(featureFlag => {
if (featureFlag[featureName].enabled) {
setFeatureEnabled(true)
}
})
.catch(err => setFeatureEnabled(false))
}, [featureName])
return <div>{featureEnabled && <p>{featureName} is enabled</p>}</div>
}
Append isFeatureEnabled function re-use answer below:
import React, { useEffect, useState } from 'react'
import isFeatureEnabled from '../somewhere'
export default function featureComponent({ featureName }) {
const [featureEnabled, setFeatureEnabled] = useState(false)
useEffect(() => {
const checkAndSetEnabled = async () => {
const enabled = await isFeatureEnabled(featureName)
setFeatureEnabled(enabled)
}
checkAndSetEnabled()
}, [featureName])
return <div>{featureEnabled && <p>{featureName} is enabled</p>}</div>
}
Related
I am trying to write a test for the following Nuxt 3 composable (useLinkToSlug).
import { computed } from 'vue';
export default function () {
const route = useRoute();
return computed(() => route?.params?.slug ? `/${route.params.slug}` : undefined);
}
To keep the code as lean as possible, I tried to mock the vue-router module and set the return of useRoute() manually.
My test looks like this:
import { vi, it, expect, describe } from 'vitest';
import useLinkToSlug from '~~/composables/useLinkToSlug';
describe('useLinkToSlug', () => {
it('should return link to slug', () => {
vi.mock('vue-router', () => ({
useRoute: () => ({ params: { slug: 'abc' } })
}));
const link = useLinkToSlug();
expect(link.value).toEqual('/abc');
});
it('should return null', () => {
vi.mock('vue-router', () => ({
useRoute: () => ({ params: { slug: undefined } })
}));
const link = useLinkToSlug();
expect(link.value).toBeNull();
});
});
The first one succeeds, but the later one fails, with:
AssertionError: expected '/abc' to be null
I don't get why and what to do, to make this work.
Using: Nuxt3 with Vitest
Got it now. Was so close. Solution:
Add import statement for useRoute to composable.
import { computed } from 'vue';
import { useRoute } from 'vue-router'; // added this import statement
export default function () {
const route = useRoute();
return computed(() => route?.params?.slug ? `/${route.params.slug}` : undefined);
}
Mock vue-router:
import { vi, it, expect, describe } from 'vitest';
import useLinkToSlug from '~~/composables/useLinkToSlug';
vi.mock('vue-router'); // mock the import
describe('useLinkToSlug', () => {
it('should return link to slug', () => {
const VueRouter = await import('vue-router');
VueRouter.useRoute.mockReturnValueOnce({
params: { slug: 'abc' }
});
const link = useLinkToSlug();
expect(link.value).toEqual('/abc');
});
it('should return null', () => {
const VueRouter = await import('vue-router');
VueRouter.useRoute.mockReturnValueOnce({
params: { slug: undefined }
});
const link = useLinkToSlug();
expect(link.value).toBeNull();
});
});
I am trying to set an array of objects into dineIns after fetching them inside useEffect. What I understood is that there is some kind of delay in receiving the data because the state variable returns an empty array when I log it after fetching the data.
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router';
import jwtDecode from 'jwt-decode';
function CheckIns() {
const [dineIns, setDineIns] = useState([]);
const navigate = useNavigate();
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
const user = jwtDecode(token);
if (!user) {
localStorage.removeItem('token');
navigate('/login');
} else {
async function UserData(user_email) {
const user_data = await axios
.get(`/api/users/${user_email}`)
.then((res) => {
const info = res.data.reservations;
setDineIns(info);
console.log(dineIns);
});
}
UserData(user.email);
}
} else {
navigate('/login');
}
}, []);
}
What needs to be corrected here to set the state in time?
set state is an async operation, which log the data after set it, will log the old value.
To ensure that the data set correctly, you can use setState again
const info = res.data.reservations
setDineIns(info)
setDineIns(prev => {
console.log(prev)
return prev;
})
Or you can use effect with dineIns dependence.
I think your code works fine.
You are expecting a Promise from the axios call but you are awaiting it.
Try to change your code like this:
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
const user = jwtDecode(token);
if (!user) {
localStorage.removeItem('token');
navigate('/login');
} else {
async function UserData(user_email) {
try {
const { data } = await axios.get(`/api/users/${user_email}`);
setDineIns(data.reservations);
console.log(dineIns);
} catch (err) {
console.log(err);
}
}
UserData(user.email);
}
} else {
navigate('/login');
}
}, []);
Hello I have custom hook
code is like below
import * as React from 'react'
export const myCustomHook = (props?: boolean) => {
const [value, setValue] = React.useState([]);
React.useEffect(() => {
return (async (p1) => {
// ....
setValue(someValues)
})
}, [])
const myFun = async (prop1) => {
// ... some operations
return {id: id}
}
return { myFun, value }
}
I am using the above like this
const { value, myFun } = myCustomHook();
const foofun = async (pp) => {
const myfunRes = await myFun(prop1);
}
Now I want to put myFun in useEffect
Please help me with this.
So I have written a custom polling hook which uses useContext and useLazyQuery hooks. I want to write a unit test for this, which should cover its returned values state and side effect.
So far I have managed to do this much but I'm not so sure how to proceed ahead. Any tips?
export const useUploadActivityPolling = (
teId: TeIdType
): UploadActivityPollingResult => {
const { dispatch, uploadActivityId }: StoreContextType = useAppContext();
const [fetchActivityStatus, { error: UploadActivityError, data: UploadActivityData, stopPolling }] = useLazyQuery(
GET_UPLOAD_ACTIVITY,
{
pollInterval: 3000,
fetchPolicy: 'network-only',
variables: { teId, activityId: uploadActivityId },
}
);
useEffect(() => {
if (UploadActivityData) {
setUploadActivityId(
UploadActivityData.getUploadActivityStatus.activity_id,
dispatch
);
updateActivityStateAction(UploadActivityData.getExcelUploadActivityStatus.status, dispatch);
}
}, [UploadActivityData]);
return { fetchActivityStatus, stopPolling, UploadActivityError };
};
import React from 'react';
import { mount } from 'enzyme';
const TestCustomHook = ({ callback }) => {
callback();
return null;
};
export const testCustomHook = callback => {
mount(<TestCustomHook callback={callback} />);
};
describe('useUploadActivityPolling', () => {
let pollingResult;
const teId = 'some id';
beforeEach(() => {
testCustomHook(() => {
pollingResult = useUploadActivityPolling(teId);
});
});
test('should have an fetchActivityStatus function', () => {
expect(pollingResult.fetchActivityStatus).toBeInstanceOf(Function);
});
});
I am trying to test a simple hook that fetches some data using axios. However the test is throwing a TypeError: "Cannot read property 'fetchCompanies' of undefined". Here's my custom hook (the full repo is here):
import { useState, useEffect } from 'react';
import { Company } from '../../models';
import { CompanyService } from '../../services';
export const useCompanyList = (): {
loading: boolean;
error: any;
companies: Array<Company>;
} => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
const [companies, setCompanies] = useState<Array<Company>>([]);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const companies = await CompanyService.fetchCompanies();
// Sort by ticker
companies.sort((a, b) => {
if (a.ticker < b.ticker) return -1;
if (a.ticker > b.ticker) return 1;
return 0;
});
setCompanies(companies);
setLoading(false);
} catch (e) {
setError(e);
}
};
fetchData();
}, []);
return { loading, error, companies };
};
and here's my test:
import { renderHook } from 'react-hooks-testing-library';
import { useCompanyList } from './useCompanyList';
const companiesSorted = [
{
ticker: 'AAPL',
name: 'Apple Inc.'
},
...
];
jest.mock('../../services/CompanyService', () => {
const companiesUnsorted = [
{
ticker: 'MSFT',
name: 'Microsoft Corporation'
},
...
];
return {
fetchCompanies: () => companiesUnsorted
};
});
describe('useCompanyList', () => {
it('returns a sorted list of companies', () => {
const { result } = renderHook(() => useCompanyList());
expect(result.current.loading).toBe(true);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual(companiesSorted);
});
});
Please help me understand how to use react-hooks-testing-library in this case.
Edit
This seems to be related to a Jest issue that was seemingly resolved. Please see https://github.com/facebook/jest/pull/3209.
The
TypeError: "Cannot read property 'fetchCompanies' of undefined"
is caused by the way you define the CompanyService service. In the code, you are exporting an object CompanyService with all the service methods. But in your test, you are mocking the CompanyService to return an object with the methods.
So, the mock should return a CompanyService object that is an object with all the methods:
jest.mock('../../services/CompanyService', () => {
const companiesUnsorted = [
{
ticker: 'MSFT',
name: 'Microsoft Corporation'
},
...
];
return {
CompanyService: {
fetchCompanies: () => companiesUnsorted
}
};
});
Now, once you solve this, you will find that you don't have the TypeError anymore but your test is not passing. That is because the code you are trying to test is asynchronous, but your test is not. So, immediately after you render your hook (through renderHook) result.current.companies will be an empty array.
You will have to wait for your promise to resolve. Fortunately, react-hooks-testing-library provides us a waitForNextUpdate function in order to wait for the next hook update. So, the final code for the test would look:
it('returns a sorted list of companies', async () => {
const { result, waitForNextUpdate } = renderHook(() => useCompanyList());
expect(result.current.loading).toBe(true);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual([]);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual(companiesSorted);
});