Access state and load data properly in stateless react component - reactjs

I wonder how to properly load data for a stateless component using a a state value, like user id, as an argument. At the moment I try to load the data in an onEnter function but I don't seem to be able to access the state at that stage.
Is it appropriate to load the data in onEnter at all or is it expected to be done somewhere else or in a different way to be able to access the state ?
Further details about the current setup
The project is using react-router v2 and the different parts are split into separate directories, like this:
/index.js
/actions/
/components
/containers
/reducers
The index.js is creating the store that holds the state.
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware} from 'redux';
import {Provider} from 'react-redux';
import thunk from 'redux-thunk';
import promiseMiddleware from 'redux-promise-middleware';
import {reactWeb} from './reducers';
import AppContainer from './containers/AppContainer';
import './css/index.css';
let middlewares = [thunk, promiseMiddleware()];
let store = createStore(reactWeb, applyMiddleware(...middlewares));
ReactDOM.render(
<Provider store={store}>
<AppContainer/>
</Provider>, document.getElementById('root'));
In the AppContainer data of the current user is loaded using onEnter and the data is stored in state as user. The callback is used to wait for the response before proceeding. In the onEnterTodos I need to get the id of the current user from state to use as an argument. And I don't know how to access the state there.
import {connect} from 'react-redux';
import App from '../components/App';
import {init} from '../actions';
import {getCurrentUser} from '../actions/profile';
import {getTodos} from '../actions/todos';
const mapStateToProps = (state) => {
return {
isLoggedIn: state.loggedIn,
redirectUrl: state.redirectUrl,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onEnterApp: (nextState, replace, callback) => {
// Get user data
dispatch(getCurrentUser(nextState, callback));
callback();
},
onEnterTodos: (nextState, replace) => {
// Get todos of the user - how to get the userid
dispatch(getTodos(795558));
}
};
};
const AppContainer = connect(mapStateToProps, mapDispatchToProps)(App);
export default AppContainer;
Some claim that the routes should be returned from a function that will take the store as an argument, then one can pass the store as an argument to an onEnter function. That means moving the routing setup to the index.js and the problem for me was that the dispatch function isn't available then.
Here is by the way the component associated with the container:
import React from 'react';
import StartContainer from '../containers/StartContainer';
import TodosContainer from '../containers/TodosContainer';
import {Router, Route, browserHistory, IndexRoute, IndexRedirect} from 'react-router';
const App = ({onEnterApp, onEnterTodos}) => {
let routes = (
<Route path="/" onEnter={onEnterApp}>
<IndexRedirect to="start"/>
<Route path="start" component={StartContainer}/>
<Route path="todos" onEnter={onEnterTodos} component={TodosContainer}/>
</Route>
);
return (
<div>
<Router history={browserHistory} routes={routes}/>
</div>
)
};
App.propTypes = {};
export default App;

To solve this I chained the requests. So, the state is not directly accessible in the onEnter function, but in this case I could rely on the API.
The following onEnter function was changed in the AppContainer:
onEnterUsers: (nextState, replace) => {
dispatch(getEmployees());
}
Then a two new actions were created. The first one, to get info about the current user, now just returns a promise.
export const getCurrentUserPromise = (nextState, callback) => {
let headers = {
'Accept': 'application/json'
};
return apiCall('get', config.apiEndPoint + 'login/currentLogin', null, headers);
};
Finally, to get the todos I use getCurrentUserPromise and chain the request for the todos.
export const getEmployees = () => {
let headers = {
'Accept': 'application/json'
};
return dispatch => {
const payloadUser = getCurrentUserPromise()
.then(res => {
const payload = apiCall('get', config.apiEndPoint + 'todos/find?userid=' + res.userid, null, headers)
.then(res => {
return res;
});
dispatch({type: 'GET_TODOS', payload});
return res;
});
return dispatch({type: 'GET_CURRENT_USER', payloadUser});
};
};

Related

React router v6 how to use `navigate` redirection in axios interceptor

import axios from "axios";
import { useNavigate } from "react-router-dom";
export const api = axios.create({
baseURL: "http://127.0.0.1:8000/",
headers: {
"content-type": "application/json",
},
});
api.interceptors.response.use(
function (response) {
return response;
},
function (er) {
if (axios.isAxiosError(er)) {
if (er.response) {
if (er.response.status == 401) {
// Won't work
useNavigate()("/login");
}
}
}
return Promise.reject(er);
}
);
In the pre-RRDv6 world you would create a custom history object, to be exported and imported and passed to a Router, and imported and accessible in external javascript logic, like redux-thunks, axios utilities, etc.
To replicate this in RRDv6 you need to also create a custom router component so it can be passed an external history object. This is because all the higher level RRDv6 routers maintain their own internal history contexts, so we need to duplicate the history instantiation and state part and pass in the props to fit the base Router component's new API.
import { Router } from "react-router-dom";
const CustomRouter = ({ history, ...props }) => {
const [state, setState] = useState({
action: history.action,
location: history.location
});
useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
{...props}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
};
Create the history object you need:
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
export default history;
Import and pass to the new CustomRouter:
import customHistory from '../path/to/history';
...
<CustomRouter history={customHistory}>
... app code ...
</CustomRouter>
Import and consume in your axios functions:
import axios from "axios";
import history from '../path/to/history';
export const api = axios.create({
baseURL: "http://127.0.0.1:8000/",
headers: {
"content-type": "application/json",
},
});
api.interceptors.response.use(
function (response) {
return response;
},
function (er) {
if (axios.isAxiosError(er)) {
if (er.response) {
if (er.response.status == 401) {
history.replace("/login"); // <-- navigate
}
}
}
return Promise.reject(er);
}
);
Update
react-router-dom exports a history router, i.e. unstable_HistoryRouter.
Example:
import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
import history from '../path/to/history';
...
<HistoryRouter history={customHistory}>
... app code ...
</HistoryRouter>
Note:
This API is currently prefixed as unstable_ because you may
unintentionally add two versions of the history library to your app,
the one you have added to your package.json and whatever version React
Router uses internally. If it is allowed by your tooling, it's
recommended to not add history as a direct dependency and instead rely
on the nested dependency from the react-router package. Once we have a
mechanism to detect mis-matched versions, this API will remove its
unstable_ prefix.
I had the same issue this is what I did and it worked for me ( based on an answer from a similar question for react router v4
I added a interceptor component inside of the react router config which passes a prop of the useNavigate hook
remove the interceptors from the axios config file and put them in a separate interceptors file
SetupInterceptor.js
//place all imports here
const SetupInterceptors = (navigate) => {
axios.interceptors.response.use(
function (response) {
// Do something with response data
return response;
},
function (error) {
// Do something with response error
if (error.response) {
if (error.response.status === 401 || error.response.status === 403) {
navigate('/login');
}
}
return Promise.reject(error);
}
);
};
export default SetupInterceptors;
axiosconfigfile.js
import axios from "axios";
//axios configuration here
export default axios;
add the AxiosInterceptorComponent to app.js
app.js
import SetupInterceptors from "SetupInterceptor";
import { useState } from "react";
function NavigateFunctionComponent(props) {
let navigate = useNavigate();
const [ran,setRan] = useState(false);
{/* only run setup once */}
if(!ran){
SetupInterceptors(navigate);
setRan(true);
}
return <></>;
}
function App(props) {
return (
<BrowserRouter>
{<NavigateFunctionComponent />}
<Routes>
{/* other routes here */}
<Route path="/login" element={<Login/>}></Route>
{/* other routes here */}
</Routes>
</BrowserRouter>
);
}
As of v6.1.0, you can easily redirect outside of react components with ease.
import {createBrowserHistory} from 'history';
import {unstable_HistoryRouter as HistoryRouter} from 'react-router-dom';
let history = createBrowserHistory();
function App() {
return (
<HistoryRouter history={history}>
// The rest of your app
</HistoryRouter>
);
}
// Usage
history.replace('/foo');
Just replace BrowserRouter with HistoryRouter and you're good.
Sauce: https://github.com/remix-run/react-router/issues/8264#issuecomment-991271554
I have solved this problem by creating a component for the interceptor:
import { useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { axiosInstance } from '../custom-axios';
export const ResponseInterceptor = () => {
const { onLogout } = useAuth();
const { addError } = useAPIError();
const navigate = useNavigate()
const interceptorId = useRef<number | null>(null);
useEffect(() => {
interceptorId.current = axiosInstance.interceptors.response.use(undefined, (error) => {
switch (error.response.status) {
case 401:
navigate('/login');
break;
}
});
return () => {
axiosInstance.interceptors.response.eject(interceptorId.current as number);
};
}, []);
return null;
};
And connected it to the App component:
const App = () => {
return (
<AuthProvider>
<APIErrorProvider>
<AppRoutes />
<ResponseInterceptor />
<APIErrorNotification />
</APIErrorProvider>
</AuthProvider>
);
};
export default App;
Since version 6.1.0 React Router has an unstable_HistoryRouter. With it history can now be used outside of React context again. It is used like this:
// lib/history.js
import { createBrowserHistory } from "history";
export default createBrowserHistory();
// App.jsx
import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom';
import history from "lib/history";
function App() {
return (
<HistoryRouter history={history}>
// The rest of your app
</HistoryRouter>
);
}
// lib/axios.js
import history from "lib/history";
import storage from "utils/storage";
export const axios = Axios.create({})
axios.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
if (error.response?.status === 401) {
storage.clearToken();
history.push("/auth/login");
return Promise.resolve();
}
return Promise.reject(error);
}
);
Source
Asked what the unstable_-prefix means, one of the maintainers answered:
The unstable_ prefix just signifies you could encounter bugs due to mismatched versions of history from what react-router requests itself. While the practical implications are probably going to just create type issues (different types between versions of history), there could also be API or behavior changes that cause more subtle bugs (such as how URLs are parsed, or how paths are generated).
As long as you're keeping an eye on the version of history requested by react-router and ensuring that's in sync with the version used by your app, you should be free of issues. We don't yet have protections or warnings against the mismatch, hence we're calling it unstable for now.
The reason this is not working is because you can only consume hooks inside a React component or a custom hook.
Since this is neither, hence the useNavigate() hook is failing.
My advice would be to wrap the entire logic inside a custom hook.
Pseudo code :
import { useCallback, useEffect, useState } from "react"
export default function useAsync(callback, dependencies = []) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState()
const [value, setValue] = useState()
// Simply manage 3 different states and update them as per the results of a Promise's resolution
// Here, we define a callback
const callbackMemoized = useCallback(() => {
setLoading(true)
setError(undefined)
setValue(undefined)
callback()
// ON SUCCESS -> Set the data from promise as "value"
.then(setValue)
// ON FAILURE -> Set the err from promise as "error"
.catch((error)=> {
setError(error)
useNavigate()('/login')
})
// Irresp of fail or success, loading must stop after promise has ran
.finally(() => setLoading(false))
// This function runs everytime some dependency changes
}, dependencies)
// To run the callback function each time it itself changes i.e. when its dependencies change
useEffect(() => {
callbackMemoized()
}, [callbackMemoized])
return { loading, error, value }
}
Here, just pass your axios call as a callback to the hook.
For reference for migration from v5 to v6 of react router, this video is helpful

pushing history according to previously selected filters from localstorage

Since I'm new to React I'm trying to correctly implement routing according to previous selected filters stored in local-storage and then push them to history (with a time-out) on the start-up of the application.
Without the timeout, the app jumps around between getting the user-context/token-authentication
http://localhost:3000/#tokenid=123456789012345678901234567890
and the actual URL I'd like to route to:
http://localhost:3000/monthly?date=1629361313861&dispatcher=Srv+DE+01
Since I don't know if this is the correct approach and the fact that it 'jumps' around since the UserContext is not yet established, I would really appreciate any advice on this issue I'm experiencing.
App.tsx
import React from 'react';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { Switch, Route } from 'react-router-dom';
import { CssBaseline } from '#material-ui/core';
import { ToastContainer } from 'react-toastify';
import { MuiPickersUtilsProvider } from '#material-ui/pickers';
import DateFnsUtils from '#date-io/date-fns';
import 'react-toastify/dist/ReactToastify.css';
import store from './services';
import history from './utils/history';
import './i18n';
import UserContext from './modules/UserContext';
import FactBox from './modules/FactBox';
import ModalRoot from './components/ModalRoot';
import pathsConst from './const/paths';
import DailyView from './pages/DailyView';
import WeeklyView from './pages/WeeklyView';
import MonthlyView from './pages/MonthlyView';
import NotFound from './pages/404';
const App = () => (
<Provider store={store}>
<ConnectedRouter history={history}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<CssBaseline />
<UserContext>
<Switch>
<Route exact path={pathsConst.INDEX} component={DailyView} />
<Route exact path={pathsConst.WEEKLY_VIEW} component={WeeklyView} />
<Route exact path={pathsConst.MONTHLY_VIEW} component={MonthlyView} />
<Route component={NotFound} />
</Switch>
<FactBox />
<ModalRoot />
</UserContext>
</MuiPickersUtilsProvider>
</ConnectedRouter>
<ToastContainer
position="bottom-center"
autoClose={6000}
hideProgressBar
pauseOnHover
/>
</Provider>
);
export default App;
reducer.ts
import { combineReducers } from 'redux';
import { connectRouter as router } from 'connected-react-router';
import history from '../utils/history';
import loading from './loading/reducer';
import modal from './modal/reducer';
import resources from './resources/reducer';
import departments from './departments/reducer';
import dispatchers from './dispatchers/reducer';
import projects from './projects/reducer';
import events from './events/reducer';
import uiSettings from './uiSettings/reducer';
import userContext from './userContext/reducer';
import holidays from './holidays/reducer';
const rootReducer = combineReducers({
router: router(history),
loading,
modal,
resources,
departments,
dispatchers,
projects,
events,
uiSettings,
userContext,
holidays,
});
export default rootReducer;
/utils/history.ts
import { createBrowserHistory } from 'history';
import pathsConst from '../const/paths';
function getHistory () {
let history = createBrowserHistory({
basename: process.env.PUBLIC_URL,
});
let lastPath = '/';
let lastQueryString = localStorage.getItem("lastQueryString");
let lastPathTemp = localStorage.getItem("lastPath");
let lastURL = localStorage.getItem("lastURL");
let adalID = localStorage.getItem("adal.idtoken");
if ( lastPathTemp !== null ) {
if (lastPathTemp.includes('weekly')=== true) {
lastPath = pathsConst.WEEKLY_VIEW;
}
else if (lastPathTemp.includes('monthly')=== true) {
lastPath = pathsConst.MONTHLY_VIEW;
}
else {
lastPath = pathsConst.INDEX;
}
}
// DBG:
console.log('DBG - LAST QUERY STRING:', lastQueryString);
console.log('DBG - LAST URL:', lastURL);
if ( lastQueryString !== null && lastPath !== null && adalID !== null ) {
let lastQueryStringEdit = "?date=" + Date.now();
// INFO: Check for additional query params
if (lastQueryString.indexOf("&") !== -1) {
let pos1 = lastQueryString.indexOf("&");
let substr = lastQueryString.substring(pos1, lastQueryString.length);
lastQueryStringEdit = "?date=" + Date.now() + substr;
}
setTimeout(function() {
console.log('DBG - PUSHING.............................');
history.push({
pathname: lastPath,
search: lastQueryStringEdit
});
}, 2000);
return history;
}
else { // INFO: Return as is
return history;
}
}
const history = getHistory();
export default history;
This is my way to access properties of url
//for accessing path of url
const {pathname} = useLocation();
...
//for going back to the last used url
const navigate = useNavigate(); //react router v6
navigate(-1) // to go back to last used url
navigate(-2) // go back to second last used url
navigate(-3) // so on ...
...
For searching for a specific param in url, I make make a custom hook as follows and pass in the required variable name that i want to get value of
import { useLocation } from "react-router-dom";
export default function useGetParameter(name: string) {
const { search } = useLocation();
const query = new URLSearchParams(search);
return query.get(name);
}
//this custom hook return the required param in url or null if it does not exist
Recommendation
I would recommend not to store values in localstorage this way because localstorage values have no expiry and you have to explicitly remove them from your browser. Make use of session storage with eg(useState, this.state, useContext, Redux etc) to store and access variable properties. However if you for any case still want to persist data on localstorage for a longer period of time then I'd recommend using other libraries that do this job for you. The benifit is that, these libraries are configured to be more binding and offer you seperation of logic. Do look in to redux persist

Better way to catch the state of my action in my app + react-redux +react-router

I create an example to use react-redux in my app that is working and print the value of my test, my doubt here is exist other way to catch the state of the store and don't write the code login[0].user to catch the value of the reduce that i will save.
thanks to all
File:: APP
import React from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import Login from '../pages/Login'
import NotFound from '../pages/404'
const App = () => (
<BrowserRouter>
<Switch>
<Route exact path="/login" name="Login Page" render={props => <Login {...props}/>} />
<Route component={NotFound}/>
</Switch>
</BrowserRouter>
)
export default App
File login
import React from 'react'
import {connect} from 'react-redux'
const Login = (...login) => {
console.log("value before")
console.log(login)
const loginUser = login[0].user;
console.log("valor after")
console.log(loginUser)
}
const mapStateProps = (state)=>{
return{
user: state.loginUser,
};
}
const wrapper = connect(mapStateProps);
const component = wrapper (Login);
export default component
File store
import { createStore, combineReducers } from 'redux'
import loginUser from './Redux/reducer/loginUser'
const reducer =combineReducers({loginUser})
const store = createStore(reducer)
export default store
File Reducer
const defautlState={username:'test',password:'test'};
function reducer(state= defautlState,{type,payload}){
switch(type){
case 'saveSession':{
return {
username:'test2',
password:'test2'
};
}
default:
return state;
}
}
export default reducer;
File Action creator
export const type = 'saveSession'
const saveSession = user =>{
return {
type,
payload:user,
};
};
export default saveSession;
The reason that you need login[0] is because of the way that you have defined Login here:
const Login = (...login) => {
The ... means that login is an array of rest parameters and that your Login component can take any number of arguments. This isn't right because React components can only take one argument which is a object of props.
You want to define it like this:
const Login = (props) => {
const user = props.user;
// rest of the component
}
Or you can destructure the arguments:
const Login = ({user}) => {
// rest of the component
}
You could also take no arguments and get the value of user from useSelector instead of connect.
import React from 'react';
import {useSelector} from 'react-redux';
const Login = () => {
const user = useSelector( state => state.loginUser );
// rest of the component
}
export default Login;

React Router Dom redirect from redux action creator

I am working in a Web application (built using React+Redux+React Router Dom) and Firebase as backend.
My question is: Is possibile to carry out a Redirect from an action creator (so, outside the react router dom components ) ?
For example: I want user to be redirected to Home after an operation of deletion of an item:
export const deleteItem = (item) =>{
const id = firebase.auth().currentUser.uid;
var itemRef = firebase.database().ref("users/"+id+"/items/"+item);
return dispatch =>{
dispatch({type:'DELETING_ITEM'});
item.remove()
.then(()=>{
// I WANT TO REDIRECT USER TO /HOME (or anywhere else)
})
}
}
How can achieve a redirect programmatically ?
Yes it is possible:
1-) create a history.js file with this content.
import createHistory from "history/createBrowserHistory";
export default createHistory();
2-) in the file you set up Router (in index.js or app.js):
use Router from react-router-dom instead of BrowserRouter, import history and give it in history prop of Router like this:
import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import * as serviceWorker from "./serviceWorker";
import { Router } from "react-router-dom";
import history from "./history";
import { Provider } from "react-redux";
import store from "./store/store";
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<App />
</Router>
</Provider>,
document.getElementById("root")
);
3-) use it in your action
import history from "../../history";
export const deleteItem = item => {
const id = firebase.auth().currentUser.uid;
var itemRef = firebase.database().ref("users/" + id + "/items/" + item);
return dispatch => {
dispatch({ type: "DELETING_ITEM" });
item.remove().then(() => {
// I WANT TO REDIRECT USER TO /HOME (or anywhere else)
history.push("/home");
});
};
};
You can just use/try this if it's just a simple redirect.
export const deleteItem = (item) =>{
const id = firebase.auth().currentUser.uid;
var itemRef = firebase.database().ref("users/"+id+"/items/"+item);
return dispatch =>{
dispatch({type:'DELETING_ITEM'});
item.remove()
.then(()=>{
window.location.href = url
})
}
}

Redux reducer doesn't update store/redux-promise not resolving

So I've recently started learning Redux and now I'm trying to make my first app with it, but I've stumbled upon a problem which I cannot resolve on my own. Basically I want a user to click a button (there will be authentication) and fetch all his or hers data from Firebase and display it.
Here is my index.js:
// Dependencies
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import createHistory from 'history/createBrowserHistory';
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux';
import ReduxPromise from "redux-promise";
import ReduxThunk from 'redux-thunk';
// Reducers
import rootReducer from './reducers';
// ServiceWorker
import registerServiceWorker from './registerServiceWorker.js';
// Styles
import './styles/index.css';
// Components
import App from './containers/App.js';
const history = createHistory();
const middleware = routerMiddleware(history);
// Create store
const store = createStore(
combineReducers({
...rootReducer,
router: routerReducer
}),
applyMiddleware(ReduxThunk, middleware, ReduxPromise)
)
ReactDOM.render((
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
), document.getElementById('root'));
registerServiceWorker();
And my main container, App.js:
import React, { Component } from 'react';
import { Route, Switch, withRouter } from 'react-router-dom'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import firebase from 'firebase';
import firebaseConfig from '../firebaseConfig.js';
// Actions
import { fetchAllMonths } from "../actions/index";
// Static components
import Nav from '../components/Nav.js';
// Routes
import CurrentMonth from '../components/CurrentMonth.js';
import AddNewMonth from '../components/AddNewMonth.js';
import Archive from '../components/Archive.js';
import Settings from '../components/Settings.js';
class App extends Component {
constructor (props) {
super(props);
this.login = this.login.bind(this);
}
componentWillMount() {
firebase.initializeApp(firebaseConfig);
firebase.auth().signInAnonymously().catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
});
}
login() {
this.props.fetchAllMonths();
}
render() {
if (this.props.data === undefined) {
return (
<button onClick={this.login}>Login</button>
)
} else if (this.props.data !== undefined) {
return (
<main className="main-container">
<Nav user="user"/>
<Switch>
<Route exact path='/' component={CurrentMonth}/>
<Route path='/aktualny' component={CurrentMonth}/>
<Route path='/nowy' component={AddNewMonth}/>
<Route path='/archiwum' component={Archive}/>
<Route path='/ustawienia' component={Settings}/>
</Switch>
</main>
);
}
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchAllMonths }, dispatch);
}
function mapStateToProps({ data }) {
return { data };
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App))
Main action, fetchAllMonths:
// import firebase from 'firebase';
// Firebase Config
// import axios from 'axios';
export const FETCH_ALL_MONTHS = 'FETCH_ALL_MONTHS';
export function fetchAllMonths() {
/*const database = firebase.database();
const data = database.ref('/users/grhu').on('value', function(snapshot) {
return snapshot.val();
});
console.log(data) */
const data = fetch('https://my-finances-app-ef6dc.firebaseio.com/users/grhu.json')
.then(async (response) => response.json())
.then(data => {
console.log(data);
return data;
}
)
console.log(data);
return {
type: FETCH_ALL_MONTHS,
payload: data
};
};
Reducers index.js:
import { combineReducers } from "redux";
import data from "./reducer_load_from_db";
const rootReducer = combineReducers({
data: data
});
export default rootReducer;
And finally my reducer:
import { FETCH_ALL_MONTHS } from "../actions/index";
export default function(state = [], action) {
switch (action.type) {
case FETCH_ALL_MONTHS:
return [action.payload.data, ...state];
default:
return state;
}
return state;
}
So I'm sure that fetch works fine, because console.log(data) gives me a valid JSON file, but second console.log(data) with the passed const gives me a promise, which then I send as a payload to a Reducer. CreateStore also seems to work, because in the React dev console I can see a "data" prop in App container. I use redux-promise which should resolve the Promise in payload and return a JSON to the store, but data remains undefined. Also tried redux-promise-middleware, but again, no luck.
What am I missing? I've looked at that code for several hours and I cannot understand what is wrong with it.
I'll appreciate all the answers, but i really want to understand the issue, not just copy-paste a solution.
Thanks in advance!
Initial Answer
From what I'm reading in your fetchAllMonths() action creator, you're setting a property on the action it returns called payload equal to the data returned from your API call.
return {
type: FETCH_ALL_MONTHS,
payload: data
};
If you logged action.payload in your reducer like so...
switch (action.type) {
case FETCH_ALL_MONTHS:
console.log(action.payload)
return [action.payload.data, ...state];
default:
return state;
}
I believe you'd see the data returned from your API call.
So then in your reducer you would be expecting action.payload as a property of the FETCH_ALL_MONTHS action. And you'd want to use the spread operator ...action.payload.
Also, to make your logic a little easier to read, why not try using an async action to fetch data and then dispatch an action that takes in the data returned from the API call?
Hope that helps!
Updated Answer
As I thought about this and read your reply to my answer, I think you may need to use an async action to ensure your data was successfully fetched. I made a really simple CodePen example using async actions.
https://codepen.io/joehdodd/pen/rJRbZG?editors=0010
Let me know if that helps and if you get it working that way.

Resources