I'm making a movie and I want to add pagination in the MoviesFromGenre component. Initially, MoviesFromGenre gets rendered based on the id from GenresList.
I want to add Pagination but I don't know how to update the state in useReducer when I click next/prev buttons?
import React, { useEffect, useReducer, useState } from 'react';
import { useParams } from 'react-router-dom'
import axios from 'axios';
import { API_URL, API_KEY } from '../api/config.js';
import Movies from './Movies'
const initialState = {
loading: true,
error: '',
movies: []
};
const reducer = (state, action ) => {
switch(action.type) {
case 'FETCH_SUCCESS':
return {
loading: false,
movies: action.payload,
error: '',
};
case 'FETCH_ERROR':
return {
loading: false,
movies: [],
error: 'Error'
};
default:
return state;
}
};
function MoviesFromGenre () {
const [state, dispatch] = useReducer(reducer, initialState);
const { id } = useParams();
const [pageNumber, setPageNumber] = useState(1)
useEffect(() => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}`
)
.then(response => {
dispatch({
type: 'FETCH_SUCCESS',
payload: response.data
})
})
.catch(err => {
dispatch({
type: 'FETCH_ERROR'
})
})
}, [])
const nextPage = () => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}&page=${pageNumber}`
)
.then(response => {
console.log(response.data)
})
setPageNumber(pageNumber+1)
}
const prevPage = () => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}&page=${pageNumber}`
)
.then(response => {
console.log(response.data)
})
setPageNumber(pageNumber-1)
}
return (
<div>
<Movies state={state}/>
<button onClick={prevPage}>Prev</button>
<button onClick={nextPage}>Next</button>
</div>
)
}
export default MoviesFromGenre;
I created a repository on GitHub.
I want to update the movies state when I click on next or prev buttons.
A Reddit user managed to solve my problem.
He suggested that I include pageNumber as a dependency in my useEffect hook, so it will run whenever pageNumber changes.
function MoviesFromGenre () {
const [state, dispatch] = useReducer(reducer, initialState);
const { id } = useParams();
const [pageNumber, setPageNumber] = useState(1)
useEffect(() => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}&page=${pageNumber}`
)
.then(response => {
dispatch({
type: 'FETCH_SUCCESS',
payload: response.data
})
})
.catch(err => {
dispatch({
type: 'FETCH_ERROR'
})
})
}, [pageNumber])
const nextPage = () => {
setPageNumber(pageNumber+1)
}
const prevPage = () => {
setPageNumber(pageNumber-1)
}
//....
}
Related
i'm newbie here, i'm stuck. i want to change value from false to true, to stop shimmering when data sucessfully to load.
i have action like this
import axios from "axios";
import { CONSTANT_LINK } from "./constants";
import { GET } from "./constants";
import { ERROR } from "./constants";
import { connect } from 'react-redux';
export const addData = () => {
return (dispatch) => {
axios
.get(CONSTANT_LINK)
.then((res) => {
dispatch(addDataSuccess(res.data));
})
.catch((err) => {
dispatch(errorData(true));
console.log("error");
});
};
};
const addDataSuccess = (todo) => ({
type: GET,
payload: todo,
});
const errorData = (error) => ({
type: ERROR,
payload: error,
});
and this is my homepage which influential in this matter
const [shimmerValue, setShimmerValue] = useState(false)
useEffect(() => {
setShimmerValue(true)
dispatch(addData());
}, []);
<ShimmerPlaceholder visible={shimmerValue} height={20}>
<Text style={styles.welcomeName}>Welcome,Barret</Text>
</ShimmerPlaceholder>
i dont understand how it works
You can pass callback like this
const [shimmerValue, setShimmerValue] = useState(false);
const updateShimmerValue = () => {
setShimmerValue(true);
}
useEffect(() => {
// setShimmerValue(true) // remove this from here
dispatch(addData(updateShimmerValue)); // pass callback as param here
}, []);
Callback call here like
export const addData = (callback) => {
return (dispatch) => {
axios
.get(CONSTANT_LINK)
.then((res) => {
....
callback(); // trigger callback like this here
})
.catch((err) => {
....
});
};
};
you can use it:
const [shimmerValue, setShimmerValue] = useState(false)
useEffect(() => {
setState(state => ({ ...state, shimmerValue: true }));
dispatch(addData());
}, [shimmerValue]);
I am creating a simple app to play with Redux ToolKit along react; however, I can see the object in the redux chrome tab, but unable to access it through components using react hooks.
My slice:
import {
createSlice,
createSelector,
createAsyncThunk,
} from "#reduxjs/toolkit";
const initialState = {
cryptoList: [],
loading: false,
hasErrors: false,
};
export const getCryptos = createAsyncThunk("cryptos/get", async (thunkAPI) => {
try {
const cryptosUrl =
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd";
let response = await fetch(cryptosUrl);
console.log(response);
return await response.json();
} catch (error) {
console.log(error);
}
});
const cryptoSlice = createSlice({
name: "cryptos",
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(getCryptos.pending, (state) => {
state.loading = true;
});
builder.addCase(getCryptos.fulfilled, (state, { payload }) => {
state.loading = false;
state.cryptoList = payload;
});
builder.addCase(getCryptos.rejected, (state, action) => {
state.loading = false;
state.hasErrors = action.error.message;
});
},
});
export const selectCryptos = createSelector(
(state) => ({
cryptoList: state.cryptoList,
loading: state.loading,
}),
(state) => state
);
export default cryptoSlice;
My component:
import React, { useEffect } from "react";
import { getCryptos, selectCryptos } from "./cryptoSlice";
import { useSelector, useDispatch } from "react-redux";
const CryptoComponent = () => {
const dispatch = useDispatch();
const { cryptoList, loading, hasErrors } = useSelector(selectCryptos);
useEffect(() => {
dispatch(getCryptos());
}, [dispatch]);
const renderCrypto = () => {
if (loading) return <p>Loading Crypto...</p>;
if (hasErrors) return <p>Error loading news...</p>;
if (cryptoList) {
return cryptoList.data.map((crypto) => <p> {crypto.id}</p>);
}
};
return (
<div className="container">
<div className="row">CryptoComponent: {renderCrypto()}</div>
</div>
);
};
export default CryptoComponent;
All constructed values from the state: cryptoList, loading, hasErrors, seem to be undefined at the component level.
Any suggestions are appreciated!
Have you tried using the following createSelector code:
export const selectCryptos = createSelector(
(state) => state,
(state) => ({
cryptoList: state.cryptoList,
loading: state.loading,
})
);
As per documentation state should be the first parameter:
https://redux.js.org/usage/deriving-data-selectors
//Fetch method:
import { useEffect, useState } from "react";
export const useFetch = (url) => {
const [state, setState] = useState({ data: null });
useEffect(() => {
setState((state) => ({ data: state.data }));
fetch(url)
.then((res) => res.json())
.then((json) => {
setState({ data: json });
});
}, [url]);
return state;
};
//My actual code:
function AdminDashboard() {
const { data } = useFetch(
//GET data
"https://jsonplaceholder.typicode.com/posts"
);
console.log(data)
The console prints my data 3 times. The first and second it prints null, then it prints the actual data.
I'm beginner in redux & hooks. I am working on form handling and trying to call an action through useDispatch hooks but it is calling my action twice.
I'm referring this article.
Here is the example:
useProfileForm.js
import { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchProfile } from '../../../redux/profile/profile.actions';
const useProfileForm = (callback) => {
const profileData = useSelector(state =>
state.profile.items
);
let data;
if (profileData.profile) {
data = profileData.profile;
}
const [values, setValues] = useState(data);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchProfile());
}, [dispatch]);
const handleSubmit = (event) => {
if (event) {
event.preventDefault();
}
callback();
};
const handleChange = (event) => {
event.persist();
setValues(values => ({ ...values, [event.target.name]: event.target.value }));
};
return {
handleChange,
handleSubmit,
values,
}
};
export default useProfileForm;
Action
export const FETCH_PROFILE_BEGIN = "FETCH_PROFILE_BEGIN";
export const FETCH_PROFILE_SUCCESS = "FETCH_PROFILE_SUCCESS";
export const FETCH_PROFILE_FAILURE = "FETCH_PROFILE_FAILURE";
export const ADD_PROFILE_DETAILS = "ADD_PROFILE_DETAILS";
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
function getProfile() {
return fetch("url")
.then(handleErrors)
.then(res => res.json());
}
export function fetchProfile() {
return dispatch => {
dispatch(fetchProfileBegin());
return getProfile().then(json => {
dispatch(fetchProfileSuccess(json));
return json;
}).catch(error =>
dispatch(fetchProfileFailure(error))
);
};
}
export const fetchProfileBegin = () => ({
type: FETCH_PROFILE_BEGIN
});
export const fetchProfileSuccess = profile => {
return {
type: FETCH_PROFILE_SUCCESS,
payload: { profile }
}
};
export const fetchProfileFailure = error => ({
type: FETCH_PROFILE_FAILURE,
payload: { error }
});
export const addProfileDetails = details => {
return {
type: ADD_PROFILE_DETAILS,
payload: details
}
};
Reducer:
import { ADD_PROFILE_DETAILS, FETCH_PROFILE_BEGIN, FETCH_PROFILE_FAILURE, FETCH_PROFILE_SUCCESS } from './profile.actions';
const INITIAL_STATE = {
items: [],
loading: false,
error: null
};
const profileReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ADD_PROFILE_DETAILS:
return {
...state,
addProfileDetails: action.payload
}
case FETCH_PROFILE_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_PROFILE_SUCCESS:
return {
...state,
loading: false,
items: action.payload.profile
};
case FETCH_PROFILE_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
items: []
};
default:
return state;
}
}
export default profileReducer;
**Component:**
import React from 'react';
import { connect } from 'react-redux';
import useProfileForm from './useProfileForm';
import { addProfileDetails } from '../../../redux/profile/profile.actions';
const EducationalDetails = () => {
const { values, handleChange, handleSubmit } = useProfileForm(submitForm);
console.log("values", values);
function submitForm() {
addProfileDetails(values);
}
if (values) {
if (values.error) {
return <div>Error! {values.error.message}</div>;
}
if (values.loading) {
return <div>Loading...</div>;
}
}
return (
<Card>
...some big html
</Card>
)
}
const mapDispatchToProps = dispatch => ({
addProfileDetails: details => dispatch(details)
});
export default connect(null, mapDispatchToProps)(EducationalDetails);
Also when I'm passing data from const [values, setValues] = useState(data); useState to values then ideally I should receive that in component but I'm not getting as it is showing undefined.
const { values, handleChange, handleSubmit } = useProfileForm(submitForm);
values is undefined
The twice dispatch of action is probably because you have used React.StrictMode in your react hierarchy.
According to the react docs, in order to detect unexpected sideEffects, react invokes a certain functions twice such as
Functions passed to useState, useMemo, or useReducer
Now since react-redux is implemented on top of react APIs, actions are infact invoked twice
Also when I'm passing data from const [values, setValues] = useState(data); useState to values then ideally I should receive that in component but I'm not getting as it is showing undefined.
To answer this question, you must know that values is not the result coming from the response of dispatch action from reducer but a state that is updated when handleChange is called so that is supposed to remain unaffected by the action
I think you mean to expose the redux data from useProfileForm which forgot to do
const useProfileForm = (callback) => {
const profileData = useSelector(state =>
state.profile.items
);
let data;
if (profileData.profile) {
data = profileData.profile;
}
const [values, setValues] = useState(data);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchProfile());
}, [dispatch]);
const handleSubmit = (event) => {
if (event) {
event.preventDefault();
}
callback();
};
const handleChange = (event) => {
event.persist();
setValues(values => ({ ...values, [event.target.name]: event.target.value }));
};
return {
handleChange,
handleSubmit,
values,
data // This is the data coming from redux store on FetchProfile and needs to logged
}
};
export default useProfileForm;
You can use the data in your component like
const { values, handleChange, handleSubmit, data } = useProfileForm(submitForm);
I want to get 20 posts by scroll down each time how can i do? my project have big Data and I use redux for get data, can I get data step by step? for example get 20 posts for first time and when a user scroll down load the next 20 posts.
I use React Hooks for develop
my posts component source is:
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import PostItem from './PostItem';
import { getPosts } from '../../actions/post';
const Posts = ({ getPosts, post: { posts, loading } }) => {
useEffect(() => {
getPosts();
}, [getPosts]);
return loading ? <Spinner /> : (
{posts.map(post => (
<PostItem key={post._id} post={post} />
))}
)
}
Posts.propTypes = {
getPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
post: state.post
});
export default connect(mapStateToProps, { getPosts })(Posts)
my action code is:
import { setAlert } from './alert';
import {
GET_POSTS,
POST_ERROR
} from "../actions/types";
// Get Posts
export const getPosts = () => async dispatch => {
try {
const res = await axios.get('/api/ads');
dispatch({
type: GET_POSTS,
payload: res.data
});
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.satusText, status: err.response.satus }
});
}
}```
///////////////////////////////
///////////////AND REDUCER IS :
import {
GET_POSTS,
POST_ERROR
} from '../actions/types';
const initialState = {
posts: [],
loading: true,
error: {}
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_POSTS:
return {
...state,
posts: payload,
loading: false
}
case POST_ERROR:
return {
...state,
error: payload,
loading: false
}
default:
return state;
}
}
You can use react-infinite-scroller library. I've tried to change your Posts method, so maybe it would be useful.but as mentioned in comments you should add pagination to your API.
const Posts = ({ getPosts, post: { posts, loading } }) => {
useEffect(() => {
getPosts();
}, [getPosts]);
const itemsPerPage = 20;
const [hasMoreItems, sethasMoreItems] = useState(true);
const [records, setrecords] = useState(itemsPerPage);
const showItems=(posts)=> {
var items = [];
for (var i = 0; i < records; i++) {
items.push( <PostItem key={posts[i]._id} post={posts[i]} />);
}
return items;
}
const loadMore=()=> {
if (records === posts.length) {
sethasMoreItems(false);
} else {
setTimeout(() => {
setrecords(records + itemsPerPage);
}, 2000);
}
}
return <InfiniteScroll
loadMore={loadMore}
hasMore={hasMoreItems}
loader={<div className="loader"> Loading... </div>}
useWindow={false}
>
{showItems()}
</InfiniteScroll>{" "}
}
Working Codesandbox sample with fake data.