I am building a Jhipster React generated project. My problem is I couldn't manage to chain reducer functions.
Simply, I want to chain getSession() function with another function in the authentication reducer.
In my component I want to handle then() operation like getSession().then(....
Can you please help for this?
Here is the authentication.ts reducer:
authentication.ts
import axios from 'axios';
import { Storage } from 'react-jhipster';
import { REQUEST, SUCCESS, FAILURE } from 'app/shared/reducers/action-type.util';
export const ACTION_TYPES = {
LOGIN: 'authentication/LOGIN',
GET_SESSION: 'authentication/GET_SESSION',
LOGOUT: 'authentication/LOGOUT',
CLEAR_AUTH: 'authentication/CLEAR_AUTH',
ERROR_MESSAGE: 'authentication/ERROR_MESSAGE'
};
const AUTH_TOKEN_KEY = 'jhi-authenticationToken';
const initialState = {
loading: false,
isAuthenticated: false,
loginSuccess: false,
loginError: false, // Errors returned from server side
showModalLogin: false,
account: {} as any,
errorMessage: null as string, // Errors returned from server side
redirectMessage: null as string
};
export type AuthenticationState = Readonly<typeof initialState>;
// Reducer
export default ( state: AuthenticationState = initialState, action ): AuthenticationState => {
switch ( action.type ) {
case REQUEST( ACTION_TYPES.LOGIN ):
case REQUEST( ACTION_TYPES.GET_SESSION ):
return {
...state,
loading: true
};
case FAILURE( ACTION_TYPES.LOGIN ):
return {
...initialState,
errorMessage: action.payload,
showModalLogin: true,
loginError: true
};
case FAILURE( ACTION_TYPES.GET_SESSION ):
return {
...state,
loading: false,
isAuthenticated: false,
showModalLogin: true,
errorMessage: action.payload
};
case SUCCESS( ACTION_TYPES.LOGIN ):
return {
...state,
loading: false,
loginError: false,
showModalLogin: false,
loginSuccess: true
};
case ACTION_TYPES.LOGOUT:
return {
...initialState,
showModalLogin: true
};
case SUCCESS( ACTION_TYPES.GET_SESSION ): {
const isAuthenticated = action.payload && action.payload.data && action.payload.data.activated;
return {
...state,
isAuthenticated,
loading: false,
account: action.payload.data
};
}
case ACTION_TYPES.ERROR_MESSAGE:
return {
...initialState,
showModalLogin: true,
redirectMessage: action.message
};
case ACTION_TYPES.CLEAR_AUTH:
return {
...state,
loading: false,
showModalLogin: true,
isAuthenticated: false
};
default:
return state;
}
};
export const displayAuthError = message => ( { type: ACTION_TYPES.ERROR_MESSAGE, message } );
export const getSession = () => dispatch => {
dispatch( {
type: ACTION_TYPES.GET_SESSION,
payload: axios.get( '/api/account' )
} );
};
export const login = ( username, password, rememberMe = false ) => async ( dispatch, getState ) => {
const result = await dispatch( {
type: ACTION_TYPES.LOGIN,
payload: axios.post( '/api/authenticate', { username, password, rememberMe } )
} );
const bearerToken = result.value.headers.authorization;
if ( bearerToken && bearerToken.slice( 0, 7 ) === 'Bearer ' ) {
const jwt = bearerToken.slice( 7, bearerToken.length );
if ( rememberMe ) {
Storage.local.set( AUTH_TOKEN_KEY, jwt );
} else {
Storage.session.set( AUTH_TOKEN_KEY, jwt );
}
}
dispatch( getSession() );
};
export const clearAuthToken = () => {
if ( Storage.local.get( AUTH_TOKEN_KEY ) ) {
Storage.local.remove( AUTH_TOKEN_KEY );
}
if ( Storage.session.get( AUTH_TOKEN_KEY ) ) {
Storage.session.remove( AUTH_TOKEN_KEY );
}
};
export const logout = () => dispatch => {
clearAuthToken();
dispatch( {
type: ACTION_TYPES.LOGOUT
} );
};
export const clearAuthentication = messageKey => ( dispatch, getState ) => {
clearAuthToken();
dispatch( displayAuthError( messageKey ) );
dispatch( {
type: ACTION_TYPES.CLEAR_AUTH
} );
};
store.ts
import { createStore, applyMiddleware, compose } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import thunkMiddleware from 'redux-thunk';
import reducer, { IRootState } from 'app/shared/reducers';
import DevTools from './devtools';
import errorMiddleware from './error-middleware';
import notificationMiddleware from './notification-middleware';
import loggerMiddleware from './logger-middleware';
import { loadingBarMiddleware } from 'react-redux-loading-bar';
const defaultMiddlewares = [
thunkMiddleware,
errorMiddleware,
notificationMiddleware,
promiseMiddleware(),
loadingBarMiddleware(),
loggerMiddleware
];
const composedMiddlewares = middlewares =>
process.env.NODE_ENV === 'development'
? compose(
applyMiddleware(...defaultMiddlewares, ...middlewares),
DevTools.instrument()
)
: compose(applyMiddleware(...defaultMiddlewares, ...middlewares));
const initialize = (initialState?: IRootState, middlewares = []) => createStore(reducer, initialState, composedMiddlewares(middlewares));
export default initialize;
Home Component
import './home.css';
import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { Button, Row, Col, Alert, Table } from 'reactstrap';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { IRootState } from 'app/shared/reducers';
import { getSession } from 'app/shared/reducers/authentication';
import { getWhoIsInsideInfo, exitVisitor } from 'app/entities/visitor/visitor.reducer';
import { IVisitorInsideInfo } from 'app/shared/model/visitors_inside.model';
import { TextFormat } from 'react-jhipster';
import { APP_TIMESTAMP_FORMAT } from 'app/config/constants';
import VisitorDeleteDialog from 'app/entities/visitor/visitor-delete-dialog';
export interface IHomeProp extends StateProps, DispatchProps { }
const mapStateToProps = ( { authentication, visitorsInsideInfo }: IRootState ) => ( {
account: authentication.account,
isAuthenticated: authentication.isAuthenticated,
visitorInsideList: visitorsInsideInfo.insiderEntities,
loginSuccess: authentication.loginSuccess
} );
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = typeof mapDispatchToProps;
const mapDispatchToProps = { getSession, getWhoIsInsideInfo, exitVisitor };
export class Home extends React.Component<IHomeProp> {
interval: any;
orgDispatch: any;
constructor( props ) {
super( props );
this.renderVisitorsInside = this.renderVisitorsInside.bind( this );
}
setListTimer() {
console.log( 'DIS' );
}
getList = () => {
if ( this.props.account && this.props.account.login ) {
getWhoIsInsideInfo();
}
};
.....
}
In my Home component I want to first call getSession then call getList. If response was OK else reject it.
Thanks from now on.
It looks like you have everything right. You just need to call your reducer functions with dispatch.
For example:
dispatch(getSession()).then(...)
Related
I have a component that should navigate when a user is authenticated:
componentDidUpdate(prevProps, prevState) {
if (this.props.authenticated) {
this.props.navigation.navigate('Main')
}
}
When I dispatch authLogin it should cause a rerender, which handles the navigation:
export const authLogin = (username, password) => {
return dispatch => {
dispatch(authStart());
axios.post(`http://10.0.2.2:8000/api/v1/rest-auth/login/`, {
username: username,
password: password
})
.then(response => {
var token = response.data.key;
try {
AsyncStorage.setItem('token', token);
} catch (err) {
console.log(err)
}
dispatch(authSuccess(token));
})
.catch(err => {
dispatch(authFail());
console.log(err);
})
}
}
Here is my reducer:
export default function (state = initialState, action) {
switch (action.type) {
case "AUTH_START": {
return {
...state,
authenticating: true,
}
}
case "AUTH_SUCCESS": {
return {
...state,
authenticating: false,
authenticated: true,
token: action.token,
}
}
case "AUTH_FAIL": {
return {
...state,
authenticating: false,
authenticated: false,
}
}
case "AUTH_LOGOUT": {
return {
...state,
authenticating: false,
authenticated: false,
token: null,
}
}
default:
return state
}
}
and action creators:
export const authStart = () => ({type: "AUTH_START"})
export const authSuccess = token => ({type: "AUTH_SUCCESS", token})
export const authFail = () => ({type: "AUTH_FAIL"})
My console is logging that Redux actions are dispatched and that the state is changing, but no rerendering is happening. Here's the whole component:
import React, { Component } from 'react';
import { View, StyleSheet } from 'react-native';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import LoginForm from '../components/LoginForm';
import { authLogin } from '../actions/authActions';
export class LoginScreen extends Component {
handlePress = async (username, password) => {
await this.props.authLogin(username, password);
}
componentDidUpdate(prevProps, prevState) {
if (this.props.authenticated) {
this.props.navigation.navigate('Main')
}
}
render() {
return (
<View style={styles.loginForm}>
<LoginForm handlePress={this.handlePress} {...this.props} />
</View>
);
}
}
const mapState = state => {
return {
authenticated: state.auth.authenticated
}
};
const mapDispatch = dispatch => {
return bindActionCreators({
authLogin,
}, dispatch)
};
export default connect(mapState, mapDispatch)(LoginScreen);
LoginScreen.propTypes = {
authLogin: PropTypes.func.isRequired,
authenticated: PropTypes.bool.isRequired,
};
const styles = StyleSheet.create({
loginForm: {
justifyContent: 'center',
alignItems: 'center',
flex: 1
}
});
and here's my store:
import { combineReducers } from 'redux';
import { createStore, applyMiddleware } from 'redux';
import { logger } from 'redux-logger';
import thunk from 'redux-thunk';
import auth from './auth'
const reducer = combineReducers({auth})
const enhancer = applyMiddleware(thunk, logger)
const store = createStore(reducer, enhancer)
export default store
The store is connected in the Provider in App.js.
I added another reducer, which fixed it instantly. Apparently redux didn't like that combineReducers() only had one argument.
i.e. change
const reducer = combineReducers({auth})
to
const reducer = combineReducers({auth, otherReducer})
Why not put the authentication check and navigation statement inside the handlePress().
handlePress = async (username, password) => {
await this.props.authLogin(username, password);
if (this.props.authenticated) {
this.props.navigation.navigate('Main')
}
}
After the authLogin() dispatches the action and state is updated, you can check the authentication status and navigate the user.
Hope this helps!
I recently started using redux for a new personal project. It worked pretty well until I started using "combineReducers". Whenever I click "Fetch todos" both my user as well as my todo reducer get updated and even though they have different data field names both get the same data. Now I probably did some wrong encapsulation here. But no matter how often I went over the docs, I just cannot see what I am doing wrong.
My store initialization script:
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import toDoReducer from './todos/reducer';
import userReducer from './users/reducer';
const rootReducer = combineReducers({
todosSlice: toDoReducer,
usersSlice: userReducer
});
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
gets injected into index:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/app/App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
ReactDOM.render(<Provider store={ configureStore }><App /></Provider>, document.getElementById('root'));
serviceWorker.unregister();
My app hold the logic for the todo container
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as todoActions from '../../store/todos/actions';
import UserContainer from '../usersContainer/UserContainer';
class App extends Component {
componentDidMount() {
console.log(this.props);
}
render() {
let loading = '';
let error = '';
let todos = [];
// check whether the component is fetching data
this.props.loading === true ? loading = <p>Loading...</p> : loading = '';
// check if there was an error
this.props.error && this.props.loading === false ? error = <p>There was an error</p> : error = '';
// map the todos in the desired html markup.
todos = this.props.todos.map( todo => {
return <div key={todo.id}> name: {todo.title} </div>
});
return (
<div className="App">
{/* <UserContainer /> */}
{loading}
{error}
<p onClick={() => this.props.onFetchTodos()}>Fetch Todos</p>
{todos}
</div>
);
}
}
const mapStateToProps = state => {
return {
error: state.todosSlice.error,
loading: state.todosSlice.loading,
todos: state.todosSlice.todos
}
}
const mapDispatchToProps = dispatch => {
return {
onFetchTodos: () => dispatch(todoActions.fetchTodos())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Which has the following actions:
import axios from 'axios';
export const FETCH_TODOS = 'FETCH_TODOS';
export const GET_TODOS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_TODOS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_TODOS_FAILURE = 'FETCH_TODOS_FAILURE';
export const fetchRequest = () => {
return dispatch => {
dispatch(getTodoStarted());
axios.get('https://one365-api-dev.azurewebsites.net/api/teams/')
.then(result => {
dispatch(fetchTodosSucces(result));
}).catch(error => {
dispatch(fetchTodoFailure(error));
});
}
}
const getTodoStarted = () => ({
type: GET_TODOS_STARTED
});
const fetchTodosSucces = todos => ({
type: FETCH_TODOS_SUCCESS,
payload: {
...todos
}
});
const fetchTodoFailure = error => ({
type: FETCH_TODOS_FAILURE,
payload: {
error
}
});
export const fetchTodos = () => {
return (dispatch => {
dispatch(fetchRequest());
});
}
And it's reducer
import * as actions from './actions';
const initialState = {
error: null,
loading: false,
todos: []
}
const todosReducer = (state = initialState, action) => {
switch(action.type) {
case actions.GET_TODOS_STARTED: {
console.log('fetch todo state', state)
return {
...state,
loading: state.loading = true
};
}
case actions.FETCH_TODOS_SUCCESS: {
const todos = action.payload.data;
return {
...state,
loading: false,
todos: state.todos = todos
};
}
case actions.FETCH_TODOS_FAILURE: {
const error = action.payload.error;
return {
...state,
loading: false,
error: state.error = error
};
}
default: {
return state;
}
}
}
export default todosReducer;
The Users Component
import React from 'react';
import { connect } from 'react-redux';
import * as userActions from '../../store/users/actions';
class UserContainer extends React.Component {
render () {
let loading = '';
let error = '';
let users = [];
// check whether the component is fetching data
this.props.usersLoading === true ? loading = <p>Loading...</p> : loading = '';
// check if there was an error
this.props.usersError && this.props.loading === false ? error = <p>There was an error</p> : error = '';
// map the users in the desired html markup.
users = this.props.users.map( user => {
return <div key={user.id}> name: {user.title} </div>
});
return (
<div className="Users">
{loading}
{error}
<p onClick={() => this.props.onFetchUsers()}>Fetch Users</p>
{users}
</div>
);
}
}
const mapStateToProps = state => {
return {
usersError: state.usersSlice.error,
usersLoading: state.usersSlice.loading,
users: state.usersSlice.users
}
}
const mapDispatchToProps= (dispatch) => {
return {
onFetchUsers: () => dispatch(userActions.fetchUsers())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UserContainer);
the user actions:
import axios from 'axios';
export const FETCH_USERS = 'FETCH_TODOS';
export const FETCH_USERS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_TODOS_FAILURE';
export const fetchRequest = () => {
return dispatch => {
dispatch(fetchUsersStarted());
axios.get('https://one365-api-dev.azurewebsites.net/api/me')
.then(result => {
dispatch(fetchUsersSuccess(result));
}).catch(error => {
dispatch(fetchUsersFailure(error));
});
}
}
export const fetchUsersSuccess = (users) => {
return {
type: FETCH_USERS_SUCCESS,
payload: {
...users
}
}
}
export const fetchUsersStarted = () => ({
type: FETCH_USERS_STARTED
});
export const fetchUsersFailure = (error) => {
return {
type: FETCH_USERS_FAILURE,
payload: {
error
}
}
}
export const fetchUsers = () => {
return dispatch => {
dispatch(fetchRequest())
}
};
And it's reducer:
import * as actions from './actions';
const initialState = {
error: '',
loading: false,
users: []
}
const userReducer = (state = initialState, action) => {
switch(action.type) {
case actions.FETCH_USERS_STARTED: {
console.log('fetch users state', state)
return {
...state,
loading: state.loading = true
}
}
case actions.FETCH_USERS_SUCCESS: {
const users = action.payload.data;
return {
...state,
loading: false,
users: state.users = users
}
}
case actions.FETCH_USERS_FAILURE: {
const error = state.payload.error;
return {
...state,
loading: false,
error: state.error = error
}
}
default: {
return state;
}
}
}
export default userReducer;
Now when I run my DEV server I only see the fetch todo button. I commented out the users on click handler to see if it was an event bubble going up. Bu t this wasn't the case.
Once the app load redux dev tools shows the state as follows:
but once i click the fetch todo's handler. Both todos and users get filled.
I appreciate anyone who read though so much (boilerplate) code. I probably made a problem encapsulating my state. but again after reading many tutorials I still cannot find my issue.
You have a copy/paste issue. You changed the names of the constants for your "USERS" actions, but left the values the same as the "TODOS" actions.
export const FETCH_USERS = 'FETCH_TODOS';
export const FETCH_USERS_STARTED = 'GET_TODOS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_TODOS_FAILURE';
I assume you meant to have:
export const FETCH_USERS = 'FETCH_USERS';
export const FETCH_USERS_STARTED = 'FETCH_USERS_STARTED';
export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE';
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 using Redux API Middleware to call the api for the data
Action.js
import { CALL_API } from 'redux-api-middleware';
export const FETCH_POSTS = 'FETCH_POSTS';
export const FETCH_POSTS_SUCCESS = 'FETCH_POSTS_SUCCESS';
export const FETCH_POSTS_FAILURE = 'FETCH_POSTS_FAILURE';
export const fetchPosts = () => ({
[CALL_API]: {
types: [
{
type: FETCH_POSTS,
payload: (action, state) => ({ action: state })
},
{
type: FETCH_POSTS_SUCCESS,
payload: (action, state, response) => {
return response
}
},
FETCH_POSTS_FAILURE
],
endpoint: 'http://localhost:8080/v1/career/',
method: 'GET',
}
});
Reducer.js
import {
FETCH_POSTS,
FETCH_POSTS_SUCCESS,
FETCH_POSTS_FAILURE
} from '../actions/Action';
import {combineReducers} from 'redux'
const INITIAL_STATE = { postsList: { posts: [], error: null, loading: false } };
export const posts=(state = INITIAL_STATE, action)=> {
let error;
switch(action.type) {
case FETCH_POSTS:
return { ...state, postsList: { posts: [], error: null, loading: true} };
case FETCH_POSTS_SUCCESS:
return { ...state, postsList: { posts: action.payload, error: null, loading: false } };
case FETCH_POSTS_FAILURE:
error = action.payload.data || { message: action.payload.message };
return { ...state, postsList: { posts: [], error: error, loading: false } };
default:
return state;
}
export const reducers=combineReducers({
posts:posts
});
export default reducers;
Store.js
import {
applyMiddleware,
createStore,compose
} from 'redux';
import { apiMiddleware } from 'redux-api-middleware';
import reducers from './reducer'
export function ConfigureStore(IntitialState={}){
const stores=createStore(reducers,IntitialState,compose(
applyMiddleware(apiMiddleware),
window.devToolsExtension ? window.devToolsExtension() : f => f
));
return stores;
};
export const store=ConfigureStore()
I can see only the FETCH POSTS is running and when i check the state i get this
I checked the network section of the developer tools.The response is coming from the server
and my response is
I dont know why this not working.Please some one help me .Thanks.
I am fetching an api with axios and the action is being fired after the "persist/REHYDRATE" action resulting in the following
"redux-persist/autoRehydrate: 1 actions were fired before rehydration completed...."
If I delete a tweet one by one and then refresh my browser, it does not store the state. Can't seem to crack this..
Client.js
import React from 'react';
import { render } from 'react-dom';
import { Provider } from "react-redux"
import { compose, applyMiddleware, createStore } from 'redux';
import logger from "redux-logger"
import thunk from "redux-thunk"
import promise from "redux-promise-middleware"
import {persistStore, autoRehydrate} from 'redux-persist'
import tweetApp from "./reducers"
import Layout from "./components/Layout"
import { REHYDRATE } from 'redux-persist/constants'
import createActionBuffer from 'redux-action-buffer'
//const middleware = applyMiddleware(promise(), thunk, logger())
let enhancer = compose(
autoRehydrate({ log: true }),
applyMiddleware(
promise(), thunk, logger(), createActionBuffer(REHYDRATE)
)
)
const store = createStore(
tweetApp,
enhancer
);
const persistConfig = {
whitelist : ["tweets"]
};
persistStore(store, persistConfig);
render(
<Provider store={store}>
<Layout />
</Provider>,
document.getElementById('app')
);
tweetsReducer.js
import {REHYDRATE} from 'redux-persist/constants'
export default function reducer(state={
tweets: [],
fetching: false,
fetched: false,
error: null,
}, action) {
switch (action.type) {
case "persist/REHYDRATE": {
const incoming = action.payload.tweets; // Carts is the name of the reducer
if (incoming) return {...state, ...incoming}
}
case "FETCH_TWEETS": {
return {...state, fetching: true}
}
case "FETCH_TWEETS_REJECTED": {
return {...state, fetching: false, error: action.payload}
}
case "FETCH_TWEETS_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
tweets: action.payload,
}
}
case "ADD_TWEET": {
return {
...state,
tweets: [...state.tweets, action.payload],
}
}
case "UPDATE_TWEET": {
const { id, text } = action.payload
const newTweets = [...state.tweets]
const tweetToUpdate = newTweets.findIndex(tweet => tweet.id === id)
newTweets[tweetToUpdate] = action.payload;
return {
...state,
tweets: newTweets,
}
}
case "DELETE_TWEET": {
return {
tweets: [
...state.tweets.slice(0, action.payload),
...state.tweets.slice(action.payload + 1)
],
}
}
}
return state
}
tweetsActions.js
import axios from "axios";
export function fetchTweets() {
return function(dispatch) {
axios.get("http://rest.learncode.academy/api/test123/tweets")
.then((response) => {
dispatch({type: "FETCH_TWEETS_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_TWEETS_REJECTED", payload: err})
})
}
}
export function addTweet(id, text) {
return {
type: 'ADD_TWEET',
payload: {
id,
text,
},
}
}
export function updateTweet(id, text) {
return {
type: 'UPDATE_TWEET',
payload: {
id,
text,
},
}
}
export function deleteTweet(id) {
return { type: 'DELETE_TWEET', payload: id}
}
layouts.js
import React from "react"
import { connect } from "react-redux"
import { fetchUser } from "../actions/userActions"
import { fetchTweets } from "../actions/tweetsActions"
import { deleteTweet } from "../actions/tweetsActions"
#connect((store) => {
return {
user: store.user.user,
userFetched: store.user.fetched,
tweets: store.tweets.tweets,
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
this.props.dispatch(fetchTweets())
}
fetchTweets() {
//this.props.dispatch(fetchTweets())
}
deleteTweet(idx, e) {
this.props.dispatch(deleteTweet(idx))
}
render() {
const { user, tweets, i } = this.props;
//console.log(this.props)
const mappedTweets = tweets.map((tweet, i) => <li key={i}>{tweet.text}<button onClick={this.deleteTweet.bind(this, i)}>delete</button></li>)
return <div>
<h1>{user.name}</h1>
<ul>{mappedTweets}</ul>
</div>
}
}
UPDATE AND SAME ISSUE:
I tried replacing "componentWILLMount() with componentDidMount()" and the issue still occurs. See logged output:
Move your fetchTweets and fetchUser calls to componentDidMount, otherwise all your code is executed synchronously: from the store being created to your Layout being instantiated and rendered.
componentWillMount is called before render, while componentDidMount is called after the component has been rendered for the first time.