I'm developing a react native mobile app using UI Kitten. I'm still fairly new to both react native and UI kitten, so I am using the just the plain Javascript template VS the Typescript template.
I have a functional component screen as shown below. This screen is working just fine. Today I started REDUX implementation.
const RequestScreen = ({ navigation }) => {
// code removed for brevity
}
Within this screen I use the useEffect hook to fetch data from my API
useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
getServices();
});
return () => {
unsubscribe;
};
}, [navigation]);
const getServices = async () => {
setLoading(true);
// helper function to call API
await getAllServices().then((response) => {
if (response !== undefined) {
const services = response.service;
setServices(services);
setLoading(false);
}
});
// update redux state
props.getAllServices(services);
};
// code removed for brevity
const mapStateToProps = (state) => state;
const mapDispatchToProps = (dispatch) => ({
getServices: (services) =>
dispatch({
type: Types.GET_SERVICES,
payload: { services },
}),
});
const connectComponent = connect(mapStateToProps, mapDispatchToProps);
export default connectComponent(RequestScreen);
On this line of code:
props.getAllServices(services);
I keep getting this error:
[Unhandled promise rejection: TypeError: undefined is not an object
(evaluating 'props.getAllServices')] at
node_modules\regenerator-runtime\runtime.js:63:36 in tryCatch at
node_modules\regenerator-runtime\runtime.js:293:29 in invoke
Anytime I try to use "props" in code here. I run into errors. How do I get the props on this screen?
I tried changing the screen, as shown below, but that does not work either!
const RequestScreen = ({ navigation, props }) => {
// code removed
}
I was able to get the props object after changing the screen component as shown below.
const RequestScreen = ({ navigation, ...props }) => {}
Related
I'm facing a problem unit-testing a component with react-native-testing-library.
I have a component like this:
// components/TestComponent.js
function TestComponent() {
const [data, setData] = useState();
useEffect(() => {
clientLibrary.getData()
.then((result) => { setData(result.data); } )
.catch((err) => { //handle error here } )
}, []);
render (
<ListComponent
testID={"comp"}
data={data})
renderItem={(item) => <ListItem testID={'item'} data={item} />}
/>
);
}
And I test it like this:
// components/TestComponent.test.js
it('should render 10 list item', async () => {
const data = new Array(10).fill({}).map((v, idx) => ({
id: `v_${idx}`,
}));
const req = jest.spyOn(clientLibrary, 'getData').mockImplementation(() => {
return Promise.resolve(data);
});
const {queryByTestId, queryAllByTestId} = render(
<TestComponent />,
);
expect(await queryByTestId('comp')).toBeTruthy(); // this will pass
expect(await queryAllByTestId('item').length).toEqual(10); // this will fail with result: 0 expected: 10
}); // this failed
The test will fail/pass with
Attempted to log "Warning: An update to TestComponent inside a test was not wrapped in act(...). pointing to setData in useEffect.
I've tried wrapping the render with act(), the assertion with act(), not mocking the api call, wrapping the whole test in act(), but the error won't go away.
I have tried looking at testing-library docs/git/q&a for this case, scoured stackoverflow questions too, but I still can't make this test works.
Can anyone point me to the right direction to solve this?
A note: I'm not trying to test implementation detail. I just want to test that given a fetch result X, the component would render as expected, which is rendering 10 list item.
Your component is performing an asynchronous state update during mounting inside useEffect so the act of rendering has an asynchronous side effect that needs to be wrapped in an await act(async()) call. See the testing recipes documentation on data fetching.
You can try something like this in your test:
it('should render 10 list item', async () => {
// Get these from `screen` now instead of `render`
const { queryByTestId, queryAllByTestId } = screen
const data = new Array(10).fill({}).map((v, idx) => ({
id: `v_${idx}`,
}));
const req = jest.spyOn(clientLibrary, 'getData').mockImplementation(() => {
return Promise.resolve(data);
});
await act(async () => {
render(
<TestComponent />
);
})
expect(await queryByTestId('comp')).toBeTruthy();
expect(await queryAllByTestId('item').length).toEqual(10);
});
I want to AsyncStorage save data in variable without onPress , show data on open app screen
code:
const AppScreen = () => {
var getusername = async () => {
await AsyncStorage.getItem('name');
};
return (
<Text>{getusername}</Text>
);
};
export default AppScreen;
You have to use react hooks, in particular, useEffect hook to call the function as the app launches
const appScreen = () => { var getusername = async () => { await AsyncStorage.getItem('token'); };};
useEffect(() => { appScreen ();}, []);
I would like to write tests for my React-native app. My parent component will execute the methods within the child component.
My child component is using the Hooks forwardRef, useImperativeHandle, Ref as seen below
childs.tsx
export interface RefChild {
toggle: () => void,
close: () => void
}
const Child = forwardRef((props: ChildProps, ref: Ref<RefChild>) => {
const [isVisible, setIsVisible] = useState(false);
useImperativeHandle(ref, () => ({ toggle, close }));
const toggle = () => {
setIsVisible(!isVisible);
}
const close = () => {
setIsVisible(false)
}
return (...mycomponent)
}
My Parent component is catching the 'ref' call with
ref={(el: RefChild) => childRef.current = el}
Which allows me to call the 'toggle' and 'close' methods from within the Parent.
Now, I fail to understand how to do the same thing within my test
my parent-test.tsx:
describe('Parent', () => {
let wrapper: ShallowWrapper;
let props: any;
beforeEach(() => {
props = createTestProps({});
wrapper = shallow(<Parent {...props} />);
});
//this is what I am currently trying to do, but not working
//test 1 (not working)
it("useRef child", () => {
const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: <Child/> });
expect(useRefSpy).toBeCalled();
useRefSpy.current.toggle();
})
//test 2 (not working)
it("useRef child2", () => {
const ref = {
current: {
toggle: jest.fn(),
close: jest.fn()
}
}
ref.current.toggle();
})
//test 3 (not working)
it("useRef child3", () => {
wrapper.instance().childref.current.toggle(); //failing as functional components don't have instance
})
})
My versions of React and RN are:
"react": "16.13.1",
"react-native": "0.63.3"
Could anyone explain me how should I achieve this?
As you mentioned in your question there is no instance in functional component, I think there is a better way to handle toggle and close functions from parent component using a boolean prop for each of them and the listen to changes in this value like this:
you have a state in parent component called isClose set to false and then in child component you use something like this:
useEffect(() => {
if(isClose){
//call close function
close()
}
}, [isClose])
But by the way in your current setup I think you need to mock the useRef hook something like this:
const useRefSpy = jest
.spyOn(React, "useRef")
.mockReturnValueOnce(() => ({ current: <Child /> }));
I am new to the world of unit testing and I have just began to write tests for my React Native (Expo) app. After doing research I have finally landed in using Jest and React Native Testing Library.
Consider the following that uses the AppLoading component.
const App: React.FC = () => {
const [resourcesHasLoaded, setResourcesHasLoaded] = useState<boolean>(false);
const cacheResources = useCallback(async (): Promise<any> => {
const images = [require('./assets/icon.png')];
const cacheImages = images.map((image) => {
return Asset.fromModule(image).downloadAsync();
});
return Promise.all([cacheImages]);
}, []);
if (resourcesHasLoaded) {
return <Text>Hello world</Text>;
}
return (
<AppLoading
startAsync={cacheResources}
onError={console.warn}
onFinish={() => setResourcesHasLoaded(true)}
/>
);
};
When running my test, that looks like this:
describe('App.tsx', () => {
it('should be able to render', async () => {
render(<App />);
});
});
I end up with the following error (although, test passes):
Warning: An update to App inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
So, I wrapped my `render` in with `act` the following way:
act(() => {
render(<App />);
});
... which resulted in the same error.
If I however wrap the onFinish-callback in my component the following way the test passes without warnings.
onFinish={() => act(() => setResourcesHasLoaded(true))}
But do I really want to pollute my React Component with test-specific functions? I saw no example of this, so I can only assume that this is bad practice.
Any suggestions here?
Update
I got the suggestion to use waitFor after my render by #Estus Flask in my comments. That did the trick... the test now passes.
https://callstack.github.io/react-native-testing-library/docs/api/#waitfor
describe('App.tsx', () => {
it('should be able to render', async () => {
const { findByText } = render(<MyApp />);
await waitFor(() => findByText('Hello world'));
});
});
I recently updated react-navigation to version 2.18.0 and a section of my code which used to work no longer does. After combing through the documentation, I'm still having trouble reproducing the functionality I had before.
Essentially I wanted all the data that the stats screen needed to be loaded before jumpToIndex is called, so that the StatsScreen Component had access to updated data before render().
This functionality used to work, but now I'm getting an "Unhandled Promise Rejection: TypeError: jumpToIndex is not a function." warning. and jumpToIndex never happened.
In App.js I changed TabNavigator to createBottomTabNavigator, and made the necessary changes for the update.
const RootNavigator = createBottomTabNavigator({
Home: {
screen: HomeStackNavigator,
navigationOptions: ({ navigation }) => ({
//Navigation options here
),
}),
},
StatsScreen: {
screen: StatsScreen,
},
}, {
lazy: false,
});
In StatsScreen.js:
export default class StatsScreen extends Component {
static navigationOptions = ({ navigation }) => ({
tabBarOnPress: async (tab, jumpToIndex) => {
if (!tab.focused) {
await navigation.state.params.update();
jumpToIndex(tab.index);
}
},
});
async componentDidMount() {
this.props.navigation.setParams({
update: this._updateStats.bind(this),
});
}
async _updateStats() {
//logic in this function calls updateData() if needed.
}
async _updateData() {
//Update the data
}
render() {
return (
// Component JSX ommitted from this example
);
}
}
Any ideas on what needs to be done?
I found the solution on Andrei Pfeiffer's blog: https://itnext.io/handle-tab-changes-in-react-navigation-v2-faeadc2f2ffe
Essentially I changed the navigation options to the following code:
static navigationOptions = () => ({
async tabBarOnPress({ navigation, defaultHandler }) {
await navigation.state.params.onTabFocus();
defaultHandler();
},
});
onTabFocus() now does the same work that updateStats() used to do.