1

(as you can see my reputation is not very high :) and I understand that if you don't like my question it is going to be my last one, therefore I am going to write it as good as I can :)

The problem I am facing is a similar to:

Redux loses state when navigating to another page

However, the answer to the above question was to use 'history.push', which is what I am doing, and I am still having a problem.

I am using:

  • "react": "^16.0.0"
  • "react-redux": "^5.0.6"
  • "react-router": "^4.2.0"
  • "react-router-dom": "^4.2.2"
  • "redux": "^3.7.2"
  • "redux-promise":"^0.5.3"
  • "axios": "^0.17.1"

I am doing the following:

  1. In a react component, "SearchText", getting a text string and calling an action creator
  2. In the action creator, using the text string to send an HTTP request to goodreads.com
  3. In my reducer, using the action payload to set the redux state
  4. Using another component, "BookResults" (in another route), to display this state

The component "SearchText" has a link to the "BookResults" page. So, once "SearchText" fires the action creator, if (when I see on the console that a result is received and the state is set with a list of books) I click on the link that routes to "BookResults", I see the list of books.

If, however, "SearchText" uses (when firing the action creator) a callback that performs history.push of the new page, and this callback is called by 'axios(xxx).then', the state is not set properly, although I see in the console that the HTTP request was successful.

I am sure you can see what I am doing wrong (and I hope it is not very stupid)... Please tell me.

Here is the code:

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';
import SearchText from './components/search_text';
import BookResults from './components/book_results';
import reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <BrowserRouter>
    <div>
      <BrowserRouter>
        <Switch>
          <Route path="/book_results" component={BookResults} />
          <Route path="/" component={SearchText} />
        </Switch>
      </BrowserRouter>
    </div>

    </BrowserRouter>
  </Provider>
  , document.querySelector('#root'));

SearchText component

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Link } from 'react-router-dom';
import { searchForBooks } from '../actions';

class SearchText extends Component {
    constructor(props) {
        super(props);
        this.state = {
            searchText: ''
        };
        this.handleFormSubmit = this.handleFormSubmit.bind(this);
        this.handleSearchTextChange = this.handleSearchTextChange.bind(this);
    }

    handleSearchTextChange(e) {
        this.setState({ searchText: e.target.value });
    }

    handleFormSubmit(e) {
        e.preventDefault();

        const formPayload = {
            searchText: this.state.searchText
        };

        console.log("In SearchBooks/handleFormSubmit. Submitting. state: ", this.state);
    this.props.searchForBooks(formPayload, () => {
            this.props.history.push(`/book_results`);
        });
    }

    render() {
        return (
            <form className="container" onSubmit={this.handleFormSubmit}>
                <h3>Search Form</h3>

                <div className="form-group">
                    <label className="form-label">{'Search Text:'}</label>
                    <input
                        className='form-input'
                        type='text'
                        name='searchText'
                        value={this.state.searchText}
                        onChange={this.handleSearchTextChange}
                        onBlur={this.handleSearchTextBlur}
                        placeholder='' />
                </div>
                <br />
                <input
                    type="submit"
                    className="btn btn-primary float-right"
                    value="Submit"/>
                <br /><br />
                <Link to={`/book_results`}>&lArr; Book Results</Link>
            </form>
        );
    }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ searchForBooks: searchForBooks }, dispatch);
}

export default connect(null, mapDispatchToProps)(SearchText);

BookResults component

import React from 'react';
import { connect } from 'react-redux';
import _ from 'lodash';
import Book from './book';

class BookResults extends React.Component {

  render() {

    let books;
    const booksArray = _.values(this.props.bookResults);
    console.log("***In BookResults. booksArray: ", booksArray);

    if (booksArray.length === 0) {
      books = "No books to display";
    } else {
      books = booksArray.map( (book) => {
        return (
          <Book book={book} key={book.id} />
        );
      });
    }

    return (
      <div>
        <h2>Search Results</h2>
        <br />
        <ul>
          {books}
        </ul>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    bookResults: state.bookResults,
    cats: state.cats
  };
}

export default connect(mapStateToProps)(BookResults);

Book component

import React from 'react';

const Book = (props) => (
  <li>
    {props.book.title}
  </li>
);

export default Book;

actions/index.js

As you can see below, the following line is commented out:

//  .then(() => callback());

If I include it, I have the problem.

import axios from 'axios';
export const SEARCH_FOR_BOOKS = 'search_for_books';

const GOODREADS = "https://www.goodreads.com/search/index.xml";
const KEY = "xxx";

export function searchForBooks(values, callback) {
  let result;
  console.log("In actions/searchForBooks. values: ", values);
  if (!values.searchText || values.searchText === "") {
    console.error("*** ERROR *** In actions/searchForBooks." +
    "values.searchText: ", values.searchText);
  } else {
    const searchUrl = `${GOODREADS}?key=${KEY}&q=${values.searchText}`;
    console.log("In actions/searchForBooks. url: " + searchUrl);

    result = axios.get(searchUrl);
    //  .then(() => callback());
  }

  return {
    type: SEARCH_FOR_BOOKS,
    payload: result
  };
}

reducers/index.js

import { combineReducers } from 'redux';
import bookResultsReducer from './reducer_book_results';

const rootReducer = combineReducers({
  bookResults: bookResultsReducer
});

export default rootReducer;

The reducer

import { parseString } from  'xml2js';
import _ from 'lodash';
import { SEARCH_FOR_BOOKS } from '../actions/index';

const bookResults = {};

export default function bookResultsReducer(state = bookResults, action) {
  switch (action.type) {
    case SEARCH_FOR_BOOKS:
      console.log("In bookResultsReducer. payload: ", action.payload);
      if (action.error) {  // error from goodreads search books
        console.error("*** APP ERROR *** In bookResultsReducer. action.error: ", action.error);
      } else if (!action.payload || !action.payload.data) {
        console.error("*** APP ERROR *** In bookResultsReducer." +
        " action.payload or action.payload.data is undefined", action.payload);
      } else {
        parseString(action.payload.data, function(err, result) {
          if (err) {
            console.error("*** APP ERROR *** In bookResultsReducer. Error from parseString: ", err);
          } else {
            state = Object.assign({}, getBooks(result));
          }
        });
      }
      console.log("In bookResultsReducer. new state: ", state);
      return state;
      break;

    default:
      return state;
  }
}

function getBooks(data) {

  const bookResults =  data.GoodreadsResponse.search[0].results[0].work;
  if (!bookResults || bookResults.length === 0) {
    return {};
  } else {
    const results = bookResults.map( (book, index) => {
      const bookInfo = book.best_book[0];
      return (
        { id: index + 1,
          title: bookInfo.title[0] }
      );
    });
    return _.mapKeys(results, 'id');
  }
}
Yossi
  • 5,577
  • 7
  • 41
  • 76

1 Answers1

2

Someone sent me the solution by mail.

The error was in the actions/index.js file.

Instead of:

import axios from 'axios';
export const SEARCH_FOR_BOOKS = 'search_for_books';

const GOODREADS = "https://www.goodreads.com/search/index.xml";
const KEY = "xxx";

export function searchForBooks(values, callback) {
  let result;
  console.log("In actions/searchForBooks. values: ", values);
  if (!values.searchText || values.searchText === "") {
    console.error("*** ERROR *** In actions/searchForBooks." +
    "values.searchText: ", values.searchText);
  } else {
    const searchUrl = `${GOODREADS}?key=${KEY}&q=${values.searchText}`;
    console.log("In actions/searchForBooks. url: " + searchUrl);

    result = axios.get(searchUrl)
      .then(() => callback());
  }

  return {
    type: SEARCH_FOR_BOOKS,
    payload: result
  };
}

I should have written:

import axios from 'axios';
export const SEARCH_FOR_BOOKS = 'search_for_books';

const GOODREADS = "https://www.goodreads.com/search/index.xml";
const KEY = "xxx";

export function searchForBooks(values, callback) {
  let result;
  console.log("In actions/searchForBooks. values: ", values);
  if (!values.searchText || values.searchText === "") {
    console.error("*** ERROR *** In actions/searchForBooks." +
    "values.searchText: ", values.searchText);
  } else {
    const searchUrl = `${GOODREADS}?key=${KEY}&q=${values.searchText}`;
    console.log("In actions/searchForBooks. url: " + searchUrl);

    result = axios.get(searchUrl)
      .then((res) => {
        callback();
        return res;
      });
  }

  return {
    type: SEARCH_FOR_BOOKS,
    payload: result
  };
}

Explanation:

The issue is that the returned value from axios.get is passed to the .then clause, and whatever is returned from the .then clause is set to be the value of result. My error was that I didn't return anything from the .then clause, and therefore the value of result was undefined, and not the returned promise.

Yossi
  • 5,577
  • 7
  • 41
  • 76