react router not loading id and handling error - react-router

I have tried to follow the udemy guide to implement react router. the problem i have is when loading the page directly with an id without going through a list page first. it does not seem to load the records. i want to go to page /commsmatrix/approve/121 and load record 121. i am using react router v4. in mapStateToProps, records is undefined
approve.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchCommsmatrix } from '../../actions/commsmatrices';
import { bindActionCreators } from 'redux';
import FontAwesome from 'react-fontawesome';
class Approve extends Component {
constructor(props) {
super(props);
this.meta = { title: 'Comms Matrix Approval', description: 'Sox approval' };
this.runOnce = false;
this.passMetaBack = this.passMetaBack.bind(this);
this.initConfirm = this.initConfirm.bind(this);
}
componentDidMount() {
this.passMetaBack;
const { id } = this.props.match.params.id;
this.props.fetchCommsmatrix(id);
}
passMetaBack = () => {
this.props.passMetaBack(this.meta);
};
initConfirm(){
this.runOnce = true;
/*this.props.fetchCommsmatrix(121)
.then(function(response){
console.log(response);
let data = response.payload.data;
if(data.header.error){
self.setState({
showError: true,
errorMsg: data.header.message
});
}else{
}
});*/
}
render() {
console.log(this);
if(!this.runOnce && this.props.isReady){
this.initConfirm();
}
const { record } = this.props ;
console.log(record);
let message = <div>Confirming...<i className="fa fa-spinner fa-spin"></i></div>;
return (
<div className="container-fluid">
<div className="row-fluid top-buffer">{message}</div>
</div>
);
}
}
function mapStateToProps({ records }, ownProps) {
console.log(records);
console.log(ownProps);
return { record : records[ownProps.match.params.id] };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{ fetchCommsmatrix },
dispatch
);
}
export default connect(mapStateToProps, { fetchCommsmatrix })(Approve);
here is my actions
import axios from 'axios';
export const FETCH_COMMSMATRIX = 'fetch_commsmatrix';
export function fetchCommsmatrix(id) {
const request = axios.get(`/api/user/comms/matrices/id/`+id+`/format/json?quiet=1`);
return {
type: FETCH_COMMSMATRIX,
payload: request
};
}
export const FETCH_COMMSMATRICES_BY_SERVICE = 'fetch_commsmatrices_by_service';
export function fetchCommsmatricesByService(service_id) {
const request = axios.get(`/api/user/comms/matrices/format/json?quiet=1&service_id=`+service_id);
return {
type: FETCH_COMMSMATRICES_BY_SERVICE,
payload: request
};
}
here is my reducer
import { FETCH_COMMSMATRIX, FETCH_COMMSMATRICES_BY_SERVICE } from '../actions/commsmatrices';
export default function(state = {}, action) {
switch (action.type) {
case FETCH_COMMSMATRIX:
return { ...state, [action.payload.data.body.recordset.record[0].id] : action.payload.data.body.recordset.record[0] };
case FETCH_COMMSMATRICES_BY_SERVICE:
return action.payload.data.body.recordset.record;
default:
return state;
}
}
here is index reducer
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import ActiveUserReducer from './reducer_active_user';
import CommsmatricesReducer from './reducer_commsmatrices';
import ContentReducer from './reducer_content';
import ContentVideListReducer from './reducer_content_video_list';
import SecurityExemptionsReducer from './reducer_security_exemption';
import ReportsWorkerJobs from './reducer_reports_workerjobs';
import ReportsWorkerJobsCount from './reducer_reports_workerjobs_count';
import ReportsFactsandfigures from './reducer_reports_factsandfigures';
import ReportsFactsandfiguresCount from './reducer_reports_factsandfigures_count';
import ServicesReducer from './reducer_services';
import ServicesEditCheckReducer from './reducer_services_edit_check';
import ServicesAddReducer from './reducer_services_add';
import ServicesRenameReducer from './reducer_services_rename';
import ServicesRemoveReducer from './reducer_services_remove';
import TemplatesReducer from './reducer_templates';
const rootReducer = combineReducers({
form: formReducer,
activeUser: ActiveUserReducer,
commsmatrices: CommsmatricesReducer,
content: ContentReducer,
contentVideoList: ContentVideListReducer,
reportsWorkerJobs: ReportsWorkerJobs,
reportsWorkerJobsCount: ReportsWorkerJobsCount,
securityExemptions: SecurityExemptionsReducer,
reportsFactsAndFigures: ReportsFactsandfigures,
reportsFactsAndFiguresCount: ReportsFactsandfiguresCount,
services: ServicesReducer,
servicesEditCheck: ServicesEditCheckReducer,
servicesAdd: ServicesAddReducer,
servicesRename: ServicesRenameReducer,
servicesRemove: ServicesRemoveReducer,
templatesReducer: TemplatesReducer
});
export default rootReducer;
here is app
import React, { Component } from 'react';
import { Switch, Route, withRouter, Redirect } from 'react-router-dom';
import ReactGA from 'react-ga';
import { connect } from 'react-redux';
import { fetchActiveUser } from './actions/index';
import { bindActionCreators } from 'redux';
import { getHttpRequestJSON } from './components/HTTP.js';
import Header from './components/header';
import Logout from './components/logout';
import SideBar from './components/sidebar';
import HomeContent from './containers/home';
import Ldapuser from './components/ldapuser';
import Admin from './components/admin/admin';
import Services from './components/services/index';
import SecurityExemptionsNew from './components/security/security_exemptions_new';
import WorkerJobs from './components/reports/workerjobs';
import FactsAndFigures from './components/reports/factsandfigures';
import Approve from './components/commsmatrix/approve';
import CommsMatrixTemplates from './components/commsmatrix/templates';
import CommsMatrixTemplate from './components/commsmatrix/template';
ReactGA.initialize('UA-101927425-1');
function fireTracking() {
ReactGA.pageview(window.location.pathname + window.location.search);
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
isGuest: false,
isSupp: false,
priv: [],
loading: true,
version: '',
redirect: false,
title: 'Home',
description: '',
isReady: false
};
}
setRedirect = () => {
this.setState({
redirect: true
});
};
renderRedirect = () => {
//if (this.state.redirect) {
return <Redirect to="/SSOLogon/manual_login.jsp" />;
//}
};
initData = () => {
let self = this;
getHttpRequestJSON(
'/api/user/get/user/method/is/guest/format/json?quiet=1'
)
.then(response => {
let isGuest = response.body.recordset.record.isGuest;
if (isGuest) {
/*$(".logo").trigger('click');
//$("#overlay").show();
$('#modalIntro').modal('toggle');
$("#modalIntro").on("hidden.bs.modal", function () {
$(".logo").trigger('click');
});*/
}
//self.props.isGuest = isGuest;
//self.props.loading = false;
//self.props.version = response.header.version;
self.setState({
loading: false,
version: response.header.version,
isGuest: isGuest
});
})
.catch(error => {
console.log('Failed!', error);
//$('#myModalError .modal-body').html(error);
//$('#myModalError').modal('show');
});
getHttpRequestJSON(
'/api/user/get/user/method/is/supp/format/json?quiet=1'
)
.then(response => {
self.setState({
isSupp: response.body.recordset.record.isSupp
});
})
.catch(error => {
console.log('Failed!', error);
//$('#myModalError .modal-body').html(error);
//$('#myModalError').modal('show');
});
getHttpRequestJSON(
'/api/user/get/user/method/priv/format/json?quiet=1'
)
.then(response => {
self.setState({
priv: response.body.recordset.record
});
})
.catch(error => {
console.log('Failed!', error);
//$('#myModalError .modal-body').html(error);
//$('#myModalError').modal('show');
});
};
componentDidMount() {
let self = this;
this.props.fetchActiveUser()
.then(() => {
self.initData();
})
.then(() => {
self.setState({
isReady : true
});
})
if (this.props.activeUser.name == 'AuthError') {
this.setRedirect();
}
}
passMetaBack = (meta) => {
this.setState({
title: meta.title,
description: meta.description
})
}
render() {
if (this.props.activeUser.name == 'AuthError') {
//console.log('redirect');
this.renderRedirect();
}
return (
<div>
<Header
activeUser={this.props.activeUser}
loading={this.state.loading}
version={this.state.version}
title={this.state.title}
description={this.state.description}
/>
<SideBar isReady={this.state.isReady} />
<main>
<Switch>
<Route
path="/commsmatrix/approve/:id"
component={Approve}
/>
</Switch>
</main>
</div>
);
}
}
//export default App;
function mapStateToProps(state) {
if (state.activeUser.id > 0) {
ReactGA.set({ userId: state.activeUser.id });
}
// Whatever is returned will show up as props
// inside of the component
return {
activeUser: state.activeUser
};
}
// Anything returned from this function will end up as props
// on this container
function mapDispatchToProps(dispatch) {
// Whenever getUser is called, the result should be passed
// to all our reducers
return bindActionCreators({ fetchActiveUser }, dispatch);
}
//Promote component to a container - it needs to know
//about this new dispatch method, fetchActiveUser. Make it available
//as a prop
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
index.js
import './scripts/api';
import React, { Component } from 'react'
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { BrowserRouter, Route, browserHistory } from 'react-router-dom';
import promise from 'redux-promise';
import App from './App'
import reducers from './reducers';
import 'react-quill/dist/quill.snow.css'; // ES6
require("babel-core/register");
require("babel-polyfill");
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter history={browserHistory}>
<App/>
</BrowserRouter>
</Provider>
, document.getElementById('root'));
UPDATE
so I have updated records to match reducer name. seems to work but need a way to handle errors when there are no records
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchCommsmatrix } from '../../actions/commsmatrices';
import { bindActionCreators } from 'redux';
import FontAwesome from 'react-fontawesome';
class Approve extends Component {
constructor(props) {
super(props);
this.meta = { title: 'Comms Matrix Approval', description: 'Sox approval' };
this.runOnce = false;
this.passMetaBack = this.passMetaBack.bind(this);
this.initConfirm = this.initConfirm.bind(this);
}
componentDidMount() {
this.passMetaBack;
const id = this.props.match.params.id;
this.props.fetchCommsmatrix(id);
}
passMetaBack = () => {
this.props.passMetaBack(this.meta);
};
initConfirm(){
this.runOnce = true;
}
render() {
let message = <div>Confirming...<i className="fa fa-spinner fa-spin"></i></div>;
const { commsmatrix } = this.props ;
if(!this.runOnce && this.props.isReady && Object.keys(commsmatrix).length > 0 ){
this.initConfirm();
}
return (
<div className="container-fluid">
<div className="row-fluid top-buffer">{message}</div>
</div>
);
}
}
function mapStateToProps({ commsmatrices }, ownProps) {
return { commsmatrix : commsmatrices[ownProps.match.params.id] };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{ fetchCommsmatrix },
dispatch
);
}
export default connect(mapStateToProps, { fetchCommsmatrix })(Approve);

Can I see your root index.js for completeness sake? I can't use comments unfortunately.
Why do you have const { id } = this.props.match.params.id;
Shouldn't it be justconst { id } = this.props.match.params;
Does your state object even have a records property?

Related

How to listen to query param change event in react-router v5

I'd like to be able to listen to query param change events, preferably via a hook, but anything would be nice. I can't find anything that suggests it's even possible with react-router, so other suggestions without it are welcome too.
There's nothing in react-router-dom#5 that directly does this, so you'd need to implement this yourself. You can use the useLocation hook to access the location.search value to create a URLSearchParams object, then a useEffect hook to issue the side-effect based on any specific queryString parameter updating.
Example:
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
...
const { search } = useLocation();
const searchParams = new URLSearchParams(search);
const param = searchParams.get("param");
useEffect(() => {
// issue side-effect
}, [param]);
For RRDv5 there is this recipe to abstract the access of the query params:
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
const useQuery = () => {
const { search } = useLocation();
return useMemo(() => new URLSearchParams(search), [search]);
};
...
import { useEffect } from 'react';
import { useQuery } from '../path/to/hooks';
...
const searchParams = useQuery();
const param = searchParams.get("param");
useEffect(() => {
// issue side-effect
}, [param]);
You can use useQuery and the useEffect hook to create another custom hook.
import { useEffect } from 'react';
import { useQuery } from '.';
const useQueryParam = (paramKey, cb) => {
const searchParams = useQuery();
const param = searchParams.get(paramKey);
useEffect(() => {
if (param) {
cb(param);
}
}, [param]);
};
...
import { useQueryParam } from '../path/to/hooks';
...
useQueryParam(
"myParameter",
(paramValue) => {
// do something with "myParameter" param value
},
);
did you tried below
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function MyComponent() {
const location = useLocation();
useEffect(() => {
console.log('Location changed');
}, [location]);
...
}

How to write a karma-Jasmine test for a dynamic configuration file JSON

I am very new to writing tests in Karma and Jasmine. In my case, I have a dynamic configuration file that loads before the app is initialized and that file is a JSON with a value.
configuration.json
{
"sampleConfigValue": "this is a sample value from config"
}
Configuration.ts
export interface Configuration {
sampleConfigValue: string;
}
ConfigurationService.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Configuration } from './configuration';
#Injectable({
providedIn: 'root'
})
export class ConfigurationService {
private configData: any | undefined;
private readonly configPath: string = '../assets/demo/data/config.json';
constructor(
private http: HttpClient
) { }
async loadConfiguration(): Promise<any> {
try {
const response = await this.http.get(`${this.configPath}`)
.toPromise().then(res => this.configData = res);
return this.configData;
} catch (err) {
return Promise.reject(err);
}
}
get config(): Configuration | undefined {
return this.configData;
}
}
Exporting the ConfigurationLoader in app.module.ts
export function configLoader(injector: Injector) : () => Promise<any>
{
return () => injector.get(ConfigurationService).loadConfiguration();
}
and Provider in app.module.ts
{provide: APP_INITIALIZER, useFactory: configLoader, deps: [Injector], multi: true},
configuration.service.spec.ts
import { TestBed } from '#angular/core/testing';
import { ConfigurationService } from './configuration.service';
describe('ConfigurationService', () => {
let service: ConfigurationService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ConfigurationService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
The configuration file is working but I am wondering how to write a test case for this dynamic configuration in my project?
Your time and help will really help me :)
Thanks :)
When unit testing, you're supposed to test a code unit and mock the rest.
So create a mock then test :
// Put this in the main describe
const returnValue = {};
let httpMock: { get: jasmine.Spy };
let service: ConfigurationService;
// Put this in the main beforeEach
httpMock = {
get: jasmine.createSpy().and.returnValue(of(returnValue)),
};
service = new ConfigurationService(<any>httpMock);
// Make a meaningful test
it('Should call the endpoint and retrieve the config', (done) => {
service.loadConfiguration().then(() => {
expect(httpMock.get)
.toHaveBeenCalledOnceWith(service['configPath']);
expect(service['configData']).toBe(returnValue);
done();
});
});

React : i have following code, i am using setInterval to change text value time to time ,when i switch pages and come back text is changing reallyfast

I assume when component is rendered multiple times something happens to setinterval,but how can i fix this.
bottom code is for Store that i am using and i don't understand.someone said that i must have useffect outside component but then it gives me error.
Anyways im new to react so i need help ,everyones appriciated.Thanks.
import SmallLogo from '../img/logo.svg';
import StarskyText from '../img/starskyproject.svg';
import './Statement.css'
import { BrowserRouter as Router,Routes,Route,Link } from "react-router-dom";
import { getElementError } from '#testing-library/react';
import react, { useRef , useState, useEffect } from 'react';
import { useDispatch, useSelector } from "react-redux";
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { store } from "./appReducer";
function TempText(props) {
return <span className="yellow changetext"> {props.body} </span>;
}
function doUpdate(callback) {
setInterval(callback, 1300);
}
export default function Statement(){
const dispatch = useDispatch();
const textOptions = ["NFT", "CRYPTO", "METAVERSE", "WEB3"];
const tempText = useSelector((state) => state.tempText);
function change() {
let state = store.getState();
const index = state.index;
console.log(index);
console.log(textOptions[index]);
dispatch({
type: "updatetext",
payload: textOptions[index]
});
let newIndex = index + 1 >= textOptions.length ? 0 : index + 1;
dispatch({
type: "updateindex",
payload: newIndex
});
}
useEffect(() => {
doUpdate(change);
}, []);
var [dropdownOpen , Setdrop] = useState(false);
return(
<div>
<Link to="/">
<img className='star-fixed' alt='starlogo' src={SmallLogo}></img>
</Link>
<img className='starsky-fixed' alt='starsky-project' src={StarskyText}></img>
<div className='text-content'>
<span className='statement-text'>WEB3 IS NOT ONLY THE FUTURE.
IT’S THE ONLY FUTURE!</span>
<span className='starsk-link'>starsk.pro</span>
</div>
<div className='text-content-bottom'>
<span className='statement-text-bottom'>CREATE YOUR NEXT
<TempText body={tempText} />
<span className='flex'> PROJECT WITH
<Dropdown className="hover-drop-out" onMouseOver={() => Setdrop(dropdownOpen=true) } onMouseLeave={() => Setdrop(dropdownOpen=false)} isOpen={dropdownOpen} toggle={() => Setdrop(dropdownOpen = !dropdownOpen) }>
<DropdownToggle className='hover-drop'> STRSK.PRO </DropdownToggle>
<DropdownMenu> </DropdownMenu>
</Dropdown> </span>
</span>
</div>
</div>
)
}
import { createStore } from "redux";
const initialState = {
tempText: "NFT",
index: 1
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "updatetext":
return {
...state,
tempText: action.payload
};
case "updateindex":
return {
...state,
index: action.payload
};
default:
return state;
}
};
export const store = createStore(reducer);
You can clear your timer by calling clearTimeout function with a reference to your timer when your component unmounting.
useEffect(() => {
const timer = setInterval(change, 1300);
// in order to clear your timeout
return () => clearTimeout(timer);
}, [])

Can we set the APP_BASE_HREF in angular 6 dynamically by getting the value from http call in a service?

This is the code from app.module.ts file
{
provide: APP_BASE_HREF,
useFactory: referalCodeValidator,
deps: [ValidatorService]
}
There is a referalCodeValidator function defined in the app.module.ts file itself
export function referalCodeValidator(common: ValidatorService) {
let path = location.pathname;
let refCode = path.split("/", 2);
return common.validateReferralCode(refCode[1]);
}
The validator service is as follows:
import { Injectable } from "#angular/core";
import { Observable } from "rxjs";
import { environment } from "../../environments/environment";
import { HttpClient } from "#angular/common/http";
import { tap, catchError } from "rxjs/operators";
#Injectable()
export class ValidatorService {
appBaseHref: any;
constructor(private http: HttpClient) {}
validateReferralCode(refCode): Observable<any> {
if (refCode != "" || refCode != null) {
this.appBaseHref = null;
return this.http
.get(someurl, {
params: {
referenceCode: refCode
}
})
.pipe(catchError((error, caught) => {
console.log(error);
throw error.error;
}) as any);
}
}
}

Redux loses state when navigating to another page using react-router 'history.push'

(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:
In a react component, "SearchText", getting a text string and calling an action creator
In the action creator, using the text string to send an HTTP request to goodreads.com
In my reducer, using the action payload to set the redux state
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`}>⇐ 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');
}
}
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.