I am making a mobile app by React Native and Redux.
It seems that my actions, state are working properly because I did console.log them.
The issue here is that my reducer is not called.
I did log it on the console but there is no result.
I don't know what is causing this.
I try to fetch API data in Market.js
I'd really appreciated if anyone helps me.
my github repo : https://github.com/sj602/invescoin
code as following:
App.js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { Stacks } from './utils/Navigation';
import {
StyleSheet, Text, View,
} from 'react-native';
import { store } from './store';
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Stacks />
</Provider>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
store.js
import {
createStore,
applyMiddleware,
compose
} from 'redux';
import promise from 'redux-promise';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import reducer from '../reducers';
// const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export const store = createStore(
reducer,
// composeEnhancers(
applyMiddleware(thunk)
// )
);
reducer.js
import { combineReducers } from 'redux';
import {
GET_MARKET_CAP,
GET_MARKET_CAP_SUCCESS,
GET_MARKET_CAP_FAIL,
GET_GLOBAL_INFO,
} from '../actions';
const initialState = {
isFetching: null,
data: '',
hasError: false,
errorMessage: null,
}
export default function reducer(state = initialState, action) {
switch (action.type) {
case GET_MARKET_CAP:
console.log(2)
return ;
case GET_MARKET_CAP_SUCCESS:
console.log('success')
case GET_MARKET_CAP_FAIL:
return {
...state,
marketCap: action.err
}
case GET_GLOBAL_INFO:
console.log('action')
return {
...state,
bitcoinPercentage: action.bitcoinPercentage
}
default:
return state;
}
}
actions.js
import {
GET_MARKET_CAP,
GET_MARKET_CAP_SUCCESS,
GET_MARKET_CAP_FAIL,
GET_GLOBAL_INFO
} from './types.js';
import * as api from '../utils/api';
export const getMarketCap = (coin, currency) => dispatch => {
return api.getMarketCap(coin, currency)
.then(data => {
dispatch({type: GET_MARKET_CAP_SUCCESS, data})
})
.catch(err => {
dispatch({type: GET_MARKET_CAP_FAIL, err})
})
}
export function getGlobalInfo() {
return dispatch => {
return api.getGlobalInfo().then(data => {
dispatch({type: GET_GLOBAL_INFO, data})
})
}
}
types.js
export const GET_MARKET_CAP = 'GET_MARKET_CAP';
export const GET_MARKET_CAP_SUCCESS = 'GET_MARKET_CAP_SUCCESS';
export const GET_MARKET_CAP_FAIL = 'GET_MARKET_CAP_FAIL';
export const GET_GLOBAL_INFO = 'GET_GLOBAL_INFO';
export const GET_COIN_PRICE = 'GET_COIN_PRICE';
Market.js
import React, { Component } from 'react';
import {
View, Text, TouchableOpacity,
ScrollView, TextInput
} from 'react-native';
import { connect } from 'react-redux';
import {
getMarketCap,
getGlobalInfo,
} from '../actions/index';
import * as api from '../utils/api';
import { cryptoList } from '../utils/cryptoList';
class Market extends Component {
componentDidMount() {
this.props.getMarketCap('bitcoin', 'KRW').then(data => data[0]['market_cap_krw'])
;
this.props.getGlobalInfo();
}
render() {
return (
<View>
<Text>
{this.props.market}
</Text>
<Text>
{this.props.bitcoinPercentage}
</Text>
</View>
)
}
}
const mapStateToProps = (state) => {
return {
market: state.market,
bitcoinPercentage: state.bitcoinPercentage
}
}
export default connect(mapStateToProps, {
getMarketCap,
getGlobalInfo
})(Market)
api.js
const Coinmarketcap_URL = `https://api.coinmarketcap.com/v1/`;
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authentication': 'c23R30cm2jOOExyAsG6pf5Xxy4QwpndxaIMRs6aOZxIQoUlMVOv1tCQZL3jZz'
};
export const getMarketCap = (coin, currency) => {
return fetch(
`${Coinmarketcap_URL}ticker/${coin}/?convert=${currency}`,
{
method: 'GET',
headers,
}
)
.then(res => res.json())
.catch(e => console.log('Error occurred : ', e))
}
export const getGlobalInfo = () => {
return fetch(
`${Coinmarketcap_URL}global/`,
{
method: 'GET',
headers,
}
)
.then(res => res.json())
.catch(e => console.log('Error occurred : ', e))
}
I think the issue is in reducer.js L7:
import { combineReducers } from 'redux';
import {
GET_MARKET_CAP,
GET_MARKET_CAP_SUCCESS,
GET_MARKET_CAP_FAIL,
GET_GLOBAL_INFO,
} from '../actions'; // <--- ../actions/index.js doesn't export these.
When you import from ../actions, the exports are expected to be in the index.js file that is in that folder. The filenames you used to describe your issue don't match the actual filenames in your repository.
Instead, try import { ... } from ../actions/types since types.js is the file that actually exports those consts.
Hope that helps!
I think the problem is in market.js file
import { bindActionCreators } from "redux"
function mapDispatchToProps(dipatch) {
return bindActionCreators({
getMarketCap,
getGlobalInfo
}, dispatch)
}
export default connect(mapStateToProps,mapDispatchToProps)(Market)
Now it should work , you missed binding your function with dispatch
Sorry, but I couldn't help but notice that is your store.js there is no initial state for the reducers. In the react docs for the create store method:
createStore(reducer, [preloadedState], [enhancer])
Can you make an initial State and see if that works. So your code might look similar to this;
import {
createStore,
applyMiddleware,
compose
} from 'redux';
import promise from 'redux-promise';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import reducer from '../reducers';
initialState = {Initial State of Reducers}
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export const store = createStore(
reducer,
intialState,
composeEnhancers(
applyMiddleware(thunk)
)
);
Related
I have a simple react app that fetches data from a JSON in my local and updates the state. The problem I face is that my reducer is not being called when I dispatch an action. I can see dispatch getting the response from the JSON but state is not updated
I did a console.log on reducer but no luck. I checked the Redux devtools but I don't see the state updated. Here is my code,
store.js
import {createStore, compose, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {routerMiddleware} from 'react-router-redux'
import rootReducer from './combined.reducer'
import {createLogger} from 'redux-logger'
let middlewares = [
thunk
]
const logger = createLogger();
if (process.env.Node_ENV !== "production" && process.env.Node_ENV !== "test") {
middlewares = [
...middlewares,
logger,
require("redux-immutable-state-invariant").default()
]
}
export default function configureStore(history) {
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const enhancer = composeEnhancers(
applyMiddleware(...middlewares, routerMiddleware(history))
);
const store = createStore(
rootReducer(history),
enhancer
)
if (process.env.Node_ENV !== "production" && process.env.Node_ENV !== "test" && module.hot) {
module.hot.accept("./combined.reducer", () => {
const nextReducer = require("./combined.reducer").default
store.replaceReducer(nextReducer)
})
}
return store
combined.reducer.js
import { connectRouter } from 'connected-react-router';
import { combineReducers } from 'redux';
import * as workoutReducer from './features/WorkoutList/Containers/Workout.Reducer'
const createAppReducer = (history) => combineReducers({
router: connectRouter(history),
workoutReducer: workoutReducer
})
const rootReducer = (history) => (state, action) => {
return createAppReducer(history)(state,action)
};
export default rootReducer;
folder structure:
src
features
Workout
Components
Containers
Workout.Actions.js
Workout.Constants.js
Workout.Container.js
Workout.Reducer.js
Workout.js
Workout.Constants.js:
const constants = {
GET_WORKOUT_LIST_REQUEST: "GET_WORKOUT_LIST_REQUEST",
GET_WORKOUT_LIST_SUCCESS: "GET_WORKOUT_LIST_SUCCESS",
GET_WORKOUT_LIST_FAILURE: "GET_WORKOUT_LIST_FAILURE"
}
export default constants
Workout.Container.js:
import { connect } from 'react-redux'
import {withRouter} from 'react-router'
import {bindActionCreators} from 'redux'
import Workout from './Workout'
import * as workoutAction from './Workout.Actions'
export default withRouter(connect(
(state) => ({
workoutListData: state.workoutListData
}),
(dispatch) => ({
workoutActions:bindActionCreators(workoutAction, dispatch)
})
)(Workout))
Workout.Actions.js:
import constants from "./Workout.Constants"
import {getConfigProperty} from "../../../settings"
import {makeGetCall} from "../../../utils/Api"
const WORKOUT_LIST = getConfigProperty("workoutList")
export function getWorkoutList(url = WORKOUT_LIST) {
return (dispatch, getState) => {
dispatch(getWorkoutListRequest(true))
return makeGetCall(url, getState)
.then(res => res.json())
.then(json => {
dispatch(getWorkoutListSuccess(json))
dispatch(getWorkoutListRequest(false))
})
.catch(ex => {
dispatch(getWorkoutListFailure(ex))
dispatch(getWorkoutListRequest(false))
})
}
}
export function getWorkoutListRequest(req) {
return {
type: constants.GET_WORKOUT_LIST_REQUEST,
req
}
}
export function getWorkoutListSuccess(response) {
return {
type: constants.GET_WORKOUT_LIST_SUCCESS,
response
}
}
export function getWorkoutListFailure(exception) {
return {
type: constants.GET_WORKOUT_LIST_FAILURE,
exception
}
}
Workout.Reducer.js
import constants from "./Workout.Constants"
const initialState = {
workoutListData :{}
}
function reducer(state = initialState, action) {
console.log(".................I'm here................")
switch(action.type) {
case constants.GET_WORKOUT_LIST_SUCCESS: {
return Object.assign({}, state, {
workoutListData: action.response.GET_LIST_DATA
})
}
case constants.GET_WORKOUT_LIST_FAILURE: {
return Object.assign({}, state, initialState)
}
default:
return state
}
}
export {initialState}
export default reducer
workout.js:
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {WorkoutListItem} from './../StyledComponents/WorkoutListItem'
import {isEmpty, isEqual} from 'lodash'
import {getConfigProperty} from "../../../settings"
const ROWS_PER_PAGE = getConfigProperty("rowsPerPage")
export default class Workout extends Component {
static get PropTypes() {
return {
workoutListData: PropTypes.array.isRequired
}
}
constructor(props){
super(props)
this.state = {
workoutListData: []
}
}
componentWillMount(){
if(isEmpty(this.props.workoutListData)){
this.props.workoutActions.getWorkoutList()
}
}
loadWorkoutListItem = () => {
return <WorkoutListItem / >
}
render() {
return (
<div>
{this.loadWorkoutListItem()}
</div>)
}
}
The reducer function should have been called. I don't even see the console log printed
I don't think you want
import * as workoutReducer from './features/WorkoutList/Containers/Workout.Reducer'
but just
import workoutReducer from './features/WorkoutList/Containers/Workout.Reducer'
The first is importing the initialState and the reducer as a combined object, as opposed to just importing the reducer.
EDIT: this is in combined.reducer.js
I am trying to refresh a react component state based on the props.
I have this file which is the main a child component for a screen:
RoomsList.js
import React from 'react';
import { View, ActivityIndicator, StyleSheet } from 'react-native';
import {connect} from "react-redux";
import {getRooms} from "../../store/actions";
import RoomIcon from "../RoomIcon/RoomIcon";
class RoomList extends React.Component {
componentDidMount() {
this.props.onGetRooms();
}
renderRooms() {
return this.props.rooms.map(room => {
return (
<RoomIcon key={room.id} room={room} />
)
});
}
render() {
return (
<View style={styles.container}>
{ this.props.rooms.length ? this.renderRooms() : <ActivityIndicator /> }
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
width: '100%',
justifyContent: 'space-between',
flexWrap: 'wrap',
}
});
const mapStateToProps = state => {
return {
rooms: state.rooms.rooms
}
};
const mapDispatchToProps = dispatch => {
return {
onGetRooms: () => dispatch(getRooms())
}
};
export default connect(mapStateToProps, mapDispatchToProps)(RoomList);
Rooms Reducer
import { SET_ROOMS } from '../actions/actionTypes';
const initialState = {
rooms: []
};
const roomsReducer = (state = initialState, action) => {
switch (action.type) {
case SET_ROOMS:
return {
...state,
rooms: action.rooms
};
default:
return state;
}
};
export default roomsReducer;
When the state is getting updated within the mapStateToProps function, which I can confirm it is doing as I put a console log inside of there to get the rooms and the object is the updated object.
However, it appears the the render isn't actually getting updated although the state is getting updated. I have tried to do a componentWillReceiveProps and assign the state but the state is never actually updated within here.
Rooms Action
import {SET_ROOMS} from './actionTypes';
import store from "../index";
export const getRooms = () => {
return dispatch => {
fetch("http://localhost/rooms").catch(err => {
console.log(err)
}).then(res => {
res.json();
}).then(parsedRes => {
dispatch(setRooms(parsedRes));
})
}
};
export const addRoom = (roomName, roomDescription) => {
const rooms = store.getState().rooms.rooms;
const room = {
room_name: roomName,
room_description: roomDescription
};
return dispatch => {
fetch("http://localhost/rooms", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(room)
}).catch(err => {
console.log(err)
}).then(res => res.json())
.then(parsedRes => {
rooms.push(parsedRes);
dispatch(setRooms(rooms));
})
}
};
export const setRooms = rooms => {
return {
type: SET_ROOMS,
rooms: rooms
}
};
Initialising Redux Store
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';
const composeEnhancers = compose;
const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)));
export default store;
Initializing Reducers
import {combineReducers} from "redux";
import lightsReducer from "./lights";
import roomsReducer from "./rooms";
import modalsReducer from "./modals";
export default combineReducers({
lights: lightsReducer,
rooms: roomsReducer,
modals: modalsReducer
});
I want to separate modules, so I tried to separate files in the src/store/modules directory.
To merge reducer modules, I use combineReducers() in modules/index.js.
Before separating these modules, modules/index.js file's code was modules/board.js.
Then I added board.js file. I moved code of index.js to board.js. Finally I added combineReducer() in index.js, but somehow it is not working.
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './containers/App';
import store from './store';
const rootElement = document.getElementById('root');
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
src/containers/BoardContainer.js
import React from 'react';
import Board from '../components/Board';
import { connect } from 'react-redux';
import * as boardActions from '../store/modules/board';
class BoardContainer extends React.Component {
componentWillMount() {
this.props.handleReadBoards();
}
render() {
/* ... */
}
}
const mapStateToProps = (state) => {
return {
boardList: state.get('boardList')
};
}
const mapDispatchToProps = (dispatch) => {
return {
handleReadBoards: () => { dispatch(boardActions.readBoardList()) }
};
}
export default connect(mapStateToProps, mapDispatchToProps)(BoardContainer);
src/store/index.js
// redux
import { createStore, applyMiddleware, compose } from 'redux';
import reducers from './modules';
// redux middleware
import thunk from 'redux-thunk';
const store = createStore(reducers,
compose(applyMiddleware(thunk))
);
export default store;
src/store/modules/index.js
import { combineReducers } from 'redux';
import board from './board';
export default combineReducers({
board
});
src/store/modules/board.js
import { createAction, handleActions } from 'redux-actions';
import { Map, List } from 'immutable';
import * as boardApi from '../../lib/api/board';
// Action Types
const READ_BOARD_LIST = 'board/READ_BOARD_LIST';
// Action Creators
export const readBoardList = () => async (dispatch) => {
try {
const boardList = await boardApi.getBoardList();
dispatch({
type: READ_BOARD_LIST,
payload: boardList
});
} catch (err) {
console.log(err);
}
}
// initial state
const initialState = Map({
boardList: List()
})
// reducer
// export default handleActions({
// [READ_BOARD_LIST]: (state, action) => {
// const boardList = state.get('boardList');
// return state.set('boardList', action.payload.data);
// },
// }, initialState);
// reducer
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case READ_BOARD_LIST:
return state.set('boardList', action.payload.data);
default:
return state;
}
}
Your reducer now contains submodules. So that you have to state from which module you want to get the data: state.board.get('boardList').
You can try to setup redux tool to easy visualize your data inside redux.
const mapStateToProps = (state) => {
return {
boardList: state.board.get('boardList')
};
}
In a small React/Redux app that I am writing, I have a thunk that looks as follow:
updateCategory(category){
return function(dispatch, getState){
dispatch({ type : types.UPDATE_CATEGORY, category });
dispatch({ type : locationTypes.UPDATE_CATEGORY_FOR_ALL_LOCATIONS, category });
}
}
complete code:
//Category Duck
import v4 from 'node-uuid';
import {types as locationTypes} from './locations'
export const types = {
UPDATE_CATEGORY:"UPDATE_CATEGORY"
};
const initialState = [];
export function reducer(state = initialState, action){
switch (action.type) {
case types.UPDATE_CATEGORY:
return state.map( item => item.id !== action.category.id ? item : {...action.category} );
default:
return state;
}
};
export const actions={
updateCategory(category){
return function(dispatch, getState){
dispatch({ type : types.UPDATE_CATEGORY, category });
dispatch({ type : locationTypes.UPDATE_CATEGORY_FOR_ALL_LOCATIONS, category });
}
}
}
//Location Duck
import v4 from 'node-uuid';
export const types = {
UPDATE_CATEGORY_FOR_ALL_LOCATIONS:"UPDATE_CATEGORY_FOR_ALL_LOCATIONS"
};
const initialState=[];
export function reducer(state = initialState, action){
switch (action.type) {
case types.UPDATE_CATEGORY_FOR_ALL_LOCATIONS:
return state.map(item => item.category.id !== action.category.id ? item : { ...item, 'category':{name:action.category.name, id:action.category.id} })
default:
return state;
}
};
export const actions={
updateCategoryForAllLocations(category){
return { type : types.UPDATE_CATEGORY_FOR_ALL_LOCATIONS, category}
}
}
//configStore
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { routerReducer, routerMiddleware } from 'react-router-redux';//, push
import logger from 'redux-logger';
import * as storage from './localstorage';
import throttle from 'lodash/throttle';
import reducers from './duckes/rootReducer'; // Or wherever you keep your reducers
import thunk from 'redux-thunk';
const persistedState = storage.loadState();
const configureStore = (history) => {
const store = createStore(
combineReducers({
...reducers,
// router: routerReducer
}),
persistedState,
applyMiddleware(thunk)
);
store.subscribe(throttle(() => {
storage.saveState( store.getState() )
}),1000);
return store;
}
export default configureStore;
when calling it from a UI click handler in a connected component like this:
updateCategory({id:1, name:'foo')
I get back Error: Actions must be plain objects. Use custom middleware for async actions.
Can you advice me on how to solve this or maybe explain why it is happening?
the component with the call looks as follows:
/ManageCategoryPage
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {actions as categoryActions} from '../../duckes/categories';
import CategoryForm from './CategoryForm';
import {getElementByID} from '../../utils';
class ManageCategoryPage extends Component {
constructor(props) {
super(props);
//Init state
this.state = {
'category' : Object.assign({},this.props.category),
'errors':{}
}
//Bind functions
this.saveCategory=this.saveCategory.bind(this);
this.updateCategoryState=this.updateCategoryState.bind(this);
this.categoryExists=this.categoryExists.bind(this);
}
updateCategoryState(event){
...
}
categoryExists(category){
...
}
saveCategory(event){
event.preventDefault();
const {category}=this.state;
this.props.actions.updateCategory(category);
this.props.history.push('/categories');
}
//Render
render(){
return (
<CategoryForm
category={this.state.category}
locations={this.props.locations}
onChange={this.updateCategoryState}
onSave={this.saveCategory}
errors={this.state.errors}/>
)
}
}
//Prop Types validation
ManageCategoryPage.propTypes={
category: PropTypes.object.isRequired,
locations: PropTypes.array.isRequired,
categories: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
//Redux connect
const mapStateToProps = ({locations, categories}, ownProps) => {
let category={id:'', name:''};
return {
category : getElementByID(categories, ownProps.match.params.id) || category,
locations : locations,
categories : categories
};
};
const mapDispatchToProps = (dispatch) => {
return {
'actions': bindActionCreators(categoryActions, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ManageCategoryPage);
I have found the issue and actually it was a mistake of mine, I had two configStore files and I somehow imported an old version of the configStore file that i created previously and it was missing 'thunk' as a param in applyMiddleware() call, so it means that it was applyMiddleware() instead applyMiddleware(thunk).
I'm using redux and NetInfo to manager connection detection during startup and any actions where connection is important.
import createOneShotMiddleware from 'redux-middleware-oneshot';
import { NetInfo } from 'react-native';
import { checkConnection } from '../actions/networkActions';
export const middleware = createOneShotMiddleware((dispatch) => {
const handle = (isConnected) => dispatch(checkConnection(isConnected));
NetInfo.isConnected.fetch().done(handle);
NetInfo.isConnected.addEventListener('change', handle);
});
Actions
import * as types from './actionTypes';
export function checkConnection(isConnected) {
return {
type: types.CHECK_CONNECTION,
isConnected: isConnected
};
}
Reducers
import { CHECK_CONNECTION } from '../actions/actionTypes';
const initialState = {
isConnected: false,
};
export default function network(state = initialState, action = {}) {
switch (action.type) {
case CHECK_CONNECTION:
return Object.assign({}, state, {isConnected: action.isConnected})
default:
return state;
}
}
App.js
import React, { Component } from 'react-native';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { middleware as netInfo } from './Middleware/redux-middleware-react-native-netinfo';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import createLogger from 'redux-logger'
import * as reducers from './reducers';
import NavigationScreen from './Containers/NavigationScreen';
const logger = createLogger()
const createStoreWithMiddleware = applyMiddleware(thunk, logger, netInfo)(createStore)
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer)
export default class App extends Component {
render() {
return (
<Provider store={store}>
<NavigationScreen />
</Provider>
);
}
}
It's not work and don't update the state, any suggestions?
This is old but for anyone that arrives here in the future this is how i have done it i tend to do this on my actions creators:
export function networkCheck(){
return (dispatch) => {
const dispatchNetworkState = (isConnected) => dispatch({
type: types.NETWORK_STATE,
state: isConnected
})
const handle = () => NetInfo.isConnected.fetch().done(dispatchNetworkState)
NetInfo.isConnected.addEventListener('change', handle);
}
}
the isue on the code before is that is not following the chain of eventlistener -> testNetwork -> dispatch result