I am trying to figure out why Redux-Devtools does not show state change because it shows that action={
type: 'GET_LOCATION_SUCCESS',
payload: {}
}, while debugger and console.log() shows that action.payload is n.
I am using
redux-observable
rxjs
typesafe-actions
This is my action creator:
import { createAsyncAction } from 'typesafe-actions';
import { GET_LOCATION_BEGIN, GET_LOCATION_FAILURE, GET_LOCATION_SUCCESS } from './constants';
export const getMyLocation = createAsyncAction(
GET_LOCATION_BEGIN,
GET_LOCATION_SUCCESS,
GET_LOCATION_FAILURE
)<undefined, Position, string>();
This is my reducer:
import { RootAction } from 'MyTypes';
import { combineReducers } from 'redux';
import { getType } from 'typesafe-actions';
import {getMyLocation} from '../actions/locationActions';
export type locationState = {
position: Position;
error: boolean;
};
export const locationReducer = combineReducers<locationState, RootAction>({
position: (state = {} as Position, action) => {
switch(action.type){
case getType(getMyLocation.success):
return action.payload;
default:
return state;
}
},
error: (state = false, action) => {
switch(action.type){
case getType(getMyLocation.failure):
return true;
default:
return state;
}
}
})
And this is my epic: [EDIT v.1]
export const getMyLocationEpic: Epic<RootAction, RootAction, RootState, Services> = (action$, state$, {geoLocation}) =>
action$.pipe(
filter(isActionOf(getMyLocation.request)),
switchMap( () => geoLocation.getGeoLocation(options).pipe(
take(1),
mergeMap( (data : Position) => of(
// Dispatch action with my location data and then dispatch action to request weather from API. It runs fetchWeatherEpic
getMyLocation.success(data),
fetchWeather.request(data)
)
),
catchError(err => of(getMyLocation.failure(err)))
)
)
);
I am wondering what might be the reason of not showing state update, it works as it should in console.log() but it doesn't work in devtools.
Related
Using react-redux to get items from my database. My reducer is receiving action.type but not action.payload from action. As shown in the redux developer tool here: The response from my database api is working and I have already applied my redux-thunk into my store using applyMiddleware.
Home.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { getAvailableItems } from '../Redux/Actions/ItemAction'
componentDidMount() {
this.props.getAvailableItems()
this.props.fetchPosts()
}
render() {
console.log(this.props)
return (<div></div>)
}
const mapStateToProps = state => {
return {
item : state.item.items
}
}
const mapActionsToProps = {
getAvailableItems,
fetchPosts
}
export default connect(mapStateToProps, mapActionsToProps)(Home)
ItemAction.js
export const getAvailableItems = () => dispatch => {
console.log("running itemAction")
fetch('https://us-central1-firebaselink.cloudfunctions.net/api/items')
.then(
(res) => {
//console.log(res.json())
res.json()
})
.then(data => dispatch(
{
type : 'GET_ALL_AVAILABLE_ITEMS',
payload : data
//console.log(data)
}
))
.catch((err) => console.log(err));
}
itemReducer.jsx
const initState = {
items : []
}
const itemReducers = (state = initState, action) => {
//return state;
console.log(action.type)
switch(action.type){
case 'GET_ALL_AVAILABLE_ITEMS':
console.log("this is the payload : "+action.payload)
return{
...state,
items: action.payload
}
default:
return state;
}
}
export default itemReducers;
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;
please help me with a situation around react-redux.
I have issues in updating the state ( which i try to do in a immutable way ), and the component where I use it, never rerenders.
/store/users/index.js
import { USER_LOGIN, USER_LOGOUT} from './actionTypes';
import {
USER_LOGIN,
USER_LOGOUT
} from './actionTypes';
const usersReducer = (user = {}, action) => {
switch(action) {
case USER_LOGIN : /* tried to change id manually, not based on payload, just to see if it works */
return {
...user,
name: 'New user',
isLoggedIn: true
}
case USER_LOGOUT:
return {
...user,
name: 'Anonymous',
isLoggedIn: false
}
default:
return user;
}
}
export default usersReducer;
/store/loops/index.js
import {
LOOPS_ADD
} from './actionTypes';
const loopsReducer = (loops =[], action) => {
switch(action) {
case LOOPS_ADD:
return [
...loops,
action.payload
]
default:
return loops;
}
}
export default loopsReducer;
/store/users/actions.js
import {
USER_LOGIN,
USER_LOGOUT
}
from './actionTypes';
export const userLogin = (newUser) => {
return {
type: USER_LOGIN,
}
}
export const userLogout = () => {
return {
type: USER_LOGOUT
}
}
/store/index.js
import {
createStore,
combineReducers,
applyMiddleware,
compose
} from 'redux';
/* import reducers */
import usersReducer from './users';
import loopsReducer from './loops';
/* import middleware */
import logger from 'redux-logger';
/* initial state */
const initialState = {
user: {
name: 'Anonymous',
isLoggedIn: false,
email: null,
lastLogin: null,
firstTimeLogin: false,
authProvider: 'email',
friendsCount: null
},
loops: []
}
/* reducers */
const rootReducer = combineReducers({
user: usersReducer,
loops: loopsReducer,
});
/* store creation */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const middleware = composeEnhancers(applyMiddleware(logger));
const store = createStore(
rootReducer,
initialState,
middleware
);
export default store;
/pages/HomeScreen/HomeScreen.js
import React from 'react';
import {connect} from 'react-redux'
import {userLogin} from '../../store/users/actions';
class Home extends React.PureComponent {
render() {
return (
<React.Fragment>
<NavBar>Nav</NavBar>
<Listing>
<Filter>Filter</Filter>
<Card>
<CardAvatar>Avatar</CardAvatar>
<CardBody>Rest of the card</CardBody>
Salut {this.props.name}
<button onClick={() => this.props.login()}>login</button>
</Card>
</Listing>
</React.Fragment>
)
}
}
const mapStateToProps = (state) => {
return {
name: state.user.name
}
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(userLogin()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
I don't know if it matters but i'm using styled-components for styling.
I tried removing the combineReducers method, and remain with a single reducer, that did not help.
I tried removing the react-logger middleware, I tried using Immutable.jsbut even if the actions is triggered the redux state does not update, and the Home component doesn't rerender as well.
It seems you are not setting the user on your action:
export const userLogin = (newUser) => {
return {
type: USER_LOGIN,
}
}
Looks like it needs to be:
export const userLogin = (newUser) => {
return {
type: USER_LOGIN,
user: newUser
}
}
Without this there will be no change in state and also no render needed.
Of course, you would then need to change your reducer function so that it is dynamic:
case USER_LOGIN :
return {
...user,
name: action.user.name,
isLoggedIn: true
}
The answer was simple.
I was doing :
const loopsReducer = (loops =[], action) => {
switch(action) {
case LOOPS_ADD:
return [
...loops,
action.payload
]
default:
return loops;
}
}
instead i should have done
const loopsReducer = (loops =[], action) => {
switch(action.type) {
case LOOPS_ADD:
return [
...loops,
action.payload
]
default:
return loops;
}
}
Notice the switch(action) before to switch(action.type)
Sorry for wasting your time, and thank you for all your replies !
I am fairly new to redux, and I am running into a problem.
I am trying to implement flash messages to my login page, but redux's dispatch is not changing the UI State.
I want a flash message to appear on the login page after user successfully register.
//login.js
class Login extends Component{
renderMessage() {
if (this.props.flashMessageType== "registrationComplete"){
return (
<Message
style={{textAlign: "left"}}
success
icon="check circle"
header="Account Registration was Successful"
list={["You must verify your email before logging in"]}
/>
);
} else {
return (null);
}
}
render() {
return ({
this.renderMessage()
});
}
}
function mapStateToProps(state) {
return {
flashMessageType:state.flashMessage.flashType,
};
}
export default connect(mapStateToProps, actions)(Login);
Here is the reducer
const initialState = {
flashType: "",
};
export default function(state = {initialState}, action){
switch(action.type){
case USER_REGISTER:
return [
...state,
{
flashType:"registrationComplete"
}
];
default:
return initialState;
}
}
and here is the actions
export const submitForm = (values,history) => async dispatch => {
const res = await axios.post('/api/signup', values);
history.push('/');
dispatch({type: FETCH_USER, payload: res.data});
dispatch({type: USER_REGISTER});
};
I appreciate your help.
Thanks,
Vincent
As Amr Aly mentioned (and now soroush), you're essentially mutating the state when you do:
return[ ...state, { flashType:"registrationComplete" }]
What you really want is:
return { ...state, flashMessage: "registrationComplete" }
Also, some of your code is a bit redundant and/or missing some important instructions (like try/catch blocks).
What your code should look like:
FlashMessage.js
import React, { PureComponent } from 'react';
import Message from '../some/other/directory';
import actions from '../some/oter/directory':
class Login extends PureComponent {
render = () => (
this.props.flashMessage == "registrationComplete"
? <Message
style={{textAlign: "left"}}
success
icon="check circle"
header="Account Registration was Successful"
list={["You must verify your email before logging in"]}
/>
: null
)
}
export default connect(state => ({ flashMessage: state.auth.flashMessage }), actions)(Login)
reducers.js
import { routerReducer as routing } from 'react-router-redux';
import { combineReducers } from 'redux';
import { FETCH_USER, USER_REGISTER } from '../actions/types';
const authReducer = (state={}, ({ type, payload }) => {
switch(type){
case FETCH_USER: return { ...state, loggedinUser: payload };
case USER_REGISTER: return { ...state, flashMessage: "registrationComplete" }
default: return state;
}
}
export default = combineReducers({
auth: authReducer,
routing
});
actions.js
import { FETCH_USER, USER_REGISTER } from './types';
export const submitForm = (values,history) => async dispatch => {
try {
const {data} = await axios.post('/api/signup',values);
dispatch({ type:FETCH_USER, payload: data });
dispatch({ type:USER_REGISTER });
history.push('/');
catch (err) {
console.error("Error: ", err.toString());
}
};
Your reducer should be:
const initialState = {
flashType: "",
};
export default function(state = initialState, action){
switch(action.type){
case USER_REGISTER:
return {
...state,
flashType: "registrationComplete",
};
default:
return state;
}
}
I have read through 100's of these threads on here, and I can't seem to understand why my component isn't updating. I am pretty sure it has something to do with the Immutability, but I can't figure it out.
The call is being made, and is returning from the server. The state is changing (based on the redux-Dev-Tools that I have installed).I have made sure to not mutate the state in any instance, but the symptoms seem to point that direction.
Code Sandbox of whole app https://codesandbox.io/s/rl7n2pmpj4
Here is the component.
class RetailLocationSelector extends Component {
componentWillMount() {
this.getData();
}
getData = () => {
this.props.getRetailLocations()
}
render() {
const {data, loading} = this.props;
return (
<div>
{loading
? <LinearProgress/>
: null}
<DefaultSelector
options={data}
placeholder="Retail Location"/>
</div>
);
}
}
function mapStateToProps(state) {
return {
loading: state.retaillocations.loading,
data: state.retaillocations.data,
osv: state.osv};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
getRetailLocations,
selectRetailLocation,
nextStep
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(RetailLocationSelector);
And here is my reducer :
import {REQUEST_RETAIL_LOCATIONS, SUCCESS_RETAIL_LOCATIONS,
ERR_RETAIL_LOCATIONS, SELECT_RETAIL_LOCATION} from
'../actions/RetailLocationsAction'
const initialState = {
data: [],
loading: false,
success: true,
selectedRetailLocation: undefined
}
function retailLocation(state = initialState, action) {
switch (action.type) {
case REQUEST_RETAIL_LOCATIONS:
return Object.assign({}, state, {
loading: true
}, {success: true})
case SUCCESS_RETAIL_LOCATIONS:
return Object.assign({}, state, {
loading: false
}, {
success: true
}, {
data: Object.assign([], action.payload.data)
})
case ERR_RETAIL_LOCATIONS:
return Object.assign({}, state, {
loading: false
}, {
success: false
}, {errorMsg: action.payload.message})
case SELECT_RETAIL_LOCATION:
return Object.assign({}, state, {
selectedRetailLocation: state
.data
.find((rec) => {
return rec.id === action.payload.id
})
})
default:
return state;
}
}
export default retailLocation
And finally, my Action file:
import axios from 'axios';
//import {api} from './APIURL'
export const REQUEST_RETAIL_LOCATIONS = 'REQUEST_RETAIL_LOCATIONS'
export const SUCCESS_RETAIL_LOCATIONS = 'SUCCESS_RETAIL_LOCATIONS'
export const ERR_RETAIL_LOCATIONS = 'ERR_RETAIL_LOCATIONS'
export const SELECT_RETAIL_LOCATION = 'SELECT_RETAIL_LOCATION'
const URL = 'localhost/api/v1/retail/locations?BusStatus=O&LocType=C'
export const getRetailLocations = () => (dispatch) => {
dispatch({ type: 'REQUEST_RETAIL_LOCATIONS' });
return axios.get(URL)
.then(data => dispatch({ type: 'SUCCESS_RETAIL_LOCATIONS', payload: data }))
.catch(error => dispatch({type : 'ERR_RETAIL_LOCATIONS', payload: error}));
}
Combined Reducer
import { combineReducers } from "redux";
import retailLocations from './RetailLocationsReducer'
import vendors from './VendorsReducer'
import receiptInformation from './ReceiptInfoReducer'
import osv from './OSVReducer'
import receiptDetail from './ReceiptDetailReducer'
const allReducers = combineReducers({
retaillocations: retailLocations,
vendors: vendors,
receiptInformation: receiptInformation,
receiptDetail: receiptDetail,
osv: osv
});
export default allReducers;
This answer doesn't solve your issue totally but provides some hints about what is not working. The broken part is your store definition. I don't have much experience with redux-devtools-extension or redux-batched-subscribe but if you define your store like that your thunk function works:
const store = createStore(reducer, applyMiddleware(thunk));
One of the configuration for the mentioned packages above brokes your thunk middleware.