Action object doesn't reach the reducer when using redux-thunk - reactjs

I'm making a to-do app with react-typescript-redux and want to fetch todos from my server. When I hardcode todos, using this.props.getTodos['todo1','todo2'] in my container, everything works, but when I try to do it asynchronously,using this.props.requestTodos() - it doesn't. When I use this.props.requestTodos() response from the server reaches the getTodos actionCreator but for some reason the action object doesn't reach the reducer, so the state doesn't get updated. How do I fix this?
actionCreators:
export const getTodos = (todoItems: ReadonlyArray<string>) => {
return {
type: Constants.GET_TODOS as typeof Constants.GET_TODOS,
payload: {todoItems},
};
};
export const requestTodos = () => {
return (dispatch: any) => {
// tslint:disable-next-line:no-expression-statement
axios('/todos')
.then((todos: any) => getTodos(JSON.parse(todos.data).todos))
.catch((err: any) => console.log(err));
};
};
Store:
const defaultState: IDefaultState = {
todoItems: [],
};
const store = createStore(
rootReducer,
defaultState,
applyMiddleware(
thunk,
createLogger(),
));
Reducer:
const rootReducer = combineReducers({
todoItems,
});
TodoItems(reducer):
function todoItems(state = ([] as ReadonlyArray<string>), action: any): any {
switch (action.type) {
case Constants.GET_TODOS:
return [
...state,
action.payload.todoItems,
];
default:
return state;
}
}
Container component:
class TodoList extends React.Component<IProps, {}> {
public componentDidMount(): any {
// tslint:disable-next-line:no-expression-statement
this.props.requestTodos();
}
public render(): JSX.Element {
const todoItems = this.props.todoItems.map((text, i) => (
<TodoItem key={i} text={text} />
));
return(
<div>
<ul className='todo-list'>
{todoItems}
</ul>
</div>
);
}
}

Looks like you're calling getTodos(), but not actually dispatching it. Try this:
.then((todos: any) => dispatch(getTodos(JSON.parse(todos.data).todos)))

Related

How to execute api call with redux?

I'm recently learn about react-redux. Now i'm bit stuck about how can i execute the api call.
Here is my action.ts
export const fetchEvent = () => async (
dispatch: Dispatch
) => {
dispatch({ type: ActionTypes.FETCH_EVENT_REQUEST });
try {
const response = await axios.get<EvenData[]>(
`https://jsonplaceholder.typicode.com/todos/`
);
dispatch<FetchEventAction>({
type: ActionTypes.FETCH_EVENT_SUCCESS,
payload: response.data
});
} catch (error) {
dispatch({ type: ActionTypes.FETCH_EVENT_FAILURE, error });
}
};
and here is my event list and this is where i want call the api call
const mapStateToProps = (state: ActionTypes) => ({});
const dispatchProps = {};
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;
type State = {};
class EventList extends React.Component<Props, State> {
componentDidMount() {
// dispatch(fetchEvent());
}
render() {
return (
<section>
<p>{ }</p>
</section>
);
}
}
export default connect(
mapStateToProps,
dispatchProps
)(EventList);
So how can i make the api call ? inside my componentDidMount
you are missing the mapDispatchToProps , the one your are using is empty {}.
import {fetchEvent} from 'action.ts' // change to correct path
const mapStateToProps = (state: ActionTypes) => ({});
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
fetchEvent: () => {
dispatch(fetchEvent())
}
};
};
type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;
type State = {};
class EventList extends React.Component<Props, State> {
componentDidMount() {
this.props.fetchEvent()
}
render() {
return (
<section>
<p>{ }</p>
</section>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(EventList);

How to properly type app state in Typescript?

I'm getting the following error:
Property 'todos' does not exist on type 'Todo[]'.
20 | return (
21 | <Wrapper>
22 | {store?.todos?.map((item) => (
| ^
This is my component:
const TodoList: React.FC = () => {
const todoSelector = (state: ApplicationState) => state.todos;
const store = useSelector(todoSelector);
return (
<Wrapper>
{store?.todos?.map((item) => (
<Todolement key={item.id} element={item}>
{item.name} {item.content}
</TodoElement>
))}
</Wrapper>
);
};
My application state:
export interface ApplicationState {
todos: Todo[];
}
And todo type:
export type Todo = {
id: string;
content: string;
name: string;
};
How to properly type this state to avoid this issue?
====================== update ======================
I added console.log(store) in list component and it returns:
{todos: Array(0)}
My initial state in store: { todos: [] }
===================== update 2 =====================
My store init:
const composedEnhancers = composeWithDevTools();
const initialState = getState();
const store = createStore(rootReducer, initialState, composedEnhancers);
getState method:
const getState = (): any => {
try {
const serializedState = localStorage.getItem('todos');
if (serializedState === null) {
return { todos: [] };
}
return JSON.parse(serializedState);
} catch (err) {
return undefined;
}
};
Root reducer:
const rootReducer = combineReducers({
todos: todoReducer,
});
And todo reducer:
export type TodoAction = ActionType<typeof actions>;
export type TodoState = Readonly<{
todos: Todo[];
}>;
const initialState: TodoState = {
todos: [],
};
export default combineReducers<TodoState, TodoAction>({
todos: (state = initialState.todos, action) => {
switch (action.type) {
case ADD_TODO:
return [...state, action.payload];
I think the problem is your code above is that you nested the todos state by several levels by using function combineReducers.
Here is what I meant, in your todoReducer you export this:
export default combineReducers<TodoState, TodoAction>({
todos: (state = initialState.todos, action) => {
// Redux will wrap your state in `todos` key this way
And at your rootReducer, you combine them once again:
const rootReducer = combineReducers({
todos: todoReducer,
});
which means your todos state is now nested 2 levels of todos which is not as same as the state you has expressed in your type ApplicationState, here is the actual store:
{
todos: {
todos: []
}
}
In order to keep the state align with ApplicationState, you just simply don't have to use combineReducers and don't initialize with object has todos key in your todoReducer:
export default (state = [], action) => {
// ...
according to your interface and selector, your store is a todo array, not an application object
const TodoList: React.FC = () => { const todoSelector = (state: ApplicationState) => state.todos; const store = useSelector(todoSelector);
return (
<Wrapper>
{store.map((item) => (
<Todolement key={item.id} element={item}>
{item.name} {item.content}
</TodoElement>
))}
</Wrapper> ); };

Redux store updated but component is not updating

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.

React-Redux mapStateToProps doesn't trigger when state changes

I'm learning Redux state management and got stuck with an issue. The mapStateToProps within a component is not triggered when the state changes. Gone through a lot of blogs, couldn't able to figure out the problem.
The store seems to update properly, as the "subscribe" method on store prints new changes. Attached screenshot as well.
Actions.js
export const GET_ITEMS_SUCCESS = "GET_ITEMS_SUCCESS";
export const GET_ITEMS_FAILURE = "GET_ITEMS_FAILURE";
export const getItemsSuccess = (items) => ({
type: GET_ITEMS_SUCCESS, payload: items
});
export const getItemsFailure = (error) => ({
type: GET_ITEMS_FAILURE, error: error
});
export function getItems(dispatch) {
return dispatch => {
fetch(myList)
.then(res => res.json())
.then(res => {
if(res.error) {
throw(res.error);
}
store.dispatch(getItemsSuccess(res));
return res;
})
.catch(error => {
store.dispatch(getItemsFailure(error));
})
}
}
Reducer
let initialState = {items: [], error: null}
function GetItemsReducer (state = initialState, action) {
switch (action.type) {
case GET_ITEMS_SUCCESS:
return Object.assign({}, state, {pending: false, items: action.payload});
case GET_ITEMS_FAILURE:
return Object.assign({}, state, {pending: false, error: action.error});
default:
return state;
}
}
export default const rootReducer = combineReducers({
GetItemsReducer: GetItemsReducer
});
Store
const mystore = createStore(rootReducer, applyMiddleware(thunk, promise));
mystore.subscribe(() => console.log("State Changed;", mystore.getState()));
Component
class Home extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchItems();
}
render() {
return (
<div>{this.props.items.length}</div>
)
}
}
const mapStateToProps = (state) => {
console.log('mapStateToProps ----------> ', state);
return {
items: state.GetItemsReducer.items,
error: state.GetItemsReducer.error
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchItems: bindActionCreators(getItems, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Main
class App extends React.Component {
render() {
return (
<Provider store={mystore}>
<Home />
</Provider>
)
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
Thanks in advance.

How to send changed state to component in react-native?

I'm a beginner of react & react-native.
I'm using react 16, react-thunk, react-redux.
I'm trying to fetch categories that I already made from firestore.
At first, I called action using connect(), and then, I typed action using thunk also fetched data from firestore.
Finally, I returned new states in reducer.
Definitely, I'm not aware of redux process, so please give some tips.
Here's my code. Thank you.
CategoryImageList.js (Component)
...
class CategoryImageList extends Component {
componentWillMount() {
this.props.getCategory();
}
renderImages() {
return this.state.categories.map(category =>
<CategoryImageCard key={category.imgName} category={category}/>
);
}
render() {
return (
<ScrollView>
{/*{this.renderImages()}*/}
</ScrollView>
);
}
}
export default connect(null, {getCategory})(CategoryImageList);
category.js (action)
...
export const getCategory = () => {
return (dispatch) => { //using redux-thunk here... do check it out
getCategories()
.then(querySnapshot => {
const test = [];
querySnapshot.forEach((doc) => {
test.push(
{
imgName : doc.data()['imgName'],
name : doc.data()['name']
});
});
dispatch({ type: GET_CATEGORY, payload: test} );
});
};
};
CategoryReducers.js (reducer)
...
const categoryInitialState = {
name: [],
imgName: []
}
export const CategoryReducer = (state = categoryInitialState, action) => {
switch (action.type) {
case GET_CATEGORY:
console.log(action);
return { ...state, categoryImg: {
name: action.payload.name,
imgName: action.payload.imgName
}};
default:
return state;
}
}
App.js
...
type Props = {};
export default class App extends Component<Props> {
render() {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider store={store}>
<View style={{flex:1}}>
<Header headerText={'FoodUp'}/>
<CategoryImageList />
</View>
</Provider>
);
}
}
reducers/index.js
import { combineReducers } from 'redux';
import { CategoryReducer } from './CategoryReducer';
export default combineReducers({
categories: CategoryReducer
});
UPDATED
Firebase.js
const config = {
...
};
firebase.initializeApp(config);
const db = firebase.firestore();
const storage = firebase.storage();
const settings = {timestampsInSnapshots: true};
db.settings(settings);
export const getCategories = () => {
return db.collection('categories').get();
}
export const getCategoryImg = (categoryName, imgName) => {
const ref = storage.ref(`category/${categoryName}/${imgName}`);
return ref.getDownloadURL();
}
You have to add mapstateToProps to your connect like,
const mapStateToProps = (state: any) => {
return {
name: state.categories.name,
imageName:state.categories.imageName
};
}
export default connect(mapStateToProps)(CategoryImageList)
And then, you will be able to access the name and image name like,
this.props.name and this.props.imageName
Edit: To dispatch GET_CATEGORY you can either use mapDispatchToProps or do the getCategory and dispatch from within your component like,
import {getCategory} from './category'
componentWillMount() {
this.props.getCategory(this.props.dispatch);
}
and change the getCategory function as,
export const getCategory = (dispatch) => {
...
dispatch({ type: GET_CATEGORY, payload: test} );
...
}
mapStateToProps has the Store state as an argument/param (provided by react-redux::connect) and its used to link the component with the certain part of the store state. in your case, you can use like this. and you can use name, imgName as a props in your component
const mapStateToProps = ({categories}) => {
const { name, imgName } = categories;
return {name, imgName};
};
export default connect(mapStateToProps, {getCategory})(CategoryImageList);

Resources