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
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 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')
};
}
action creator
export function pickup(latlng) {
return function(dispatch) {
dispatch({ type: PICKUP_STATE,payload:latlng });
};
}
Reducer
import {
PICKUP_STATE,
PICKUP_ADD,
DROPOFF_STATE
} from '../actions/types';
export default (state={},action) => {
const INITIAL_STATE = {
pickup: '',
pickupAdd:''
};
switch(action.type) {
case PICKUP_STATE:
console.log(action.payload)
return {...state,pickup:action.payload};
case PICKUP_ADD:
return{...state,pickupAdd:action.payload};
case DROPOFF_STATE:
return {...state,dropoff:action.payload}
default:
return state;
}
//return state;
}
Map component
import {
connect
} from "react-redux";
import * as actions from "../actions"
class Map extends React.Component {
componentWillReceiveProps(nextprops) {
if (nextprops.pickupProps !== undefined) {
this.setState({
pick: nextprops.pickupProps
}, () => {
console.log(this.state.pick);
});
}
}
isPickEmpty(emptyPickState) {
this.props.pickup(emptyPickState);
// setTimeout(() =>{ console.log('sdkjlfjlksd',this.state.pick)
},3000);
console.log(this.state.pick);
}
}
const mapStateToProps = (state) => {
// console.log(state.BookingData.pickup);
return {
pickupProps:state.BookingData.pickup,
pickupAddProps: state.BookingData.pickupAdd
}
}
export default connect(mapStateToProps,actions)(Map);
app.js root file
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import "normalize.css/normalize.css"
import "./styles/styles.scss";
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import reduxThunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import AppRouter from './routers/AppRouter';
import reducers from './reducers';
import {AUTH_USER} from "./actions/types";
const middleware = [
reduxThunk,
];
const store = createStore(reducers, composeWithDevTools(
applyMiddleware(...middleware),
// other store enhancers if any
));
const token = localStorage.getItem('token');
if(token){
store.dispatch({type:AUTH_USER});
}
ReactDOM.render(
<Provider store={store}>
<AppRouter />
</Provider>
, document.getElementById('app'));
here my problem is when i'm calling isPickEmpty() from my map component
it invoke action creator this.props.pickup(false) (i also checked in redux-devtools it show false value) then i'm consoling pick state( which store in componentWillReceiveProps(nextprops)) so it showing default value instead of false but when i'm consoling the value inside setTimeout(() =>{console.log('sdkjlfjlksd',this.state.pick) }, 3000); it showing false value
correct me if i'm wrong what i know that redux-thunks works in synchronous manner not asynchronous manner so here why it's not working in synchronous manner
i'm stuck,plz anyone help me!
Update
i just got where the prblm, actually in componentWillReceiveProps where i'm setting pick state value because it is asynchronous so when i'm fetching the value in isPickEmpty function i'm getting prev value.
how handle setState or is there any way to solve
At the component you use the values in BookingData, but on the reducer you add it direct to the state.
const mapStateToProps = (state) => {
console.log(state);//Check the state here
return {
pickupProps:state.pickup,
pickupAddProps: state.pickupAdd
}
}
Should work well if you se this mapStateToProps
When authentication is done I can see the actions updating and the reducer updating - but then mapstatetoprops does not do anything with the new reducer state
The Store keeps logging the same state(initial state) on every update
import React from 'react';
import { bindActionCreators } from 'redux'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';
import { Redirect } from 'react-router-dom'
import firebase from 'firebase';
import { connect } from 'react-redux';
import { signIn, signOut } from '../reducer/actions'
import { auth } from '../firebase'
class LoginPage extends React.PureComponent {
// Configure FirebaseUI.
uiConfig = {'FirebaseUI Config'}
componentDidMount = () => {
auth.onAuthStateChanged((user) => { // gets user object on authentication
console.log('OnAuthStateChanged', user)
console.log('Check If Props Change in AuthChange', this.props.isauthed)
if (user) {
this.props.signIn(user)
} else {
this.props.signOut(user)
}
});
}
render() {
console.log('Check If Props Change in Render', this.props.isauthed)
if (!this.props.isauthed) {
return (
<div>
<h1>My App</h1>
<p>Please sign-in:</p>
<StyledFirebaseAuth uiConfig={this.uiConfig} firebaseAuth={firebase.auth()} />
</div>
);
}
return (
<Redirect to='/' />
)
}
}
export default (LoginPage);
JS that should dispatch and update the props
import { connect } from 'react-redux';
import { signIn, signOut } from '../reducer/actions'
import { bindActionCreators } from 'redux'
import LoginPage from './LoginPage';
const mapStateToProps = (state) => {
console.log('LOGINmapstatetoprops', state.Authed)
return {
isauthed: state.Authed.isauthed,
}
}
const mapDispatchToProps = (dispatch) => {
console.log('LOGINmapDISPATCHoprops')
return bindActionCreators({signIn, signOut}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginPage);
The reducer
import LoginPage from '../components/LoginPage';
import firebase from 'firebase';
const initialState = {
isauthed: false,
error: ''
}
const AuthReducer = (state = initialState, action) => {
console.log('this is an action', action)
switch (action.type) {
case 'IsSignedIn':
return {
...state,
isauthed: action.payload
}
break;
case 'IsNotSignedIn':
return {
...state,
isauthed: action.payload
}
break;
default: return state
}
}
export default AuthReducer;
This is the actions file
export const signIn = (user) => {
console.log('this is from actions', user)
return {
type: 'isSignedIn',
payload: true
}
}
export const signOut = (user) => {
console.log(user)
return {
type: 'isNotSignedIn',
payload: false
}
}
Redux Store
import { createStore } from 'redux';
import logger from 'redux-logger';
import { createEpicMiddleware } from 'redux-observable';
import {AllReducers} from './reducer/index';
//import rootEpic from './modules/rootEpic';
//const epicMiddleware = createEpicMiddleware(rootEpic);
const store = createStore(AllReducers);
store.subscribe(() => {
console.log('storeupdated', store.getState())
});
export default store;
CombineReducers Function
import {combineReducers} from 'redux'
import {AuthReducer} from './AuthReducer'
import {UserReducer} from './UserReducer'
export const AllReducers = combineReducers({
Authed: AuthReducer
User: UserReducer
})
Seems like you have a typo error between 'isSignedIn' and 'IsSignedIn' (first letter differ)
You could avoid that by using a variable declared in a separated file. I like to call it types.js for the types of actions:
// types.js
export const IS_SIGNED_IN = 'isSignedInOrEvenWhateverButYouBetterKeepAClearName'
// (same with all your types)
And then you can import this variable in your other files. You won't have typos anymore!
P.S: you can find all these ideas in redux documentation
I'm having trouble retrieving data from the Redux store. Redux logger is showing the data but can't seem to get it to render. Below is the code for my container component and my action/reducer:
//COMPONENT:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchGrade } from '../../modules/smiles';
class Main extends Component {
componentDidMount() {
this.props.fetchSmile();
console.log(this.props);
}
render() {
const { smiles } = this.props;
return (
<div>
<h1>This is the Main Component</h1>
</div>
);
}
}
const mapStateToProps = state => {
return { smiles: state.smiles };
};
const mapDispatchToProps = dispatch => {
return {
fetchSmile: params => dispatch(fetchGrade(params))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Main);
//ACTION/REDUCER:
import axios from 'axios';
const ADD_GRADE = 'SMILES/ADD_GRADE';
export function reducer(state = {}, action) {
switch (action.type) {
case ADD_GRADE:
return {
...state,
grade: action.payload
};
default:
return state;
}
}
export const fetchGrade = () => {
return dispatch => {
axios
.get('/api/test')
.then(res => dispatch({ type: ADD_GRADE, payload: res.data }));
};
};
//STORE:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import logger from 'redux-logger';
import reducer from '../modules';
let store;
export function configureStore(state: {}) {
if (!store) {
store = createStore(
reducer,
state,
composeWithDevTools(applyMiddleware(logger, thunk))
);
}
return store;
}
//INDEX.JS:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import './index.css';
import App from './components/App';
import registerServiceWorker from './registerServiceWorker';
import { configureStore } from './store';
window.store = configureStore();
render(
<Provider store={window.store}>
<App />
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
I really don't know if this is complicated or an easy fix. I feel like I'm doing everything right but no luck.
You named your reducer, reducer:
export function reducer(state = {}, action) {
Seems that you forgot to access it from your reducer object. it should be something like this:
const mapStateToProps = state => {
return { smiles: state.reducer.smiles };
};