I have a simple program that retrieve some remote data and set to redux store and then display. Got an infinite loop at the end, using all kinds of methods described everywhere, just doesn't work. I believe it might be a small error, just cannot figure it out.
Component:
const OrdersScreen = (props) => {
useEffect(() => {
props.getCurrentOrders();
}, []);
return (
<>
....
</>
)
}
const mapStateToProps = (state) => {
return state;
};
const mapDispatchToProps = dispatch => {
return bindActionCreators({
getCurrentOrders
}, dispatch)
};
export default connect(mapStateToProps, mapDispatchToProps)(OrdersScreen);
Action::
export function getCurrentOrders() {
return function (dispatch, getState) {
var userID = getState().authReducer.user.local.email;
fetchServer('mGetOrders', { userID: userID })
.then((res) => {
dispatch({ type: 'GET_ORDERS', orders: res });
})
}
};
Reducer:
export default function orderReducer(state = initialState, action) {
switch (action.type) {
case 'GET_ORDERS':
return {
...state,
orders: action.orders,
}
...
Store:
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
I already tried everything I can find from the web, but none of them work. Really weird for me. A big thanks to anybody who can help me out of this.
Related
I am very new to RTK, so I am trying to create a store and a slicer.
At first, at least I want to fetch some data from an API so when it start loading and after being succeed, I know the state of it.
Here I am creatinf the slicer:
const initialState: PlayerState = {
players: [],
status: 'idle'
};
export const getPlayers = createAsyncThunk('players/getPlayers', async () => {
const response = await axios.get(
'https://6360055fca0fe3c21aaacc04.mockapi.io/player'
);
return response.data;
});
const playerSlice = createSlice({
name: 'players',
initialState,
reducers: {
addPlayer: (state, action: PayloadAction<IPlayerProps>) => {
console.log('done');
state.players.push(action.payload);
}
},
extraReducers: {
[getPlayers.pending]: (state, action) => {
console.log('loading');
state.status = 'loading';
},
[getPlayers.fulfilled]: (state, action) => {
console.log('succeeded');
state.status = 'succeeded';
state.players = state.players.concat(action.payload);
}
}
});
export const { addPlayer } = playerSlice.actions;
export const selectPlayers = (state: RootState) => state.players.payload;
And here I am trying to connect it to the store:
//#ts-nocheck
import { configureStore } from '#reduxjs/toolkit'
import { addPlayer } from './playerSlice'
export const store = configureStore({
reducer: {
players: addPlayer,
},
})
export type RootState = ReturnType<typeof store.getState>;
So, after that I have a page with a button, so when I click it I try to dispatch something out of it with no luck unfortunately:
const NextPage = () => {
const dispatch = useDispatch();
return (
<ButtonNext
onClick={() => {
dispatch(addPlayer);
}}
text="< Back"
/>
);
};
export default NextPage;
Any help would be appreciated! :)
The are several issues in your code
First fix your createAsyncThunk
export const getPlayers = createAsyncThunk('players/getPlayers'
async (_unusedArgs, _thunkApi) => {
const response = await fetch('http://localhost:3000/players')
return response.json()
}
)
Your slice should look like this, note the builder callbacks for the cases:
export const playerSlice = createSlice({
name: "players",
initialState,
reducers: {
addPlayer: (state, action) => {
console.log("done");
state.players.push(action.payload);
}
},
extraReducers: (builder) => {
builder.addCase(getPlayers.fulfilled, (state, action) => {
console.log(action.payload);
state.players = action.payload;
state.status = "idle";
});
builder.addCase(getPlayers.pending, (state, action) => {
console.log("loading");
state.status = "loading";
});
}
});
export default playerSlice.reducer;
Call it inside the anonymous fn
<ButtonNext
onClick={() => {
dispatch(getPlayers()); // call with no arguments.
}}
text="< Back"
/>
And I also think that your root reducer in store is not right
import playerSlice from './playerSlice' // defaulted export
export const store = configureStore({
reducer: {
players: playerSlice,
},
})
Please check this sandbox with working example: https://codesandbox.io/s/redux-toolkit-basic-players-w-pokemons-6wmjm0?file=/src/features/playerSlice.js:409-995
I am using redux toolkit with thunk to receive data from api.
I need to fetch data from 2 apis in consecutive order using data I got from the first api call as a argument of second api call (search1 first, then search2)
In order to do that, I need to wait for the first dispatch to fully complete its job from calling getSearch1 to updating the state.
Please help!
// store
import { configureStore } from "#reduxjs/toolkit";
import searchReducer from "./slice/searchSlice";
export const store = configureStore({
reducer: {
search: searchReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
// slice
export const getSearch1 = createAsyncThunk(
"search/getSearch1",
async (args: string[]) => {
const result = await ApiUtil.search1(args);
return result;
}
);
export const getSearch2 = createAsyncThunk(
"search/getSearch2",
async (ids: string[]) => {
const result = await ApiUtil.search2(ids);
return result;
}
);
export const searchSlice = createSlice({
name: "search",
initialState,
reducers: {...},
extraReducers: (builder) => {
builder
.addCase(getSearch1.fulfilled, (state, action) => {
state.search1 = action.payload;
})
.addCase(getSearch2.fulfilled, (state, action) => {
state.search2 = action.payload;
});
},
});
// home page
import {
...
getSearch1,
getSearch2,
} from "../../redux/slice/searchSlice";
const handleSearch = () => {
dispatch(getSearch1(args));
const ids = search1?.map((item) => item.id.toString());
dispatch(getSearch2(ids ?? []));
history.push(ROUTES.RESULT_PAGE, search1);
};
You can use .unwrap() method to achieve that,see the documentation
:
try {
const { data } = await dispatch(getSearch1(args)).unwrap()
await dispatch(getSearch2(data ?? []));
// handle result here
} catch (rejectedValueOrSerializedError) {
// handle error here
}
I solved it just as slideshowp2's shared link.
useEffect(() => {
getResult();
}, [dispatch]); // listen for dispatch(), should run getResult() twice
const getResult = async () => {
let action;
if (!search1) { // skip below when called twice
action = await dispatch(getSearch1(args));
}
if (isFulfilled(action)) {
const id = action.payload.map((item) => item.id.toString());
dispatch(getSearch2(id ?? []));
}
};
I am working on a project and I need to fetch data from backend or from an API. I tried fetch the data but nothing appears. I think I am doing something wrong in the container. I am a beginner in react-redux, I don't know what I am doing wrong.
I've already read all the posts but nothing seems to works.
my reducer:
const initialState={
articles: [],
};
const rootReducer = (state = initialState, action) => {
const { type, payload }=action;
switch(type) {
case SRETRIEVE_ARTICLE:{
return {
...state,
articles:payload,
};
}
default: return state;
}
}
export default rootReducer;
This is what I have right now in container:
import Articles from 'components/Articles';
import { fetchArticles } from '../../pages/index';
const mapStateToProps = (state) => ({
articles:state.articles
})
const ConnectedArticles = connect(
mapStateToProps,
{fetchArticles}
)(Articles)
export default ConnectedArticles;
pages.js
axios.get('API').then((response) => {
const { data } = response;
dispatch({ type: RETRIEVE_ARTICLES, payload: data });
});
};
const Index = () => {
const articles= useSelector((state) => state.articles);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchArticles);
}, []);
return <>{articles && articles.map((article) => <Article key={article.id} name={article.name} />)}</>;
};
Index.getInitialProps = async () => ({
authRequired: true,
label: 'Dashboard',
});
export default Index;
Also I defined the action type: export const SET_UNOPENED_REWARD = 'SET_UNOPENED_REWARD';
and action const unopenedRewards = (payload) => ({ type: SET_UNOPENED_REWARD, payload });
One very nice way to do data fetching with redux is to use redux toolkit's createAsyncThunk and createSlice functions.
// src/features/articles/articlesSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const fetchArticles = createAsyncThunk("articles/get", async () => {
// Here you can use axios with your own api
const response = await fetch("https://rickandmortyapi.com/api/character");
const json = await response.json();
return json.results;
});
export const slice = createSlice({
name: "articles",
initialState: {
loading: false,
data: []
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchArticles.pending, (state) => {
state.loading = true;
});
builder.addCase(fetchArticles.fulfilled, (state, action) => {
state.data = action.payload;
state.loading = false;
});
builder.addCase(fetchArticles.rejected, (state) => {
state.loading = false;
});
}
});
export default slice.reducer;
// src/features/articles/Articles.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchArticles } from "./articlesSlice";
export const Articles = () => {
const articles = useSelector((state) => state.articles.data);
const loading = useSelector((state) => state.articles.loading);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchArticles());
}, []);
return (
<>
{loading && "...loading"}
{articles.map((article) => <Article key={article.id} {...article} />)}
</>
);
};
you should use async and await
let response = await axios.get('https://run.mocky.io/v3/5c045896-3d18-4c71-a4e5-5ed32fbbe2de')
if(response.status==200){
dispatch({ type: RETRIEVE_ARTICLES, payload: data });
}
Please help me how I can introduce new function like getOrdersByCustomer in ordersSlice. I have provided full code of ordersSlice below. Please tell me what is extraReducers and how it works.
import { createSlice, createAsyncThunk, createEntityAdapter } from '#reduxjs/toolkit';
import axios from 'axios';
export const getOrders = createAsyncThunk('eCommerceApp/orders/getOrders', async () => {
const response = await axios.get('/api/e-commerce-app/orders');
const data = await response.data;
return data;
});
export const removeOrders = createAsyncThunk(
'eCommerceApp/orders/removeOrders',
async (orderIds, { dispatch, getState }) => {
await axios.post('/api/e-commerce-app/remove-orders', { orderIds });
return orderIds;
}
);
const ordersAdapter = createEntityAdapter({});
export const { selectAll: selectOrders, selectById: selectOrderById } = ordersAdapter.getSelectors(
state => state.eCommerceApp.orders
);
const ordersSlice = createSlice({
name: 'eCommerceApp/orders',
initialState: ordersAdapter.getInitialState({
searchText: ''
}),
reducers: {
setOrdersSearchText: {
reducer: (state, action) => {
state.searchText = action.payload;
},
prepare: event => ({ payload: event.target.value || '' })
}
},
extraReducers: {
[getOrders.fulfilled]: ordersAdapter.setAll,
[removeOrders.fulfilled]: (state, action) => ordersAdapter.removeMany(state, action.payload)
}
});
export const { setOrdersSearchText } = ordersSlice.actions;
export default ordersSlice.reducer;
In Addition
Also can you please tell me what I will do with this following code for my custom function getOrdersByCustomer.
export const { selectAll: selectOrders, selectById: selectOrderById } = ordersAdapter.getSelectors(
state => state.eCommerceApp.orders
);
because, in my component I have used like
const orders = useSelector(selectOrders);
You can introduce new (async) functions as you already have (I used the customerId as part of the url -> you could access it through the params in your backend):
export const getOrdersByCustomer = createAsyncThunk('eCommerceApp/orders/getOrdersByCustomer', async (customerId) => {
const response = await axios.get(`/api/e-commerce-app/orders/${customerId}`);
const data = await response.data;
return data;
});
Then you can handle the response in your extraReducer:
extraReducers: {
[getOrders.fulfilled]: ordersAdapter.setAll,
[removeOrders.fulfilled]: (state, action) => ordersAdapter.removeMany(state, action.payload),
[getOrdersByCustomer.fulfilled]: (state, action) =>
// set your state to action.payload
}
The extraReducers handle actions like async thunks. The createAsyncThunk function return 3 possible states (along with other things): pending, rejected or fulfilled. In your case you only handle the fulfilled response. You could also set your state with the other two options (in your case [getOrdersByCustomer.pending] or [getOrdersByCustomer.rejected]
trying to save in local storage bookmarked recipes but i keep getting state.bookmarkedRecipes is not iterable what I am doing wrong? Please help me!!
redux store
const reducer = combineReducers({
recipeList: recipeListReducer,
recipeDetail: recipeDetailsReducer,
bookmark: bookmarkReducer,
});
const bookmarkedRecipesFromStorage = localStorage.getItem("bookmarkedRecipes")
? JSON.parse(localStorage.getItem("bookmarkedRecipes"))
: [];
const initialState = {
bookmark:{
bookmarkedRecipes: bookmarkedRecipesFromStorage,
};
}
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
bookmarkReducer
The payload which is the new bookmarked recipe will be added to the bookmarkedRecipes that was saved in the localStorage.
export const bookmarkReducer = (state = { bookmarkedRecipes: [] }, action) => {
switch (action.type) {
case BOOKMARK_ADD_RECIPE: {
return {
...state,
bookmarkedRecipes: [...state.bookmarkedRecipes, action.payload],
};
}
default:
return { state };
}
};
bookmarkAction
This is what the action looks like with the passed id it will get the recipe from the api and pass it as a payload with only the properties below.
export const addBookmark = (id) => async (dispatch, getState) => {
const { data } = await axios.get(
`https://forkify-api.herokuapp.com/api/get?rId=${id}`
);
console.log(data.recipe);
dispatch({
type: BOOKMARK_ADD_RECIPE,
payload: {
recipe: data.recipe.recipe_id,
title: data.recipe.title,
},
});
localStorage.setItem(
"bookmarkedRecipes",JSON.stringify(getState().bookmark.bookmarkedRecipes)
);
};