Like in title, component EventList is not re-rendered when redux state is updated. I think I've tried everything, but nothing works. That the state is modified I can see in the browser extension Redux DevTools.
Please, write what I'm doing wrong.
Thank you in advance for help.
EventList component:
interface RootState {
lastEvent: Event,
allEvents: Event[]
}
const EventList = () => {
const classes = useStyles();
const lastEvent = useSelector((state: RootState) => state.lastEvent);
var allEvents = useSelector((state: RootState) => state.allEvents);
const dispatch = useDispatch();
useEffect(() => {
dispatch(eventActions.getAllEvents());
}, [lastEvent]);
return (
<div>
{allEvents && <div>EventListTable...</div>}
</div>
);};
Action:
const getAllEvents = (): ThunkAction<
void,
RootState,
unknown,
Action<string>
> => (dispatch) => {
eventService.getAllEvents().then((events: Event[]) => {
dispatch(success(events));
});
function success(events: Event[]): EventActionsTypes {
return {
type: GET_ALL_EVENTS,
events: events,
};
}
};
Reducer:
export interface EventState {
lastEvent: Event | null;
allEvents: Event[];
}
const initialState: EventState = {
lastEvent: null,
allEvents: [],
};
export function eventReducer(state = initialState, action: EventActionsTypes) {
switch (action.type) {
case SAVE_EVENT: {
return {
...state,
lastEvent: action.event,
};
}
case GET_ALL_EVENTS: {
return {
...state,
allEvents: [...action.events],
};
}
default: {
return state;
}
}
}
Combined all reducers:
export const rootReducer = combineReducers({
authentication,
registration,
eventReducer,
alert,
});
export type RootState = ReturnType<typeof rootReducer>
Try
var allEvents = useSelector((state: RootState) => state.eventReducer .allEvents);
In redux devtools you can see where allEvents is set, why would you try and get it from state.allEvents
Here you can see how combineReducers work and what state it will produce.
Related
When I use dispatch as follows in my react component, My component keeps rendering. How can I avoid that?
const dispatch = useDispatch();
useEffect(() => {
dispatch(reportsActionCreators.changeSalesDashboardData(someData));
}, []);
in the parent component, I'm using useSelector as this. But didn't use this report's data.
const { selectedSalesTab } = useSelector<RootState, any>((state: RootState) => {
return {
selectedSalesTab: state.reports.selectedSalesTab,
};
this is the parent component I'm using.
const SalesReports: FC = () => {
const dispatch = useDispatch();
const { selectedSalesTab } = useSelector<RootState, any>((state: RootState) => {
return {
selectedSalesTab: state.reports.selectedSalesTab,
};
});
const getPageContent = useMemo(() => {
switch (selectedSalesTab) {
case salesReportsTabs[0].id:
return <Dashboard />;
default:
return <div>not found :(</div>;
}
}, [selectedSalesTab]);
return (
<div className="sales-report-wrapper">
<GTTabs
id="sales-reports-tabs"
onClickTab={(tab: Tab) => dispatch(reportsActionCreators.changeSalesTab(tab.id))}
tabs={salesReportsTabs}
defaultSelectedTabId={selectedSalesTab}
/>
<div>{getPageContent}</div>
</div>
);
};
export default SalesReports;
this is the Child component I'm using
const Dashboard: FC = () => {
const repostsRxjs = rxjsConfig(reportingAxios);
const dispatch = useDispatch();
useEffect(() => {
repostsRxjs
.post<SalesDashboardItem[]>(
'/sales-data/order-details/6087bc3606ff073930a10848?timezone=Asia/Dubai&from=2022-09-03T00:00:00.00Z&to=2022-12-25T00:00:00.00Z&size=10',
{
brandIds: [],
channelIds: [],
kitchenIds: [],
countryIds: [],
},
)
.pipe(
take(1),
catchError((err: any) => of(console.log(err))),
)
.subscribe((response: SalesDashboardItem[] | void) => {
if (response) {
dispatch(reportsActionCreators.changeSalesDashboardData(response));
}
});
}, []);
const { isActiveFilter } = useSelector<RootState, any>((state: RootState) => {
return {
isActiveFilter: state.filterData.isActiveFilter,
};
});
return (
<>
<div
onClick={() => {
dispatch(filterssActionCreators.handleFilterPanel(!isActiveFilter));
dispatch(
filterssActionCreators.changeSelectedFiltersType([
FilterTypes.BRAND,
FilterTypes.CHANNEL,
FilterTypes.COUNTRY,
FilterTypes.KITCHEN,
]),
);
}}
>
Dashboard
</div>
{isActiveFilter && <FilterPanel />}
</>
);
};
export default Dashboard;
Actions
import { SalesDashboardItem } from 'app/models/Reports';
import { actionCreator } from 'app/state/common';
export type ChangeSalesTabPayload = string;
export type ChangeSalesDashboardDataPayload = SalesDashboardItem[];
export const reportsActionTypes = {
CHANGE_SALES_TAB: 'CHANGE_SALES_TAB',
CHANGE_SALES_DASHABOARD_DATA: 'CHANGE_SALES_DASHABOARD_DATA',
};
export const reportsActionCreators = {
changeSalesTab: actionCreator<ChangeSalesTabPayload>(reportsActionTypes.CHANGE_SALES_TAB),
changeSalesDashboardData: actionCreator<ChangeSalesDashboardDataPayload>(
reportsActionTypes.CHANGE_SALES_DASHABOARD_DATA,
),
};
export type ReportsAction = {
type: typeof reportsActionTypes.CHANGE_SALES_TAB | typeof reportsActionTypes.CHANGE_SALES_DASHABOARD_DATA;
payload: ChangeSalesTabPayload | ChangeSalesDashboardDataPayload;
};
Reducer
import { SalesDashboardItem } from 'app/models/Reports';
import { salesReportsTabs } from 'app/utils/reports';
import { reportsActionTypes, ReportsAction } from './actions';
export type ReportsState = {
selectedSalesTab: string;
salesDashboardFilterData: {
brands: string[];
kitchens: string[];
channels: string[];
countries: string[];
};
salesDashBoardDatta: SalesDashboardItem[];
};
const initialState: ReportsState = {
selectedSalesTab: salesReportsTabs[0].id,
salesDashboardFilterData: {
brands: [],
kitchens: [],
channels: [],
countries: [],
},
salesDashBoardDatta: [],
};
export default (state = initialState, action: ReportsAction): ReportsState => {
switch (action.type) {
case reportsActionTypes.CHANGE_SALES_TAB:
return { ...state, selectedSalesTab: action.payload as string };
case reportsActionTypes.CHANGE_SALES_DASHABOARD_DATA:
return { ...state, salesDashBoardDatta: action.payload as SalesDashboardItem[] };
default:
return state;
}
};
root reducer
import { combineReducers } from 'redux';
import SidePanelReducer from './reducers/sidepanel.reducer';
import authReducer from './auth';
import onboardingReducer from './onboarding';
import applicationReducer from './application';
import inventoryConfigReducer from './inventoryConfig/inventory.reducer';
import reportsReducer from './reports';
import filtersReducer from './filter';
const rootReducer = combineReducers({
sidePanel: SidePanelReducer,
auth: authReducer,
onboarding: onboardingReducer,
application: applicationReducer,
inventory: inventoryConfigReducer,
reports: reportsReducer,
filterData: filtersReducer,
});
export default rootReducer;
when I'm adding the dispatch action in useEffect(componentDidMount) this looping is happening. Otherwise, this code works fine. How can I avoid that component rerendering?
I think the issue is that the useSelector hook is returning a new object reference each time which triggers the useMemo hook to re-memoize an "instance" of the Dashboard component. The new "instance" of Dashboard then mounts and runs its useEffect hook which dispatches an action that updates the state.reports state in the Redux store.
Instead of creating and returning a new object reference to destructure selectedSalesTab from, just return the state.reports object directly.
Change
const { selectedSalesTab } = useSelector<RootState, any>((state: RootState) => {
return {
selectedSalesTab: state.reports.selectedSalesTab,
};
});
to
const { selectedSalesTab } = useSelector<RootState, any>((state: RootState) => {
return state.reports;
});
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 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 });
}
I'm a little confused on passing an object to the redux store. I have successfully created the store and can add items from the initial state. The function also fires when called
Action:
import { GET_ITEM } from './OrderTypes'
export const getItem = (payload) => {
return {
type: GET_ITEM,
payload: { payload }
}
}
Reducer:
import { GET_ITEM } from './OrderTypes'
const initialState = {
orderList: [],
}
const orderReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ITEM: return {
...state,
orderList: [...state.orderList, action.payload]
}
default: return state
}
}
export default orderReducer
Component:
class TestComponentextends Component {
pushItem = () => {
this.props.getItem({
payload: 'test object'
})
}
render() {
return (
<input type='button' value='test btn' onClick={this.pushItem} />
)
}
}
const mapStateToProps = state => {
return {
orderList: state.orderList
}
}
const mapDispatchToProps = dispatch => {
return {
getItem: () => dispatch(getItem())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TestComponent)
What happens: An empty object is added to the orderList array.
What I want to happen: Store the object in pushItem in the orderList array.
Your mapDispatchToProps doesn't pass the arguments to the action creator (see mapDispatchToProps function arguments - 2nd example):
const mapDispatchToProps = dispatch => ({
getItem: (...args) => dispatch(getItem(...args))
})
Even easier is to let react-redux handle the mapping by using mapDispatchToProps as an object:
const mapDispatchToProps = {
getItem
}
I need to make redux action and then use state to update my url.
I have tried to make a promise:
const opa = (type: string, checked: boolean) => (
dispatch: any,
getState: any ) =>
Promise.resolve().then(() => {
return dispatch(sidebarEmploymentTypeActions.setType(type, checked))
})
And then use it:
const handleEmploymentTypeChange = (type: string, checked: boolean) => {
//#ts-ignore
dispatch(opa(type, checked)).then(console.log(employmentType))
}
But console.log returns previous state.
My action:
export const setType = (type: string, checked: boolean) => {
return {
type: sidebarEmploymentType.SET_TYPE,
employmentType: type,
checked
}
}
And reducer:
const sidebarEmploymentType: Reducer = (
state = defaultState,
{ type, employmentType, checked }
) => {
switch (type) {
case sidebarEmploymentTypeType.SET_TYPE:
return {
...state,
[employmentType]: {
checked: checked,
label: state[employmentType].label,
},
}
default:
return state
}
}
export { sidebarEmploymentType }
Root reducer:
export const rootReducer = combineReducers({
sidebarEmploymentType
})
And store creation:
import thunk from 'redux-thunk'
const store = createStore(
persistReducer(persistConfig, rootReducer),
composeEnhancers(applyMiddleware(thunk))
)