How to test the useEffect hook using Jest - reactjs

How can we initite the useEffect using jest to wite the test cases.
let initailState = {
loading: false,
card: [],
welcomd: true
}
const helloWorld = () => {
const [state, setState] = useState(initialState);
useEffect(() => {
axios.get(url)
.then(res => {
setState(...state, card: res.data, loading: true);
})
.catch(error => {
setState(error.respone.data);
});
},[]);
return(
{
state.loading && <h1> Welcome to Stackoverflow </h1>
}
);
}
I am not able to write the test case for this sceniarion based on hook

Related

how to test useEffect setInterval with react testing?

how to test if i give isSuccess true at 1 second ?
(when loading screen is done)
const [state, setState]= useState({
isLoading: true,
isSuccess: false
})
useEffect(() => {
setInterval(() => {
setState({
isSuccess:true
});
}, 1000)
});
if (!state.isSuccess) {
return <p data-testid='fetching-data' className='text-center'>"loading..."</p>
}
I tried like this but it doesn't work :
test('isSuccess true', async()=>{
render(<App/>)
const ele = screen.getByText(/learn react/i)
await waitFor(() => expect(ele).toBeInTheDocument())
})

Create a react component that fetches data

I'd like to create a component that takes in an ID of a movie and from that ID I would be able to fetch data i would be able to get details from that movie. For example, in my moviecard component, I run this axios get to obtain data on a movie.
const Moviecard = (props) => {
const {movie} = props
const [details, setDetails] = useState('')
useEffect(()=> {
if(movie) {
axios.get(`https://api.themoviedb.org/3/movie/${movie.id}?api_key=mykey&append_to_response=videos,images`).then(resp=> {
setDetails(resp.data)
})
.catch(err=> {
console.log(err)
})
}
}, [movie])
return (
<div>Movie Card</div>
)
}
export default Moviecard
So, I would like to create a component that only handles getting the data, and then importing that state which would contain all the data
Think this is where you would make your own custom hook, you can do something like
//useFetchMovie.js
export const useFetchMovie = (movieId) => {
const [movieData, setMovieData] = useState({
isLoading: true,
error: false,
data: null,
});
useEffect(() => {
if (movieId) {
axios
.get(
`https://api.themoviedb.org/3/movie/${movie.id}?api_key=mykey&append_to_response=videos,images`
)
.then((resp) => {
setMovieData((oldMovieData) => ({
...oldMovieData,
isLoading: false,
data: resp.data,
}));
})
.catch((err) => {
setMovieData((oldMovieData) => ({
...oldMovieData,
isLoading: false
error: err,
}));
});
}
}, [movieId]);
return movieData;
};

Unable to fetch data from api in component

I am working on this project in React JS where I fetch data from this API URL for my frontend development.
I have made my custom hooks to fetch the data into several files following this medium article as follows:
useApiResult.js
import { useState, useEffect } from "react";
export const useApiResult = (request) => {
const [results, setResults] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch(request)
.then(async (response) => {
if (response.ok) {
setResults(await response.json());
setError(null);
} else {
setError(await response.text())
}
})
.catch((err) => {
setError(err.message);
});
}, [request]);
return [results, error];
};
useImages.js
import { useMemo } from "react";
import { useApiResult } from "./useApiResult";
const BASE_URL = "http://api.vidyarajkumari.com";
const createUrl = (base, path) => `${base}${path}`;
const getImages = () => [
createUrl(BASE_URL, "/images/"),
{
method: "GET",
}
];
export const useImages = () => {
const request = useMemo(() => getImages(), []);
return useApiResult(request);
}
React component: Images.js
import React from "react";
import { useImages } from "../../hooks/useImages";
export default function Images() {
const [images, error] = useImages();
//console.log(images);
//console.log(error);
return (
<>
<div className="row">
{
images.map((item, index) => {
<div key={index} className="col-md-4 animate-box">
...
// Rest of code goes here
}
}
</>
</>
)
}
The problem is that I am unable to get the data in the Images.js component from the useImages hook. The console.log values of images return null. This has been bugging me for a while now and I would greatly appreciate a solution to this. What am I doing wrong here and how can I work around this?
P.S. The API Url is live; so feel free to reference it. Thank you for your time.
I Have a better way to do this using useReducer and custom hook, check this:
By the way, I think your API URL has some problems! (I added input for fetching another URL for test)
const IMAGE_URL = "http://api.vidyarajkumari.com/images/";
const initialState = { loading: true };
function fetchReducer(state, action) {
switch (action.type) {
case "fetch":
return {
...state,
error: undefined,
loading: true,
};
case "data":
return {
...state,
data: action.data,
loading: false,
};
case "error":
return {
...state,
error: "Error fetching data. Try again",
loading: false,
};
default:
return state;
}
}
function useFetch(url) {
const [state, dispatch] = React.useReducer(fetchReducer, initialState);
React.useEffect(() => {
dispatch({ type: "fetch" });
fetch(url, {
headers: {
accept: "application/json",
},
})
.then((res) => res.json())
.then((data) => dispatch({ type: "data", data }))
.catch((e) => {
console.warn(e.message);
dispatch({ type: "error" });
});
}, [url]);
return {
loading: state.loading,
data: state.data,
error: state.error,
};
}
function FetchComponent({url}) {
const { loading, data, error } = useFetch(url);
console.log(data);
if (loading) {
return <p>Fetching {url}...</p>;
}
if (error) {
return <p>{error}</p>
}
return <div>{JSON.stringify(data)}</div>
}
const App = () => {
const [url, setUlr] = React.useState(IMAGE_URL)
const inputEl = React.useRef(null);
const changeUrl = () => setUlr(inputEl.current.value)
return (
<React.Fragment>
<input defaultValue="https://icanhazdadjoke.com/" ref={inputEl} type="text" />
<button onClick={changeUrl}>Fetch</button>
{url && <FetchComponent url={url}/>}
</React.Fragment>
)
}
ReactDOM.render(<App/>, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Give results and error also, in the dependency array, So that component get render when result is updated.
import { useState, useEffect } from "react";
export const useApiResult = (request) => {
const [results, setResults] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch(request)
.then(async (response) => {
if (response.ok) {
setResults(await response.json());
setError(null);
} else {
setError(await response.text())
}
})
.catch((err) => {
setError(err.message);
});
}, [request, results, error]);
return [results, error];
};

How to hide and show progress bar when fetching data from network?

I am using the Context Api with Hooks in my ReactNative App.
Here is my code to fetch array of blogs from the api
const getBlogPosts = dispatch => {
return async () => {
try {
const response = await jsonServer.get("/blogposts");
dispatch({ type: "get_blogposts", payload: response.data });
} catch (err) {
dispatch({
type: "get_blogposts",
payload: "Something went wrong"
});
}
};
};
const blogReducer = (state, action) => {
switch (action.type) {
case "get_blogposts":
return action.payload;
.....
Here my Component file I am doing something like below
const IndexScreen = ({ navigation }) => {
const { state, getBlogPosts } = useContext(Context);
useEffect(() => {
getBlogPosts();
}, []);
return (
<View>
<FlatList..../>
{state.length === 0 ? <ProgressBar /> : null}
Suppose there are no blogs then the progress bar keeps showing even after the network operation is finished so I can't write the above code for showing and displaying the progress bar
Now I tried firing multiple dispatch when user calls getBlogPosts but that changes the value of state from boolean to array and then again to boolean.
Is there an easy way to handle the visibility of progress bar?
You can have a new type in the dispatch like get_blogposts_in_progress and set true/false in the reducer like state.loading = true if the dispatch is get_blogposts_in_progress and dispatch state.loading = false when the api call is a success or an error.
const getBlogPosts = dispatch => {
return async () => {
dispatch({ type: "get_blogposts_in_progress" });
try {
const response = await jsonServer.get("/blogposts");
dispatch({ type: "get_blogposts", payload: response.data });
} catch (err) {
dispatch({
type: "get_blogposts",
payload: "Something went wrong"
});
}
};
};
const blogReducer = (state, action) => {
switch (action.type) {
case "get_blogposts_in_progress":
return { ...state, ...{ loading: true } };
case "get_blogposts":
return { ...action.payload, ...{ loading: false } };
.....
And the component file.
const IndexScreen = ({ navigation }) => {
const { state, getBlogPosts } = useContext(Context);
useEffect(() => {
getBlogPosts();
}, []);
return (
<View>
<FlatList..../>
{state.loading ? <ProgressBar /> : null}
Since your blog array can be empty, your blog array may the same after loading. You will have to store a boolean value indicating that the loading is done in your state.
Once your data is fetched, just set this value to false :
const IndexScreen = ({ navigation }) => {
const { state, getBlogPosts } = useContext(Context);
const [loading, setLoading] = useState(true);
useEffect(async () => {
await getBlogPosts();
setLoading(false)
}, []);
return (
<View>
<FlatList..../>
{loading && <ProgressBar />}
You will also have to make your effect async to be able to use await.
I also used an inline if (&&) to render the loading component.

Is parent component re-rendered when the child component returns with something new?

As long as I know, child component is being re-rendered when the parent component's state or props change.
But I have no idea with the case of vice-versa.
Here is a code.
usePromise.js (custom made hooks)
import { useEffect, useReducer } from 'react';
const reducer = (state, action) => {
switch (action.type) {
case 'RESOLVED':
return { ...state, resolved: action.diff };
case 'LOADING':
return { ...state, loading: action.diff };
case 'ERROR':
return { ...state, resolved: action.diff };
default:
return state;
}
};
export default function usePromise(promiseCreator, deps = []) {
const [state, dispatch] = useReducer(reducer, {
resolved: null,
loading: false,
error: null
});
const process = async () => {
dispatch({ type: 'LOADING', diff: true });
try {
const result = await promiseCreator();
dispatch({ type: 'RESOLVED', diff: result });
} catch (e) {
dispatch({ type: 'ERROR', diff: e });
}
dispatch({ type: 'LOADING', diff: false });
};
useEffect(() => {
process();
}, deps);
return state;
}
usePromiseSample.js
import React from 'react';
import usePromise from './usePromise';
const wait = () => {
return new Promise(resolve =>
setTimeout(() => resolve('Hello hooks!'), 3000)
);
};
const UsePromiseSample = () => {
const { resolved, loading, error } = usePromise(wait);
console.log('test')
if (loading) return <div>loading...</div>;
if (error) return <div>error happened!</div>;
if (!resolved) return null;
return <div>{resolved}</div>;
};
export default UsePromiseSample;
As you can see above the code, child(usePromise.js) component's state is changing four times.
But it seems that parent(usePromiseSample.js) is also being re-rendered four times since test is logged four times.
How can I understand this situation easily?
usePromise is not a child component, but a custom hook. The hook itself it not being re-rendered when an action is dispatched inside usePromise, but the component that uses it is.
If you render UsePromiseSample inside another component, you will see that the parent is not re-rendering when UsePromiseSample is.
const { useEffect, useReducer } = React;
const reducer = (state, action) => {
switch (action.type) {
case 'RESOLVED':
return { ...state, resolved: action.diff, loading: false };
case 'ERROR':
return { ...state, resolved: action.diff, loading: false };
default:
return state;
}
};
function usePromise(promiseCreator, deps = []) {
const [state, dispatch] = useReducer(reducer, {
resolved: null,
loading: true,
error: null
});
const process = () => {
promiseCreator()
.then(result => {
dispatch({ type: 'RESOLVED', diff: result });
})
.catch(e => {
dispatch({ type: 'ERROR', diff: e });
});
};
useEffect(() => {
process();
}, deps);
return state;
}
const wait = () => {
return new Promise(resolve =>
setTimeout(() => resolve('Hello hooks!'), 3000)
);
};
const UsePromiseSample = () => {
const { resolved, loading, error } = usePromise(wait);
console.log('UsePromiseSample rendered')
if (loading) return <div>loading...</div>;
if (error) return <div>error happened!</div>;
if (!resolved) return null;
return <div>{resolved}</div>;
};
const App = () => {
console.log('App rendered')
return <UsePromiseSample />
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

Resources