Create a react component that fetches data - reactjs

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;
};

Related

How to test the useEffect hook using Jest

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

Why my dispatch action doesn't work in use effect after request?

I need help. I don't understand why my dispatch action doesn't work. I've redux store currency list and current currency.
My reducer:
export const currencyReducer = (
state: typeState = initialState,
action: TypeActionCurrency
): typeState => {
switch (action.type) {
case types.CURRENCY_FILL_LIST:
return { ...state, list: action.payload }
case types.CURRENCY_SET_CURRENT:
return {
...state,
current:
state.list.find(currency => currency._id === action.payload) ||
({} as ICurrency),
}
default:
return state
}
}
My actions:
export const setCurrencyList = (currencies: ICurrency[]) => ({
type: types.CURRENCY_FILL_LIST,
payload: currencies,
})
export const setCurrentCurrency = (_id: string) => ({
type: types.CURRENCY_SET_CURRENT,
payload: _id,
})
My useEffect:
useEffect(() => {
if (!list.length) {
const fetchCurrencies = async () => {
try {
const data = await $apiClient<ICurrency[]>({ url: '/currencies' })
dispatch(setCurrencyList(data))
if (!current._id) dispatch(setCurrentCurrency(data[0]._id))
} catch (error) {
console.log(error)
}
}
fetchCurrencies()
}
}, [])
I want make request when load page and write currency list to Redux store, if we don't have current currency we write default currency from data.
There is one more strange thing, my redux extension shows that the state has changed, but when I receive it via the log or useSelector, it is empty
enter image description here
Thanks!
I am not 100% sure but it should work.
const [loader, setLoader] = useState(false);
const list = useSelector(state => state.list)
useEffect(() => {
if (!list.length) {
const fetchCurrencies = async () => {
try {
setLoader(true)
const data = await $apiClient<ICurrency[]>({ url: '/currencies' })
dispatch(setCurrencyList(data))
if (!current._id) dispatch(setCurrentCurrency(data[0]._id))
} catch (error) {
console.log(error)
} finally {
setLoader(false)
}
}
fetchCurrencies()
}
}, [])
useEffect(() => {
console.log(list);
}, [loader])

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 can I access current redux state from useEffect?

I have a list of objects ("Albums" in my case) fetched from the database. I need to edit these objects.
In the editing component in the useEffect hook I fire up the action for getting the needed album using it's ID. This action works. However in the same useEffect I am trying to fetch the changed by before fired action redux state. And now I face the problem - all I am fetching is the previos state.
How can I implement in the useEffect fetching of current redux state?
I've seen similar questions here, however none of the answers were helpfull for my use case.
I am using redux-thunk.
Editing component. The problem appears in setFormData - it's fetching previous state from the reducer, not the current one. It seems that it fires before the state gets changed by the getAlbumById:
//imports
const EditAlbum = ({
album: { album, loading},
createAlbum,
getAlbumById,
history,
match
}) => {
const [formData, setFormData] = useState({
albumID: null,
albumName: ''
});
useEffect(() => {
getAlbumById(match.params.id);
setFormData({
albumID: loading || !album.albumID ? '' : album.albumID,
albumName: loading || !album.albumName ? '' : album.albumName
});
}, [getAlbumById, loading]);
const { albumName, albumID } = formData;
const onChange = e =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = e => {
e.preventDefault();
createAlbum(formData, history, true);
};
return ( //code );
};
EditAlbum.propTypes = {
createAlbum: PropTypes.func.isRequired,
getAlbumById: PropTypes.func.isRequired,
album: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
album: state.album
});
export default connect(
mapStateToProps,
{ createAlbum, getAlbumById }
)(withRouter(EditAlbum));
Action:
export const getAlbumById = albumID => async dispatch => {
try {
const res = await axios.get(`/api/album/${albumID}`);
dispatch({
type: GET_ALBUM,
payload: res.data
});
} catch (err) {
dispatch({
type: ALBUMS_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
reducer
const initialState = {
album: null,
albums: [],
loading: true,
error: {}
};
const album = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case GET_ALBUM:
return {
...state,
album: payload,
loading: false
};
case ALBUMS_ERROR:
return {
...state,
error: payload,
loading: false
};
default:
return state;
}
};
Will be grateful for any help/ideas
You should split up your effects in 2, one to load album when album id changes from route:
const [formData, setFormData] = useState({
albumID: match.params.id,
albumName: '',
});
const { albumName, albumID } = formData;
// Only get album by id when id changed
useEffect(() => {
getAlbumById(albumID);
}, [albumID, getAlbumById]);
And one when data has arrived to set the formData state:
// Custom hook to check if component is mounted
// This needs to be imported in your component
// https://github.com/jmlweb/isMounted
const useIsMounted = () => {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => (isMounted.current = false);
}, []);
return isMounted;
};
// In your component check if it's mounted
// ...because you cannot set state on unmounted component
const isMounted = useIsMounted();
useEffect(() => {
// Only if loading is false and still mounted
if (loading === false && isMounted.current) {
const { albumID, albumName } = album;
setFormData({
albumID,
albumName,
});
}
}, [album, isMounted, loading]);
Your action should set loading to true when it starts getting an album:
export const getAlbumById = albumID => async dispatch => {
try {
// Here you should dispatch an action that would
// set loading to true
// dispatch({type:'LOAD_ALBUM'})
const res = await axios.get(`/api/album/${albumID}`);
dispatch({
type: GET_ALBUM,
payload: res.data
});
} catch (err) {
dispatch({
type: ALBUMS_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
Update detecting why useEffect is called when it should not:
Could you update the question with the output of this?
//only get album by id when id changed
useEffect(() => {
console.log('In the get data effect');
getAlbumById(albumID);
return () => {
console.log('Clean up get data effect');
if (albumID !== pref.current.albumID) {
console.log(
'XXXX album ID changed:',
pref.current.albumID,
albumID
);
}
if (getAlbumById !== pref.current.getAlbumById) {
console.log(
'XXX getAlbumById changed',
pref.current.getAlbumById,
getAlbumById
);
}
};
}, [albumID, getAlbumById]);

React Redux not re-rendering, but state being copied?

I've read the docs here but I am having trouble getting the component to rerender after state is updated. The posts are being added, I just have to rerender the component manually to get them to show up, what am I missing?
I have this in the component:
class ListPosts extends Component {
state = {
open: false,
body: '',
id: ''
}
openPostModal = () => this.setState(() => ({
open: true,
}))
closePostModal = () => this.setState(() => ({
open: false,
}))
componentWillMount() {
const selectedCategory = this.props.selectedCategory;
this.props.fetchPosts(selectedCategory);
}
handleChange = (e, value) => {
e.preventDefault();
// console.log('handlechange!', e.target.value)
this.setState({ body: e.target.value });
};
submit = (e) => {
// e.preventDefault();
console.log(this.state.body)
const body = this.state.body;
const id = getUUID()
const category = this.props.selectedCategory;
const post = {
id,
body,
category
}
this.props.dispatch(addPost(post))
this.closePostModal()
}
Then down below I am adding the dispatch to props...
const mapStateToProps = state => ({
posts: state.postsReducer.posts,
loading: state.postsReducer.loading,
error: state.postsReducer.error,
selectedCategory: state.categoriesReducer.selectedCategory,
// selectedPost: state.postsReducer.selectedPost,
});
function mapDispatchToProps (dispatch) {
return {
fetchPosts: (selectedCategory) => dispatch(fetchPosts(selectedCategory)),
addPost: (postObj) => dispatch(addPost(postObj)),
}
}
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(ListPosts))
Here is the code for the reducer:
case C.ADD_POST :
const hasPost = state.some(post => post.id === action.payload.postObj.id)
console.log('caseADD_POST:', action.payload.postObj.id)
return (hasPost) ?
state :
[
...state,
post(null, action)
];

Resources