I have a NotifiCard component and a ReplyComment component
When I run the LookforReply function, the GETONECOMMENT_REQUEST and LOAD_POST_REQUEST dispatches are executed, and the data comes into onecomment.
which is
const {onecomment} = useSelector((state) => state.post);
And I also want to pass the {item:one comment} data in the ReplyComment using navigation.navigate.
However, when we run our code, the data does not come into ReplyComment immediately, so it causing an error.
this is my code
(NotifiCard.js)
const NotifiCard = ({item}) => {
const dispatch = useDispatch();
const navigation = useNavigation();
const {onecomment} = useSelector((state) => state.post);
const LookforReply = useCallback(() => {
dispatch({
type:GETONECOMMENT_REQUEST,
data:item?.CommentId,
}),
dispatch({
type: LOAD_POST_REQUEST,
data:item.PostId,
}),
navigation.navigate('ReplyComment',{itemm:onecomment})
},[]);
return (
<LookContainer onPress={LookforReply}>
<Label>대댓보기</Label>
</LookContainer>
);
};
when dispatch GETONECOMMENT_REQUEST, getonecommentAPI this api run and get data from backend router
(postsaga.js)
function getonecommentAPI(data) {
return axios.get(`/post/${data}/getonecomment`);
}
function* getonecomment(action) {
try {
const result = yield call(getonecommentAPI, action.data);
yield put({
type: GETONECOMMENT_SUCCESS,
data: result.data,
});
} catch (err) {
console.error(err);
yield put({
type: GETONECOMMENT_FAILURE,
error: err.response.data,
});
}
}
which is this backend router
(backend/post.js)
router.get('/:onecommentId/getonecomment', async (req, res, next) => {
try {
// console.log("req.params.onecomment:",req.params.onecommentId);
const onecomment = await Comment.findOne({
where:{id: req.params.onecommentId},
include: [{
model: User,
attributes: ['id', 'nickname'],
}],
})
// console.log("onecomment:",JSON.stringify(onecomment));
res.status(200).json(onecomment);
} catch (error) {
console.error(error);
next(error);
}
});
if i get result data this will put draft.onecomment
(reducer/post.js)
case GETONECOMMENT_REQUEST:
draft.loadPostLoading = true;
draft.loadPostDone = false;
draft.loadPostError = null;
break;
case GETONECOMMENT_SUCCESS:
// console.log("action.data:::",action.data);
draft.loadPostLoading = false;
draft.onecomment = action.data;
draft.loadPostDone = true;
break;
case GETONECOMMENT_FAILURE:
draft.loadPostLoading = false;
draft.loadPostError = action.error;
break;
and i can get data in onecomment by using useselector at (NotifiCard.js)
const {onecomment} = useSelector((state) => state.post);
what i want is that when i press LookforReply i want to pass itemm data to
ReplyComment component but if i press LookforReply, i can't get itemm data
Immediately
(ReplyComment.js)
const ReplyComment = ({route}) => {
const {itemm} = route.params;
console.log("itemm",itemm);
return (
<Container>
<TodoListView parenteitem={itemm} />
<AddTodo item={itemm} />
</Container>
);
};
I think that the LookforReply function is executed asynchronously and navigate is executed before the onecomment data comes in, and it doesn't seem to be able to deliver the itemm.
so how can i fix my code?....
Related
I am new with paginations, and I am getting an error in my getPosts action function. When I run it in the backend in Insomnia AND frontend, i get the same error "{(intermediate value)}.limit is not a function"
I am using mongoose for the backend and here is the function , which is in my controllers:
export const getPosts = async (req, res) => {
//passing it through the query via frontned
const { page } = req.query;
try {
const LIMIT = 8;
//convert page to a number
const startIndex = Number(page) - 1 * LIMIT; //get starting index from every page
const total = await PostMessage.countDocuments({});
//gives newest post first
const posts = await PostMessage.find()
.sort({ _id: -1 }.limit(LIMIT))
.skip(startIndex);
res
.status(200)
.json({
data: posts,
currentPage: Number(page),
numberOfPages: Math.ceil(total / LIMIT),
});
} catch (error) {
res.status(404).json({ message: error.message });
}
};
here is the frontend logic in my actions folder:
export const getPosts = (page) => async (dispatch) => {
try {
//fetch data from api
//then pass page into the api
const { data: { data, currentPage, numberOfPages} } = await api.fetchPosts(page);
console.log(data)
//call the async dispatch from thunk
//payload is the data send the api data into the payload
dispatch({ type: FETCH_ALL, payload:{ data, currentPage, numberOfPages} });
} catch (error) {
console.log(error.response?.data);
}
//payload is the data where we store all of our posts
};
here is my axios request:
export const fetchPosts = (page) => API.get(`/posts?page=${page}`)
here is where page is coming from
const useQuery = () => {
return new URLSearchParams(useLocation().search);
};
const Home = () => {
//where we will get page info from
const query = useQuery();
const history = useHistory();
//this will read our URL and see if we include page in there or if we dont have a page then we are on the first page
const page = query.get("page") || 1;```
I'm trying to load the second set of items from the reddit API with Infinite scrolling when the user scrolls to the bottom of the page and although they do load successfully, the previous items are overridden by the new ones.
You can see this happening here: https://reddix.netlify.app/
This is the Redux Slice with the Thunks:
// Gets the first 10 Posts from the API
export const getPosts = createAsyncThunk(
"post/getPosts",
async (apiAddress) => {
const response = await fetch(apiAddress);
if (!response.ok) throw new Error("Request Failed!");
const data = await response.json();
return data;
}
);
// Loads the Next 10 Posts
export const getMorePosts = createAsyncThunk(
"post/getMorePosts",
async (apiAddress) => {
const response = await fetch(apiAddress);
if (!response.ok) throw new Error("Request Failed!");
const data = await response.json();
return data;
}
);
const redditPostSlice = createSlice({
name: "post",
initialState: {
redditPost: {},
isLoading: false,
hasError: false,
moreIsLoading: false,
moreHasError: false,
},
extraReducers: (builder) => {
builder
.addCase(getPosts.pending, (state) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(getPosts.fulfilled, (state, action) => {
state.redditPost = action.payload.data;
state.isLoading = false;
state.hasError = false;
})
.addCase(getPosts.rejected, (state) => {
state.isLoading = false;
state.hasError = true;
})
.addCase(getMorePosts.pending, (state) => {
state.moreIsLoading = true;
state.moreHasError = false;
})
.addCase(getMorePosts.fulfilled, (state, action) => {
state.redditPost = action.payload.data;
state.moreIsLoading = false;
state.moreHasError = false;
})
.addCase(getMorePosts.rejected, (state) => {
state.moreIsLoading = false;
state.moreHasError = true;
});
},
});
And in this Search components I have the functionality for loading the pages:
const Search = () => {
const [input, setInput] = useState("");
const [isFetching, setIsFetching] = useState(false);
const redditPost = useSelector(selectRedditPost);
const dispatch = useDispatch();
// Get the Last Post
const lastPost = () => {
if (redditPost.children) {
const [lastItem] = redditPost.children.slice(-1);
const lastKind = lastItem.kind;
const lastId = lastItem.data.id;
return `${lastKind}_${lastId}`;
} else {
return;
}
};
// API Endpoints
const hotApiAddress = `https://www.reddit.com/r/${input}/hot.json?limit=10`;
const newApiAddress = `https://www.reddit.com/r/${input}/new.json?limit=10`;
const moreApiAddress = `https://www.reddit.com/r/${input}/new.json?limit=10&after=${lastPost()}`;
// Get Hot Posts
const handleHot = (e) => {
e.preventDefault();
if (!input) return;
dispatch(getPosts(hotApiAddress));
};
// Get New Posts
const handleNew = (e) => {
e.preventDefault();
if (!input) return;
dispatch(getPosts(newApiAddress));
};
// Fire Upon Reaching the Bottom of the Page
const handleScroll = () => {
if (
window.innerHeight + document.documentElement.scrollTop !==
document.documentElement.offsetHeight
)
return;
setIsFetching(true);
};
// Debounce the Scroll Event Function and Cancel it When Called
const debounceHandleScroll = debounce(handleScroll, 100);
useEffect(() => {
window.addEventListener("scroll", debounceHandleScroll);
return () => window.removeEventListener("scroll", debounceHandleScroll);
}, [debounceHandleScroll]);
debounceHandleScroll.cancel();
// Get More Posts
const loadMoreItems = useCallback(() => {
dispatch(getMorePosts(moreApiAddress));
setIsFetching(false);
}, [dispatch, moreApiAddress]);
useEffect(() => {
if (!isFetching) return;
loadMoreItems();
}, [isFetching, loadMoreItems]);
Is there any way to keep the previous items when the next set loads?
Because you set on every dispatch a different payload value, your previous array disappears. Take a look at the entityAdapter. Whit this adapter you can easily manage arrays, you can add, modify, update or remove items from the array. This is can be a solution for you.
Keep in a list the previous value, and when a next action is dispatched, append the existing list.
Note: you need the upsertMany method on the entityAdapter to keep the previous values.
Other solutions without entityAdapter:
You have to store the array in the state somehow because when another payload appears you have to access this array for example state.redditPosts = [...state.redditPosts, ...payload.array]. Or because you use redux js toolkit, you can mutate the state, state.redditPosts.push(...payload.array)
I am currently making a Spotify clone which gives user a preview of the song. The problem occurs when I am making many different api requests. When there are more than one requests on the page, it throws a 429 error(making too many requests at once).
Please read through the whole question as I have mentioned the steps I have taken to fix this below.
Profile.js
const { api, refreshableCall } = useSpotify()
const [error, setError] = useState(null)
const [userName, setUserName] = useState("")
const [userFollowers, setUserFollowers] = useState("")
const [userImage, setUserImage] = useState([])
const [userLink, setUserLink] = useState("")
const [userId, setUserId] = useState("")
const [userFollowing, setUserFollowing] = useState("")
const [userTopArtists, setUserTopArtists] = useState([])
const [userTopSongs, setUserTopSongs] = useState([])
useEffect(() => {
let disposed = false
refreshableCall(() => api.getMyTopTracks({
limit: 10,
time_range: "long_term"
}))
.then((res) => {
if (disposed) return
setUserTopSongs(res.body.items)
setError(null)
})
.catch((err) => {
if (disposed) return
setUserTopSongs([])
setError(err)
});
return () => disposed = true
})
useEffect(() => {
let disposed = false
refreshableCall(() => api.getMe())
.then((res) => {
if (disposed) return
var data = res.body
setUserName(data.display_name)
setUserImage(data.images)
setUserFollowers(data.followers["total"])
setUserLink(data.external_urls.spotify)
setUserId(data.id)
setError(null)
})
.catch((err) => {
if (disposed) return
setUserName("")
setUserImage([])
setUserFollowers("")
setUserLink("")
setUserId("")
setError(err)
});
return () => disposed = true
})
useEffect(() => {
let disposed = false
refreshableCall(() => api.getFollowedArtists())
.then((res) => {
if (disposed) return
var data = res.body
var artists = data.artists
setUserFollowing(artists.total)
})
.catch((err) => {
if (disposed) return
setUserFollowing([])
setError(err)
});
return () => disposed = true
})
useEffect(() => {
let disposed = false
refreshableCall(() => api.getMyTopArtists({
limit: 10,
time_range: "long_term"
}))
.then((res) => {
if (disposed) return
var data = res.body
var artists = data.items
setUserTopArtists(artists)
setError(null)
})
.catch((err) => {
if (disposed) return
setUserTopArtists([])
setError(err)
});
return () => disposed = true
})
SpotifyContext.js
import React, { useState, useEffect, useContext } from "react"
import axios from "axios"
import SpotifyWebApi from 'spotify-web-api-node';
const spotifyApi = new SpotifyWebApi({
clientId: 1234567890,
});
export const SpotifyAuthContext = React.createContext({
exchangeCode: () => { throw new Error("context not loaded") },
refreshAccessToken: () => { throw new Error("context not loaded") },
hasToken: spotifyApi.getAccessToken() !== undefined,
api: spotifyApi
});
export const useSpotify = () => useContext(SpotifyAuthContext);
function setStoredJSON(id, obj) {
localStorage.setItem(id, JSON.stringify(obj));
}
function getStoredJSON(id, fallbackValue = null) {
const storedValue = localStorage.getItem(id);
return storedValue === null
? fallbackValue
: JSON.parse(storedValue);
}
export function SpotifyAuthContextProvider({ children }) {
const [tokenInfo, setTokenInfo] = useState(() => getStoredJSON('myApp:spotify', null))
const hasToken = tokenInfo !== null
useEffect(() => {
if (tokenInfo === null) return;
// attach tokens to `SpotifyWebApi` instance
spotifyApi.setCredentials({
accessToken: tokenInfo.accessToken,
refreshToken: tokenInfo.refreshToken,
})
// persist tokens
setStoredJSON('myApp:spotify', tokenInfo)
}, [tokenInfo])
function exchangeCode(code) {
return axios
.post("http://localhost:3001/login", {
code
})
.then(res => {
// TODO: Confirm whether response contains `accessToken` or `access_token`
const { accessToken, refreshToken, expiresIn } = res.data;
// store expiry time instead of expires in
setTokenInfo({
accessToken,
refreshToken,
expiresAt: Date.now() + (expiresIn * 1000)
});
})
}
function refreshAccessToken() {
const refreshToken = tokenInfo.refreshToken;
return axios
.post("http://localhost:3001/refresh", {
refreshToken
})
.then(res => {
const refreshedTokenInfo = {
accessToken: res.data.accessToken,
// some refreshes may include a new refresh token!
refreshToken: res.data.refreshToken || tokenInfo.refreshToken,
// store expiry time instead of expires in
expiresAt: Date.now() + (res.data.expiresIn * 1000)
}
setTokenInfo(refreshedTokenInfo)
// attach tokens to `SpotifyWebApi` instance
spotifyApi.setCredentials({
accessToken: refreshedTokenInfo.accessToken,
refreshToken: refreshedTokenInfo.refreshToken,
})
return refreshedTokenInfo
})
}
async function refreshableCall(callApiFunc) {
if (Date.now() > tokenInfo.expiresAt)
await refreshAccessToken();
try {
return await callApiFunc()
} catch (err) {
if (err.name !== "WebapiAuthenticationError")
throw err; // rethrow irrelevant errors
}
// if here, has an authentication error, try refreshing now
return refreshAccessToken()
.then(callApiFunc)
}
return (
<SpotifyAuthContext.Provider value={{
api: spotifyApi,
exchangeCode,
hasToken,
refreshableCall,
refreshAccessToken
}}>
{children}
</SpotifyAuthContext.Provider>
)
}
Errors
Without the dependency, it keeps cycling and firing off requests, likely hundreds per second. (Error 429)
With the dependency, it seems the Access Token is being ignored or sidestepped. (Error: WebApiAuthentication - No token provided)
What I have tried to do ?
I tried to implement all the requests in a single useEffect, still getting the errors.
Calling useEffect with dependency array and without.
Link to the Github Repo
https://github.com/amoghkapoor/spotify-clone
status 429 means you have made too many calls in a specific time window.
you are therefore banned for this specific time window.
try waiting a bit before retrying.
did you try :
useEffect(..., [])
this guaranties it will be run only once.
None of your useEffect calls are using a dependency array, remember if useEffect is called without any dependencies it goes into an infinite loop. Either find what dependency or state change should re-run the useEffect hook and include it in the dependency array:
useEffect(() => { /* your logic */ }, [dependencies])
or if there are no dependencies simply fire it once the component mounts:
useEffect(() => { /* your logic */ }, [])
when i act onPress onPress={() => Login()} function
first of all, i want to get token by using signInWithKakao function,
second of all, right after i got token, if i have token, i want to dispatch profile by using
kakaoprofile function
if i use my code when i act onPress, this error occure
token is not defined
I think it is because signInWithKakao is not recognized because it is in the Login function.
how can i fix my code?
this is my code
import {
getProfile as getKakaoProfile,
login,
} from '#react-native-seoul/kakao-login';
const Kakao = () => {
const [result, setResult] = useState('');
const Login = () => {
const signInWithKakao = async() => {
const token = await login();
setResult(JSON.stringify(token));
};
if (token) {
const kakaoprofile = async() => {
const profile = await getKakaoProfile();
// console.log("profile:",profile);
dispatch({
type: KAKAOLOG_IN_REQUEST,
data: profile
})
};
}
}
return (
<Container>
<LoginButton onPress = {() => Login()}>
<Label>카카오 로그인</Label>
</LoginButton>
</Container>
);
};
export default Kakao;
You can try to create another function. let's say handleOrder and make it async and then put all your awaits there.
const Login = () => {
handleInOrder()
}
async function handleInOrder() {
try{
const token = await getKakaoProfile()
if(token){
let data = await nextInOrder(token)
//do a dispatch
}
} catch(err){
// handle the error
}
}
Let me know,if you need any help
I've written these actions and the saga to listen for them.
export async function addToWishlistApi(id: number) {
console.log("addToWishlistApi HELLO CAN ANYBODY HEAR ME");
const res = await axios.get(WishlistUrls.add(id));
console.log(res);
}
export function* addToWishlistSaga({ id }: { id: number }): Saga<void> {
console.log("addToWishlistSaga HELLO CAN ANYBODY HEAR ME");
try {
const res = yield call(addToWishlistApi, id);
console.log(res);
const wishlist = yield call(getCurrentWishlist);
console.log("WISHLIST", wishlist);
if (wishlist.contains(id))
yield put({ type: DealActionTypes.wishlistSuccess });
} catch (error) {
console.log("ERROR", error);
}
}
export async function getCurrentWishlist(): Promise<number[]> {
const res = await axios.get(WishlistUrls.getWishlist);
console.log(res.data.includes("WISHLIST-2.HTML"));
const $ = cheerio.load(res.data);
const tags = $(".button.yith-wcqv-button");
const idStrings: string[] = [];
tags.each((i, tag) => {
idStrings.push(tag.attribs["data-product_id"]);
});
const wishlist = idStrings.map(n => Number(n));
return wishlist;
}
export default function* wishlistSaga(): Saga<void> {
yield all([
yield takeEvery(DealActionTypes.addToWishlistStart, addToWishlistSaga),
yield takeEvery(
DealActionTypes.removeFromWishlistStart,
removeFromWishlistSaga
)
]);
}
I'm trying to test with recordSaga
import { runSaga } from "redux-saga";
export default async function recordSaga(saga, initialAction) {
const dispatched = [];
await runSaga(
{ dispatch: action => dispatched.push(action) },
saga,
initialAction
).done;
return dispatched;
}
And here are my tests:
describe("addToWishlistSaga", () => {
const startAction: Type.AddToWishlistStartAction = {
type: Types.addToWishlistStart,
id: deal.id
};
const successAction: Type.WishlistSuccessAction = {
type: Types.wishlistSuccess
};
const failureAction: Type.AddToWishlistFailureAction = {
type: Types.addToWishlistFailure,
id: deal.id
};
fit("dispatches a success action on success", async () => {
const dispatched = await recordSaga(wishlistSaga, startAction);
expect(dispatched).toContainEqual(successAction);
});
xit("dispatches a failure action on failure", async () => {
mock.onGet(WishlistUrls.add(deal.id)).networkErrorOnce();
const dispatched = await recordSaga(wishlistSaga, startAction);
expect(dispatched).toContainEqual(failureAction);
});
});
});
The saga setup and test setup are identical to what I've used successfully in other projects, but for some reason addToWishlistSaga is never being run. None of the desperate log statements are being printed, and dispatched comes back empty.
What am I doing wrong here? My head is about to explode.