Integrating Pull-To-Refresh in a ScrollView with Redux in React Native - react-native

I am trying to add pull-to-refresh functionality to a <ScrollView> using a refreshControl and integrate it with Redux.
Example from https://facebook.github.io/react-native/docs/refreshcontrol:
_onRefresh = () => {
this.setState({refreshing: true});
fetchData().then(() => {
this.setState({refreshing: false});
});
}
My problem is that my own fetchData function dispatches an action for the reducer to handle, so as far as I understand it, it is not a thenable promise. So I don't fully understand the integration with Redux in this case. What do I need to change in my code to be able to set refreshing to false as in the above example?
PostFeedScreen.js
// on mount, fetch all posts from the API
componentDidMount() {
this.props.fetchPostsFromAPI();
}
_onRefresh = () => {
this.setState( { refreshing: true } );
this.props.fetchPostsFromAPI().then( () => { // error
this.setState( { refreshing: false } );
});
}
// map dispatch to props
const mapDispatchToProps = ( dispatch ) => {
return {
fetchPostsFromAPI: () => {
dispatch( fetchPostsFromAPI() );
}
}
}
PostActions.js
// fetch all posts
export function fetchPostsFromAPI() {
return( dispatch ) => {
let loadData = new Promise( ( resolve, reject ) => {
resolve( postsInitial ); // using dummy data for now
})
loadData
.then( posts => dispatch( fetchPostsSuccess( posts ) ) );
}
// is used if posts were succesfully loaded
function fetchPostsSuccess( posts ) {
return {
type: PostConstants.FETCH_POSTS_SUCCESS,
data: posts,
}
}
PostReducer.js
const PostReducer = ( state = initialState, action ) => {
switch( action.type ) {
// if fetching data was successful
case PostConstants.FETCH_POSTS_SUCCESS: {
return {
...state,
posts: action.data,
}
}
default: {
return state
}
}

You get an error cause you call .then on something who don't return a promises. Just add return in front of your loadData, cause you can chain promises.
export function fetchPostsFromAPI() {
return dispatch => {
let loadData = new Promise((resolve, reject) => {
resolve(postsInitial);
});
return loadData.then(posts => dispatch(fetchPostsSuccess(posts)))
};
}

Related

Unit tests, check if function have been called

I need to implement a test that checks if the function has been called on the button click
onSave (e) {
this.$qiwaApi.createDimension()
.then(() => {})
.catch(err => this.showSnackbar(err.message))}
I need to test the function createDimension. In my test i mocked it
const createComponent = () => {
wrapper = mount(dimensions, {
store,
localVue,
mocks: {
$qiwaApi: {
createDimension: function (e) {
return new Promise((resolve) => { resolve({}) })
}
}
},
router
})
}
In the project, the function exported this way
export default $axios => ({
createDimension (data, surveyId) {
return $axios.post(`/lmi-admin/surveys/${surveyId}/dimension`, {
data: {
attributes: {
...data
}
}
})
}
})
I expect this test to work. But for some reason wrapper.qiwaApi or wrapper.createDimension return undefined
expect(wrapper.$qiwaApi.createDimension).toHaveBeenCalled()
The wrapper doesn't provide access to your mocks that way.
You would have to hold a reference to a jest.fn(), and then verify the calls on that reference directly instead of trying to pull it out of the wrapper:
it('calls createDimension on button click', async () => {
const createDimension = jest.fn(() => Promise.resolve())
const wrapper = mount(dimensions, {
mocks: {
$qiwaApi: {
createDimension
}
}
})
await wrapper.find('[data-testid="save"]').trigger('click')
expect(createDimension).toHaveBeenCalled()
})
demo

How can I test my custom react hook that uses fetch?

I have created a custom react hook which uses fetch from whatwg-fetch. I have tests for the components that make use of the hook and can mock the whole hook, but now am trying to write tests for the hook itself and my goal is to mock the fetch response. This is my hook.
import { useState, useEffect } from "react";
import "whatwg-fetch";
export default function useFetch(url) {
const [data, setData] = useState(undefined);
const [response, setResponse] = useState(undefined)
const [isLoading, setLoading] = useState(true);
const [error, setError] = useState(undefined);
useEffect(() => {
try {
const fetchData = async () => {
const result = await fetch(url);
setResponse(result);
const responseText = await result.text();
setData(responseText);
setLoading(false);
};
fetchData();
} catch (error) {
setError(error);
}
}, [url]);
return { data, response, isLoading, error };
}
export { useFetch }
Currently, this is how my test looks like. Feels like I cannot mock the fetch to return the desired value.
I have tried writing tests by looking at several tutorials with no luck. I have tried the following tutorials:
Test Custom Hooks Using React Hooks Testing Library
Testing custom react hooks with jest
A Complete Guide to Testing React Hooks
UPDATE: Changed tests, my first test passes (resolve) my second one does not. Based on the third tutorial.
NEW TESTS
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import useFetch from "./useFetch";
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
let container = null;
describe("useFetch tests", () => {
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("useFetch returns data on success", async () => {
function fetchMock(url) {
return new Promise((resolve) => setTimeout(() => {
resolve({
status: 200,
ok: true,
text: () => Promise.resolve({
data: "data from api"
})
});
}, 300));
}
jest.spyOn(global, "fetch").mockImplementation(fetchMock);
act(() => {
render(<TestComponent url="url1" />, container);
});
expect(container.textContent).toBe("loading");
await sleep(500);
expect(container.textContent).toBe("data from api");
});
it("useFetch return error on fail", async () => {
// const a = 200 + Math.random() * 300;
// console.log(a)
// let promise = new Promise(function (resolve, reject) {
// // after 1 second signal that the job is finished with an error
// setTimeout(() => reject("error"), a);
// });
// function fetchMock(url) {
// return promise;
// }
function fetchMock(url) {
return new Promise((resolve) => setTimeout(() => {
resolve({
status: 404,
ok: true,
text: () => Promise.resolve({
data: "data from api"
})
});
}, 200 + Math.random() * 300));
}
jest.spyOn(global, "fetch").mockImplementation(fetchMock);
act(() => {
render(<TestComponent url="url1" />, container);
});
expect(container.textContent).toBe("loading");
await sleep(500);
expect(container.textContent).toBe("error");
});
});
function TestComponent({ url }) {
const {
data, response, isLoading, error
} = useFetch(url);
if (isLoading) {
return <div>loading</div>;
}
if (data) {
return <div>{data.data}</div>
}
if (error) {
return <div>error</div>
}
return <div></div>;
}
OLD TESTS
import { useFetch } from "../../../src/utils/custom-hooks/useFetch";
describe("useFetch tests", () => {
beforeEach(() => {
jest.spyOn(window, "fetch");
});
it("200", () => {
window.fetch.mockResolvedValueOnce({
ok: true,
status: 200,
})
const { result, rerender } = renderHook(
(url) => useFetch(url),
{
url: "url1"
}
);
expect(result.current.response).toBe(undefined);
rerender("url2");
expect(result.current.status).toBe(200);
});
});

Calling componentWillMount every time focused page in react native

I want call the componentWillMount every time that I focused a page. I using react-redux and react-navigation.
With react-navigation I use import { withNavigationFocus } from 'react-navigation'; to detect if the page was active but when I call componentDidMount there ara a few seconds that I see old view. Is for this I want calling componentWillMount instead of componentDidMount when page focused.
This is my code:
class HomeScreen extends React.Component {
componentWillMount() {
this.props._loading(true);
}
omponentDidMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener('didFocus', () => {
this.setState({loading: 0});
this.props._loading(true);
Api.get('s?type=Featured')
.then( response => {
if (response.profiles){
this.setState({featured_users: response.profiles, loading: this.state.loading + 1});
}
}).catch((error) => {
console.log(error);
this.props._loading(false);
});
Api.get('s?type=Top')
.then( response => {
if (response.profiles){
this.setState({featured_top: response.profiles, loading: this.state.loading + 1});
}
}).catch((error) => {
console.log(error);
this.props._loading(false);
});
});
}
componentWillUnmount() {
// Remove the event listener
this.focusListener.remove();
}
render() {
if (this.state.loading >= 4){
this.props._loading(false);
}
return (
...
);
}
}
const mapStateToProps = (state) => ({
user: state.reducerUser,
loading: state.reducerGeneral
});
mapDispatchToProps = dispatch => {
return {
_loading: loading => {
dispatch(actionLoading(loading));
},
updateUser: user => {
dispatch(actionUser(user));
},
}
}
export default withNavigationFocus(connect(mapStateToProps, mapDispatchToProps)(HomeScreen));
You can add this inside your componentWillMount and whatever you write inside your addListener , it will be executed everytime:
this.focusListener = this.props.navigation.addListener('didFocus', () => {
// The screen is focused
this.getData();
});

map is not a function in react-native

I want to get some data from api and display data in my app. This is my code,
class AlbumList extends Component {
state = { albums: [] };
async componentWillMount() {
try {
const data = await axios.get(
'https://rallycoding.herokuapp.com/api/music_albums'
);
this.setState({ albums: data });
} catch (err) {
console.error(err.message);
}
}
renderAlbums() {
return this.state.albums.map(album => <Text>{album.title}</Text>);
}
render() {
return (
<View>
{this.renderAlbums()}
</View>
);
}
}
this will give a error this.state.albums.map is not a function..
any way to solve this?
The error "map it not a function" occurs because axios don't return an array.
Axios returns an object with keys like status, data.
const data = await axios.get(
'https://rallycoding.herokuapp.com/api/music_albums'
);
console.log(data);
console.log(data.data); // album data
this.setState({album: data.data});
When using without await:
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response => {
this.setState({ album: response.data });
})
.catch(error => {
console.log(error);
});
So you must check the object key "data" returned by axios get.

How to read redux store from non React components?

I am building my first react native app with redux.
My store configuration is as follows
function configureStore(onComplete: ?() => void) {
const store = autoRehydrate()(createMyAppStore)(reducers);
persistStore(store, {storage: AsyncStorage}, onComplete);
if (isDebuggingInChrome) {
window.store = store;
}
return store;
}
I have an authentication action as follows
export function authentiate(credentials) {
return (dispatch) => {
return LoginApi.authenticate(credentials)
.then(response => {
return response.json().then(function(json) {
dispatch(onAuthSuccess(json));
});
})
.then(response => { dispatch(getUserInfo()); })
.catch(error => {
throw(error);
});
};
}
My getUserInfo action is as follows
export function getUserInfo() {
return (dispatch) => {
return LoginApi.gerUserInfo()
.then(result => { dispatch(onGetUserInfoSuccess(result.json())); })
.catch(error => {
throw(error);
});
};
}
My authentication reducer is as follows
function auth(state: State = initialState, action: Action): State {
switch (action.type) {
case types.AUTH_SUCCESSFUL :
return [...state, Object.assign({}, action.auth)];
default :
return state;
}
return state;
}
The LoginApi is a simple ES6 class. The getUserInfo requires details like access token and other parameters obtained from the LoginApi.authenticate call.
How do I read the authentication information from the redux store from a non-react component?
The second argument to redux-thunk is getState
export function getUserInfo() {
return (dispatch, getState) => {
const state = getState() // or cherry pick what you need
return LoginApi.gerUserInfo(state)
.then(result => { dispatch(onGetUserInfoSuccess(result.json())); })
.catch(error => {
throw(error);
});
};
}
https://github.com/gaearon/redux-thunk#composition
Which is just the stores own getState function.
http://redux.js.org/docs/api/Store.html#getState
you can export / import your store and call getState directly if you want.
let store = createStore(...)
export { store }
// anywhere.js
import { store } from './your-store.js'
let state = store.getState()