Problem
Hello friends,
I'm developing an app with react + redux, the problem I have is that the series property that comes from
const mapStateToProps = (state) => {
return {
series: state.serieReducer
}
}
It is showing me undefined in console, but if you see the image in the action it loads the 30 data in the array.
I would like if you could help me to solve the problem and be able to load the data in the render(){} function.
src/components/Home/index.js
import React, {Component} from 'react';
import Page from './page.js';
import fetchSeriesAction from '../../redux/actions/series/action.fetch.js';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux'
class Home extends Component {
componentDidMount() {
this.props.fetchSeries();
}
render(){
console.log('TESTING SERIES LIST:', this.props) // show undefined
return(
<Page/>
)
}
}
const mapStateToProps = (state) =>{
return{
series: state.serieReducer
}
}
const mapDispatchToProps = dispatch => bindActionCreators({
fetchSeries: fetchSeriesAction
}, dispatch)
export default connect(mapStateToProps , mapDispatchToProps)(Home);
src/redux/actions/series
serie/action.error.js
import {FETCH_SERIES_ERROR} from '../../types.js';
const fetchSeriesError = (error) =>{
return {
type: FETCH_SERIES_ERROR,
error: error
}
}
export default fetchSeriesError;
series/action.pending.js
import {FETCH_SERIES_PENDING} from '../../types.js';
const fetchSeriesPending = () =>{
return {
type: FETCH_SERIES_PENDING
}
}
export default fetchSeriesPending;
series/action.success.js
import {FETCH_SERIES_SUCCESS} from '../../types.js';
const fetchSeriesSuccess = (series) =>{
return {
type: FETCH_SERIES_SUCCESS,
series: series
}
}
export default fetchSeriesSuccess;
series/action.fetch.js
import fetchSeriesPending from './action.pending.js';
import fetchSeriesSuccess from './action.sucess.js';
import fetchSeriesError from './action.error.js';
const fetchData = () =>{
return async dispatch => {
dispatch(fetchSeriesPending());
await fetch('https://cinemanight.chrismichael.now.sh/api/v1/series/1')
.then(res => res.json())
.then(res => {
if(res.error) {
throw(res.error);
}
dispatch(fetchSeriesSuccess(res.series));
return res.series;
})
.catch(error => {
dispatch(fetchSeriesError(error));
})
}
}
export default fetchData;
reducers/series
series/result.js
import {
FETCH_SERIES_ERROR,
FETCH_SERIES_PENDING,
FETCH_SERIES_SUCCESS
} from '../../types.js';
const defaultState = {
pending: true,
series: [],
error: null
}
const reducer = (state = defaultState, action) =>{
switch(action.type){
case FETCH_SERIES_PENDING:
return{
... state,
pending: true
}
case FETCH_SERIES_SUCCESS:
return{
... state,
pending: false,
series: action.payload
}
case FETCH_SERIES_ERROR:
return{
... state,
pending: false,
error: action.error
}
default:
return state;
}
}
export default reducer;
series/index.js
import resultReducer from './result.js';
export default {
resultReducer
}
store.js
import {createStore , combineReducers , applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import SeriesReducer from './reducers/series/index.js';
const middlewares = [thunk , logger];
const reducers = combineReducers({
... SeriesReducer
});
const store = createStore(reducers , applyMiddleware(... middlewares));
export default store;
You need to fix either action.success.js or your reducer because in reducer you are setting action.payload and you are sending series from your action.success.js
Either change your action.success.js to:
import {FETCH_SERIES_SUCCESS} from '../../types.js';
const fetchSeriesSuccess = (series) =>{
return {
type: FETCH_SERIES_SUCCESS,
payload: series
}
}
export default fetchSeriesSuccess;
or change your reducer like this:
import {
FETCH_SERIES_ERROR,
FETCH_SERIES_PENDING,
FETCH_SERIES_SUCCESS
} from '../../types.js';
const defaultState = {
pending: true,
series: [],
error: null
}
const reducer = (state = defaultState, action) =>{
switch(action.type){
case FETCH_SERIES_PENDING:
return{
... state,
pending: true
}
case FETCH_SERIES_SUCCESS:
return{
... state,
pending: false,
series: action.series
}
case FETCH_SERIES_ERROR:
return{
... state,
pending: false,
error: action.error
}
default:
return state;
}
}
export default reducer;
update your mapStateToProps method as well:
const mapStateToProps = (state) => {
return {
series: state.resultReducer
}
}
Related
I've got a sample website with redux(redux-saga,redux-next-wrapper).
I created all components of redux and configured them. But I got an error when dispatching in getStaticProps with title `TypeError: nextCallback is not a function. I will share my structures code and thanks to finding my problem.
Action code
import { NewsActionE } from "../../../enums/newsActionEnum"
import { FetchGetsRequest, FetchNewsSuccessPayload, FetchPostsFailure, FetchPostsFailurePayload, FetchPostsSuccess } from "../../../types/allNewsT"
export const requestNews = (): FetchGetsRequest => ({
type: NewsActionE.REQUESTNEWS
})
export const fetchNewsSuccess = (
payload: FetchNewsSuccessPayload
): FetchPostsSuccess => ({
type: NewsActionE.GETALLNEWS,
payload
});
export const ErrorNews = (
payload: FetchPostsFailurePayload
): FetchPostsFailure => ({
type: NewsActionE.Failure,
payload
});
reducers
import { HYDRATE } from "next-redux-wrapper";
import { AnyAction } from "redux";
import { NewsActionE } from "../../../enums/newsActionEnum"
export interface initialState {
pending: false
errors: null
articles: []
}
export const reducer = (state: initialState | undefined, action: AnyAction): initialState | any => {
switch (action.type) {
case HYDRATE:
// Attention! This will overwrite client state! Real apps should use proper reconciliation.
return { ...state, ...action.payload };
case NewsActionE.REQUESTNEWS:
return {
...state,
pending: true,
}
case NewsActionE.Failure:
return {
...state,
pending: false,
articles: [],
errors: action.payload.console.error
}
case NewsActionE.GETALLNEWS:
return {
...state,
pending: false,
articles: action.payload,
errors: null
}
default:
return { ...state };
}
}
sagas
import axios, { AxiosResponse } from "axios"
import http from '../../../services/httpService';
import { call, put, all, takeLatest } from 'redux-saga/effects'
import { News } from '../../../models/news/News'
import { ErrorNews, fetchNewsSuccess } from "../..";
import { NewsActionE } from "../../../enums/newsActionEnum";
const getNews = (): Promise<AxiosResponse<News, any>> =>
http.get<News>('/data/getallposts')
function* fetchNewsSaga(): any {
try {
const response = yield call(getNews);
yield put(fetchNewsSuccess({
news: response.data
}));
} catch (e) {
yield put(ErrorNews({
error: 'e.message'
}));
}
}
function* newsSaga() {
yield all([takeLatest(NewsActionE.REQUESTNEWS, fetchNewsSaga)])
}
export default newsSaga;
root saga
import { all, fork } from "#redux-saga/core/effects";
import newsSaga from "./news/newssaga";
export function* RootSaga() {
yield all([fork(newsSaga)])
}
store
import { AnyAction, applyMiddleware, createStore, Store } from "redux";
import reducers from "../reducer/news";
import createSagaMiddleware from "#redux-saga/core";
import { Task } from 'redux-saga'
import { RootSaga } from "../sagas/rootSaga";
import { Context, createWrapper } from "next-redux-wrapper";
import { initialState } from "../reducer/news/newsReducer";
import { RootState } from "..";
export interface SagaStore extends Store<initialState | any, AnyAction> {
sagaTask?: Task;
}
export const makeStore = (context: Context) => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducers,
applyMiddleware(sagaMiddleware));
(store as SagaStore).sagaTask = sagaMiddleware.run(RootSaga);
return store;
};
export const wrapper = createWrapper<Store<RootState>>(makeStore, { debug: true });
index.ts
import { END } from '#redux-saga/core'
import type { NextPage } from 'next'
import Layout from '../components/Layout'
import AllNews from '../components/news'
import { requestNews, wrapper } from '../state'
const Home: NextPage = () => {
return (
<Layout title="News site">
<p>News site Home page</p>
<AllNews />
</Layout>
)
}
export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
store.dispatch(requestNews());
// end the saga
store.dispatch(END);
await store.sagaTask.toProm();
})
export default Home
You are exporting your reducer as a named export, but import it as a default import. As a result, it will likely be undefined.
Do
import { reducer } from "../reducer/news";
instead of
import reducers from "../reducer/news";
I am pretty new to Redux and the whole Redux-Saga thing and wanted to use React-Boilerplate to try a small project that basically just makes an API call and iterates over the data. And I currently have a problem I've been stuck at for hours. Maybe you have an idea?
My React Component looks like this:
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';
import { useInjectSaga } from 'utils/injectSaga';
import { useInjectReducer } from 'utils/injectReducer';
import {
makeSelectDevices,
makeSelectLoading,
makeSelectError
} from './selectors';
import reducer from './reducer';
import { fetchDevices } from './actions';
import saga from './saga';
export function LeafletMap(props) {
const {devices, loading, error, fetchDevices } = props;
useInjectReducer({ key: 'leafletMap', reducer });
useInjectSaga({ key: 'leafletMap', saga });
useEffect(() => {
fetchDevices();
}, [fetchDevices]);
if (loading) return(<div>Loading...</div>)
return (
<div>
{ !error ?
<Map center={[47.3, 9.9]} zoom={9} style={{height: '500px'}}>
<TileLayer
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
attribution='© OpenStreetMap contributors'
/>
{ devices && devices.map((device)=> {
let coordinates = [device.latitude, device.longitude];
return (
<Marker key={device.id} position={coordinates}></Marker>
);
})}
</Map>
: ''
}
</div>
);
};
LeafletMap.propTypes = {
devices: PropTypes.array,
loading: PropTypes.bool,
error: PropTypes.any,
};
const mapStateToProps = createStructuredSelector({
devices: makeSelectDevices(),
loading: makeSelectLoading(),
error: makeSelectError(),
});
function mapDispatchToProps(dispatch) {
return {
fetchDevices: () => dispatch(fetchDevices())
};
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
export default compose(withConnect)(LeafletMap);
When my component mounts I use the useEffect Hook to dispatch an action that I bound to my props using mapDispatchToProps. The actions file looks like this:
import {
FETCH_DATA,
FETCH_DATA_ERROR,
FETCH_DATA_SUCCESS,
CLICK_DEVICE
} from './constants';
export function fetchDevices() {
return {
type: FETCH_DATA,
};
}
export function fetchDevicesSuccess(devices) {
return {
type: FETCH_DATA_SUCCESS,
devices
};
}
export function fetchDevicesError(error) {
return {
type: FETCH_DATA_ERROR,
error
};
}
My saga then reacts to the FETCH_DATA action and calls a generator to fetch the data from my local API:
import { all, call, put, takeEvery } from 'redux-saga/effects';
import request from 'utils/request';
import { fetchDevicesSuccess, fetchDevicesError } from './actions';
import { FETCH_DATA } from './constants';
function* fetchDevicesAsync() {
yield takeEvery(FETCH_DATA, fetchAllDevices);
}
function* fetchAllDevices() {
try {
const requestUrl = '/api/devices';
const devices = yield call(request, requestUrl);
yield put(fetchDevicesSuccess(devices));
} catch (error) {
yield put(fetchDevicesError(error.toString()));
}
}
export default function* rootSaga() {
yield all([fetchDevicesAsync()]);
}
This in return should trigger my reducer which looks as follows:
import produce from 'immer';
import {
FETCH_DATA,
FETCH_DATA_ERROR,
FETCH_DATA_SUCCESS,
} from './constants';
export const initialState = {
devices: [],
loading: true,
error: false,
};
/* eslint-disable default-case, no-param-reassign */
const leafletMapReducer = (state = initialState, action) =>
produce(state, () => {
switch (action.type) {
case FETCH_DATA:
state.loading = true;
state.error = false;
break;
case FETCH_DATA_ERROR:
state.loading = false
state.error = action.error;
break;
case FETCH_DATA_SUCCESS:
state.loading = false;
state.error = false;
state.devices = action.devices;
break;
}
});
export default leafletMapReducer;
My problem here is that everything seems to work but my action is neither being displayed in Redux DevTools nor does my component update after the initial render. It seems as if the action is being dispatched before the ##INIT event.
Any idea why this happens?
Thanks in advance!
EDIT:
Just in case it has something to do with my selectors:
import { createSelector } from 'reselect';
import { initialState } from './reducer';
/**
* Direct selector to the leafletMap state domain
*/
const selectLeafletMapDomain = state => state.leafletMap || initialState;
/**
* Other specific selectors
*/
const makeSelectDevices = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.devices
);
const makeSelectLoading = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.loading,
);
const makeSelectError = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.error,
);
/**
* Default selector used by LeafletMap
*/
const makeSelectLeafletMap = () =>
createSelector(selectLeafletMapDomain, leafletMapState => leafletMapState.toJS());
export default makeSelectLeafletMap;
export {
selectLeafletMapDomain,
makeSelectDevices,
makeSelectLoading,
makeSelectError
};
Found the problem myself :)
The problem was in my reducer:
const leafletMapReducer = (state = initialState, action) =>
produce(state, () => { // <-- here
switch (action.type) {
case FETCH_DATA:
state.loading = true;
state.error = false;
break;
I here wrongly mutated my state which leads to the error. The correct solution is:
const leafletMapReducer = (state = initialState, action) =>
produce(state, draftState => { // use draftState instead of normal state
switch (action.type) {
case FETCH_DATA:
draftState.loading = true; //<------
draftState.error = false; //<------
break;
**Hello! my problem is my state is not uploading, is always empty altough my actions brings data correct. Can anyone give me some help of what am I doing wrong ?
I think is something with the name or the combine reducers part.
Maybe I am not accesing data correct with my reducer or something like that **
The object I receive from the api call has this format {categories: Array(4), items: Array(50)}
Component
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ItemList from '../components/ItemList/ItemList';
import { getItems } from './actions'
class ItemListContainer extends PureComponent {
async componentDidMount() {
const { getItems } = this.props;
await getItems()
console.log(this.props)
}
render() {
return <ItemList />;
}
}
const mapStateToProps = state => (
{
items: state.items.items,
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
getItems,
},
dispatch,
);
export default connect(mapStateToProps, mapDispatchToProps)(ItemListContainer);
actions.js
export const GET_ITEMS = 'GET_ITEMS';
export const GET_ITEM = 'GET_ITEM';
export const GET_ITEM_DESCRIPTION = 'GET_ITEM_DESCRIPTION';
export function getItems(query) {
return async function (dispatch) {
// const res = await fetch(`http://localhost:3000/api/items?q=${query}`)
const res = await fetch(`http://localhost:3000/api/items?q=ipad`)
const items = await res.json()
return dispatch({
type: 'GET_ITEMS',
items: items.items,
})
}
}
reducer.js
import { GET_ITEMS } from './actions';
const initialState = {
items: [],
itemsLoaded: false,
};
export default function(state = initialState, action) {
const { type, data } = action;
switch (type) {
case GET_ITEMS:
return {
...state,
items: data,
itemsLoaded: true,
};
default: {
return {
...state
}
}
}
}
I was accessing { data} in the reducer which of course it was empty. The correnct action was items.
I'm making my first react-native app and I cant seem to bind my actions to props. In the component this.props.actions is an empty, and LoginActions is also an empty object in the mapDispatchToProps function. This leads me to believe its an issue in the action or the connect binding. Can anyone see where I'm going wrong?
Component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
View,
StyleSheet,
} from 'react-native';
import {
google,
facebook,
twitter,
} from 'react-native-simple-auth';
import LoginConstants from '../../constants/Login.constants';
import * as LoginActions from '../../actions/Login.actions';
import LoginForm from '../../components/LoginForm';
class Login extends Component {
constructor(props) {
super(props);
alert(JSON.stringify(this.props.actions))
this.loginActions = {
google,
facebook,
twitter,
};
this.loginAction = this.loginAction.bind(this);
}
loginAction(platform) {
alert(JSON.stringify(this.props.actions))
// this.loginActions[platform](LoginConstants[platform])
// .then((info) => {
// alert(info);
// // info.user - user details from the provider
// // info.credentials - tokens from the provider
// }).catch((error) => {
// throw Error(`Error ${error.code}: ${error.description}`);
// });
}
render() {
return (
<LoginForm actions={this.loginActions} loginAction={this.loginAction} />
);
}
}
Login.propTypes = {
actions: PropTypes.object.isRequired,
user: PropTypes.object
};
const styles = StyleSheet.create({
});
const mapStateToProps = (state) => {
return {
user: state.user
};
};
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(LoginActions, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Actions:
import LoginConstants from '../constants/Login.constants';
export function getUser(userId) {
return {
type: LoginConstants.actions.getUser,
payload: new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
userId: '123ddgs',
});
}, 2000);
});
};
};
export function saveUser(user) {
return {
type: LoginConstants.actions.saveUser,
payload: new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
userId: '123ddgs',
});
}, 2000);
})
};
};
Reducer:
import LoginConstants from '../constants/Login.constants';
const loginReducers = (state = {
user: {},
prevStates: []
}, action) => {
switch (action.type) {
case LoginConstants.actions.getUser:
state = {
...state,
user: action.payload,
prevStates: [...state.prevStates, action.payload]
};
break;
case LoginConstants.actions.saveUser:
state = {
...state,
user: action.payload,
prevStates: [...state.prevStates, action.payload]
};
break;
}
return state;
};
export default loginReducers;
Store:
import {
createStore,
combineReducers,
applyMiddleware,
} from 'redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise-middleware';
import { createLogger } from 'redux-logger';
import loginReducers from './src/reducers/Login.reducers';
import beerReducers from './src/reducers/Beer.reducers';
export default createStore(
combineReducers({
loginReducers,
beerReducers,
}),
{},
applyMiddleware(createLogger(), thunk, promise())
);
JSON.stringify strips functions from its output and therefore, the actions and dispatcher were not visible in the alert output.
I am new to react redux, and trying to make frineds search function with mock-api call(https://github.com/DerekCuevas/friend-list/blob/master/redux-saga-solution/api/index.js).
In this project, I am just getting stuck with props are not updated when redux-state update. When the fetch action dispatch, states is updated well. I can see successfully states update by redux dev tool, but there's just error "" at container even though I connect the container component with redux store by connect method. I appreciate if you guys have some idea to make updating props to container well.
here is my code.
https://github.com/gakuakachi/friends-list
containers/Main/index.js
const mapStateToProps = state => {
const { isFetching, friends, error } = state.friends;
return {
isFetching,
friends,
error
}
}
const mapDispatchToProps = dispatch => {
return {
fetchFriendsStart: friends => {
dispatch(Actions.fetchFriendsStart(friends))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps
)(Main);
containers/Main/reducer.js
import { combineReducers } from 'redux'
import * as ActionType from './constants';
import { fromJS } from 'immutable';
/*
*
* Main reducer
*
*/
const initialState = fromJS({
isFetching: false,
query: '',
friends: [],
error: ''
});
export default function friendsReducer( state = initialState, action) {
switch (action.type) {
case ActionType.FRIENDS_FETCH_START:
return state.set('isFetching', false);
case ActionType.FRIENDS_FETCH_SUCCESSED:
return state.merge({
isFetching: false,
friends: action.friends
});
case ActionType.FRIENDS_FETCH_FAILED:
return state.merge({
isFetching: false,
error: action.error
});
default:
return state;
}
}
containers/Main/sagas.js
import { fork, call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import * as Actions from './actions';
import * as ActionTypes from './constants';
import search from './service/api';
function* helloSaga() {
console.log('Hello Sagas!');
}
function* fetchFriendsSaga(action) {
try {
// console.log("this is withing fetchFriendsSaga" + " "+ action.param)
const friends = yield call(search, action.param);
yield put({type: ActionTypes.FRIENDS_FETCH_SUCCESSED, friends: friends });
}catch(e) {
yield put({type: ActionTypes.FRIENDS_FETCH_FAILED, error: e.message});
}
}
function* fetchFriendsWatherSaga() {
yield takeEvery(ActionTypes.FRIENDS_FETCH_START, fetchFriendsSaga);
}
function* rootSaga() {
yield [
fork(helloSaga),
fork(fetchFriendsWatherSaga)
];
}
export default rootSaga;
containers/Main/actions.js
import fetch from 'isomorphic-fetch'
import { Api } from './service/api';
import * as ActionType from './constants';
export function fetchFriendsStart(param) {
return {
type: ActionType.FRIENDS_FETCH_START,
param
}
}
export const receiveFriends = friends => {
return {
type: ActionType.FRIENDS_FETCH_SUCCESSED,
friends
}
}
export const friendsFetchFailed = error => {
return {
type: ActionType.FRIENDS_FETCH_FAILED,
error
}
}