**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.
Related
I'm new to React, Redux and have been following tutorials on the topic. I'm come across a lot of issues that I've been able to resolve but I've come across an issue I can't resolve. I set up store, and can even view it through Chrome's Redux Tools and it show correctly, however when I try and dispatch to the Store, I always get a Cannot read property 'dispatch' of undefined error. I have followed numerous tutorials letter for letter, and am still stuck with the same error message.
Index.Js
import Layout from '../components/layout/Layout';
import Home from '../components/Home';
import { getRooms } from '../redux/actions/roomActions';
import { wrapper } from '../redux/store';
export default function Index() {
return (
<Layout>
<Home />
</Layout>
);
}
export const getServerSideProps = wrapper.getServerSideProps(
async ({ req, store }) => {
await store.dispatch(getRooms(req));
}
);
roomConstants.js
export const ALL_ROOMS_SUCCESS = 'ALL_ROOMS_SUCCESS';
export const ALL_ROOMS_FAIL = 'ALL_ROOMS_FAIL';
export const CLEAR_ERRORS = 'CLEAR_ERRORS';
reducer.js
import { combineReducers } from 'redux';
import { allRoomsReducer } from './roomReducers';
const reducer = combineReducers({
allRooms: allRoomsReducer,
});
export default reducer;
Store.js
import { createStore, applyMiddleware } from 'redux';
import { HYDRATE, createWrapper } from 'next-redux-wrapper';
import thunkMiddleware from 'redux-thunk';
import reducers from './reducers/reducers';
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const reducer = (state, action) => {
if (action.type === HYDRATE) {
const nextState = {
...state,
...action.payload,
};
return nextState;
} else {
return reducers(state, action);
}
};
const initStore = () => {
return createStore(reducer, bindMiddleware([thunkMiddleware]));
};
export const wrapper = createWrapper(initStore);
roomReducer.js
import {
ALL_ROOMS_SUCCESS,
ALL_ROOMS_FAIL,
CLEAR_ERRORS,
} from '../constants/roomConstants';
// All rooms reducer
export const allRoomsReducer = (state = { rooms: [] }, action) => {
switch (action.type) {
case ALL_ROOMS_SUCCESS:
return {
roomsCount: action.payload.roomsCount,
resPerPage: action.payload.resPerPage,
filteredRoomsCount: action.payload.filteredRoomsCount,
rooms: action.payload.rooms,
};
case ALL_ROOMS_FAIL:
return {
error: action.payload,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
roomAcion.js
import axios from 'axios';
import absoluteUrl from 'next-absolute-url';
import {
ALL_ROOMS_SUCCESS,
ALL_ROOMS_FAIL,
CLEAR_ERRORS,
} from '../constants/roomConstants';
//Clear errors
export const clearErrors = () => async (dispatch) => {
return dispatch({
type: CLEAR_ERRORS,
});
};
// Get all rooms
export const getRooms = (req) => async (dispatch) => {
try {
const { origin } = absoluteUrl(req);
const { data } = await axios.get(`${origin}/api/rooms`);
dispatch({
type: ALL_ROOMS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: ALL_ROOMS_FAIL,
payload: error.response.data.message,
});
}
};
in index.js, your getServerSideProps function should read this;
export const getServerSideProps = wrapper.getServerSideProps((store) => async ({ req }) => {
await store.dispatch(getRooms(req));})
Use the old version of next-redux-wrapper like 6.0.2
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;
The problem is:
I'm trying to use redux-saga in my react app, but i still has this error: Actions must be plain objects. Use custom middleware for async actions. Code it seems correct but no idea why gives that error. I'll be glad for all the help. I'm fighting with it for about two days and still doesn't have a solution. I tried to look up, but I still have this error.
action...
import { GET_DISTRICTS} from '../../constants';
const getAdres = async (url) => {
let response = await fetch(url);
let data = await response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
return list;
};
export const actions = {
handleGetDistrictsData: async () => {
let districts = await getAdres(`url is here`);
return {
type: GET_DISTRICTS,
payload: districts
};
},
reducer...
import { GET_DISTRICTS } from '../../constants';
export const initialState = {
districts: [],
quarters: [],
streets: [],
doors: [],
districtSelected: false,
districtSelectedID: null,
quarterSelected: false,
quarterSelectedID: null,
streetSelected: false,
streetSelectedID: null,
doorSelected: false,
doorSelectedID: null
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_DISTRICTS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};
component...
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actions as addressActions } from '../../../../redux/actions/address';
import Select from 'react-select';
const Districts = (props) => {
let [ fetchedData, setFetchedData ] = useState(false);
useEffect(() => {
props.handleGetDistrictsData();
setFetchedData(true);
});
return (
<React.Fragment>
<Select
name='adresSelect'
options={props.address.districts}
onChange={props.handleDistrictChange}
placeholder='Please Select'
/>
</React.Fragment>
);
};
const mapStateToProps = (state) => ({
address: state.address
});
const mapDispatchToProps = function(dispatch) {
return bindActionCreators({ ...addressActions }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(Districts);
-------------
import React from 'react';
import Districts from './Districts';
const AddressSearchWidget = (props) => {
return (
<React.Fragment>
<Districts />
</React.Fragment>
);
};
export default AddressSearchWidget
store...
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas/index';
import * as reducers from './';
export function initStore() {
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers(reducers);
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, composeEnhancer(applyMiddleware(sagaMiddleware)));
// Run sagas
sagaMiddleware.run(rootSaga);
return store;
}
handleGetDistrictsData returns a promise (all async functions return promises). You cannot dispatch a promise in plain redux saga, and redux-saga does not change this. Instead, dispatch a normal action, and have that action run a saga. The saga can then do async things, and when it's done dispatch another action. The reducer listens only for that second action.
// Actions:
export const getDistrictsData = () => ({
type: GET_DISTRICTS,
})
export const districtsDataSuccess = (districts) => ({
type: DISTRICTS_DATA_SUCCESS,
payload: districts
})
// Sagas:
export function* watchGetDistricts () {
takeEvery(GET_DISTRICTS, getDistricts);
}
function* getDistricts() {
let response = yield fetch(url);
let data = yield response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
yield put(districtsDataSuccess(list));
}
// reducer:
export default (state = initialState, action) => {
switch (action.type) {
case DISTRICTS_DATA_SUCCESS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};
I'm dispatching the action from a non-component JS (utils/index.js) file using store.dispatch(), but after the reducer state change, the component in which that reducer state is connected using mapStateToProps is not being re-rendered,
What am i doing wrong?
Any help would be appreciated
Thanks in advance
I tried searching through out and i went to all the questions and articles which are dispatching the action from a component file.
All the related files
/store/index.js
import { createStore } from "redux";
import rootReducer from "reducers";
store = createStore(rootReducer);
/utils/index.js
import store from './store';
import { toggleShowAlert } from "reducers/alert";
export const customAlert = msg => {
store.dispatch(toggleShowAlert(msg));
}
/views/Nav/index.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Nav extends Component {
render() {
....
// component code
....
}
}
const mapStateToProps = state => {
return {
showAlert: state.alert.showAlert
}
}
export default connect(mapStateToProps)(Nav);
/reducer/alert.js
const TOGGLE_SHOW_ALERT = "TOGGLE_SHOW_ALERT";
export const toggleShowAlert = msg => {
return dispatch => {
dispatch({
type: TOGGLE_SHOW_ALERT,
msg
});
};
};
const initialState = {
showAlert: false,
alertMsg: ""
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_SHOW_ALERT:
return {
...state,
showAlert: !state.showAlert,
alertMsg: state.showAlert ? "" : action.msg
};
default:
return { ...state };
}
}
export default reducer;
another reducer from which that customAlert is being called
/reducers/user.js
import { customAlert } from 'utils';
...other codes...
export const updateAgeGender = (userId, age, gender) => {
return dispatch => {
dispatch(updateButtonLoading());
callAPI("post", `${COMM_BE_URL}/users/update-age-gender`, {
userId,
age,
gender
})
.then(res => {
if (res.status === "SUCCESS") {
dispatch(updateAgeGenderAction(age, gender));
customAlert("Your info has been updated, now u can join");
}
})
.catch(err => {
console.log(err);
dispatch(updateError(SERVER_ERROR_MSG));
});
};
};
After change of showAlert, the Nav Component should re-render, but it's not happening
I'm trying to make an api call in action with axios and pass the results of it to the reducer.
Though action is triggered, reducer isn't. And I can't understand why.
Here's the component that should make api call before mounting
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
//actions
import { getPost } from '../actions/';
class PostShow extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
getPost(this.props.params.id);
}
render() {
console.log(this.props.activePost);
return (
<div>
<h1> hello from a post</h1>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
activePost: state.posts.activePost
}
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
getPost
}, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(PostShow);
Here's my action
import axios from 'axios';
import { FETCH_POSTS, SEND_POST, FETCH_POST } from './types';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=qwerty';
export function fetchPosts() {
const req = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: req
}
}
export function sendPost(props) {
const req = axios.post(`${ROOT_URL}/posts${API_KEY}`, props);
return {
type: SEND_POST,
payload: req
}
}
export function getPost(id) {
console.log('action triggered');
const req = axios.get(`${ROOT_URL}/posts/${id}${API_KEY}`);
return {
type: FETCH_POST,
payload: req
}
}
And here's my reducer
import { FETCH_POSTS, FETCH_POST } from '../actions/types';
const INITIAL_STATE = {
allPosts: [],
activePost: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
allPosts: action.payload.data
};
case FETCH_POST:
console.log('reducer triggered');
return {
...state,
activePost: action.payload.data
};
default:
return state;
}
}
As a result I see 'action triggered' coming from console.log in action, and null coming from console.log in component, and no console.log from the reducer, so it's not triggered and I have no data to render in my component.
Though I make a request and get a response from server with the data, it doesn't go to the reducer. (moreover case FETCH_POSTS works fine and I can render a list of posts, but not a particular one).
"axios": "^0.17.0"
"redux-promise": "^0.5.3"
You need to use this.props.getPost in componentDidMount instead of getPost.
Connect sends bound action creator to component as a prop