0
votes

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!

1
but data remains undefined. is this from your redux state? then you should return { ...state, data: action.payload.data }; so data state will hold your payload data. - Rei Dien
or if you prefer return { ...state, ...action.payload } - Rei Dien
Unfortunately, that doesn't change a thing. I've also added console.logs to reducers, but it looks like it doesn't even reach it - I still see onlye two console.logs from action (line 18 and 23). - grhu
also 1 thing i see is, on your reducer you use action. while on the component you use a const. might worth if you check those out - Rei Dien

1 Answers

-1
votes

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.