Trying to create login with react, golang and mysql - mysql

this time I am creating a login for a dashboard, the problem is that I cannot identify what is failing, I have the following configuration
HANDLER CONTROLLER
func Login(username string, pass string) map[string]interface{} {
db := GetConnection()
user := &models.User{}
// sql := "SELECT * FROM users WHERE username'" + user.Username + "'and password ='" + user.Password + "'"
if db.Where("username = ?", username).First(&user).RecordNotFound() {
return map[string]interface{}{"message": "Usuario no encontrado"}
}
passErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(pass))
if passErr == bcrypt.ErrMismatchedHashAndPassword && passErr != nil {
return map[string]interface{}{"mesagge": "Usuario o contraseña incorrectos"}
}
// buscar cuenta usuario
accounts := []models.ResponseAccount{}
db.Table("accounts").Select("id, name").Where("user_id = ?", user.ID).Scan(&accounts)
// Configuración del response
responseUser := &models.ResponseUser{
ID: user.ID,
Username: user.Username,
Accounts: accounts,
}
defer db.Close()
fmt.Println(responseUser)
//Entrada token
tokenContent := jwt.MapClaims{
"user_id": user.ID,
"expiry": time.Now().Add(time.Minute ^ 60).Unix(),
}
jwtToken := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tokenContent)
token, err := jwtToken.SignedString([]byte("TokenPassword"))
HandleErr(err)
var response = map[string]interface{}{"Message": "Bienvenido"}
response["jwt"] = token
response["data"] = responseUser
return response
}
AXIOS CONFIG
import axios from "axios";
export class AdminService {
baseUrl = "http://localhost:3001/persona/api/";
async login() {
const res = await axios.post(this.baseUrl + "login");
return res.data;
}
}
export default new AdminService();
INDEX LOGIN CONFIG
class Login extends React.Component {
state = {
form: {
"username": "",
"password": ""
},
error: false,
errormsg: ""
}
manageChange = async (e) => {
this.setState({
form: {
...this.state.form,
[e.target.name]: e.target.value,
}
})
console.log(this.state.form)
}
handleClick = (e) => {
e.preventDefault();
AdminService.login(this.state.form).then((response) => {
console.log(response)
if (response.data.status === 200) {
this.props.history.push('/dashboard');
}
else {
this.setState({
error: true,
errormsg: response.data.message
})
}
})
}
render() {
return (
<React.Fragment>
<section >
<div class="container" >
<div class="top"></div>
<div class="bottom"></div>
<div class="center">
<img src={LogoIndex} id='logo-df' />
<form >
<input placeholder="Usuario" name="username" type="text" onChange={this.manageChange} />
<input placeholder="Contraseña" name="password" type="password" onChange={this.manageChange} />
<button onClick={this.handleClick} type='submit' className='corner-button' >Entrar</button>
</form>
</div>
</div>
</section>
</React.Fragment>
)
}
}
I've tried to do other configurations, but I can't go any further, I followed some guides from platforms documentation and I don't understand what it can be

Related

React: Error-Message does not show up, while others do?

I have this simple Login-Form, in which I want to display an Error-Message, which contains Login tries (3 tries possible). Somehow, my one errormessage (loginTries) is not being displayed. Why?
My form is running the method handleSubmitAndStartTimer() when I submit it. You can find more comments and details below:
export const Login = () => {
const [errorMessages, setErrorMessages] = useState({});
const [loginFailCounter, setLoginFailCounter] = useState(2);
const [firstTimeSubmitted, setFirstTimeSubmitted] = useState(false);
const mockUserData = [
{
username: "user1",
password: "pass1",
istGesperrt: false,
},
];
const handleSubmitAndStartTimer = (event) => {
setFirstTimeSubmitted(true);
event.preventDefault();
var { benutzername, passwort } = document.forms[0];
const userData = mockUserDaten.find(
(user) => user.username === benutzername.value
);
// Compare user info
if (userData) {
if (!userData.istGesperrt) {
if (userData.password !== passwort.value) {
setLoginFailCounter(loginFailCounter - 1);
// THIS DOESNT WORK!
setErrorMessages({ name: "loginTries", message: errors.loginTries, });
// ALL THESE ERROR MESSAGES WORK!
// Invalid password
setErrorMessages({ name: "passwort", message: errors.passwort });
} else {
// Username not found
setErrorMessages({
name: "benutzername",
message: errors.benutzername,
});
}
} else {
setErrorMessages({
name: "userGesperrt",
message: errors.userGesperrt,
});
}
}
};
// This method is called to display to correct message
const renderErrorMessage = (name) =>
name === errorMessages.name && (
<div className="error">{errorMessages.message}</div>
);
const errors = {
benutzername: "invalid username",
passwort: "invalid password",
loginTries: loginFailCounter + " Tries possible!",
userGesperrt: "",
};
const renderForm = (
<div className="form">
<form onSubmit={handleSubmitAndStartTimer}>
<div className="input-container">
<label for="benutzername">Benutzername</label>
<input type="text" id="benutzername" name="benutzername" required />
{renderErrorMessage("benutzername")}
</div>
<div className="input-container">
<label for="passwort">Passwort</label>
<input type="passwort" id="passwort" name="passwort" required />
{renderErrorMessage("passwort")}
</div>
<div className="button-container">
<input type="submit" value="Log in" />
</div>
{renderErrorMessage("userGesperrt")}
// THIS MESSAGE ISNT BEING SHOWN!
{renderErrorMessage("loginTries")}
</form>
</div>
);
return (
<div className="app">
<div className="login-form">
<div className="title">Sign In</div>
{isSubmitted ? <div>User is successfully logged in</div> : renderForm}
</div>
</div>
);
};
export default Login;

Chrome firinng onChange event when month is changed for input type="date"

I am using an input type="date" to allow the user to select dates.
In Firefox/Safari/Edge the onChange event doesn't fire unless a date is selected (you can press the forward/backward month buttons and the onChange does NOT fire).
However, in Chrome, if you select a date, then open the calendar again and navigate the month, Chrome will fire the onChange event with a date value for the new month in event.target.value.
Is there a way to protect against this to get consistent behavior across browsers? I've tried setting the value for the input field to null/undefined but that hasn't helped.
import { useEffect, useState } from "react";
import { InputFieldChange } from "../../../constants";
import { format, addDays } from "date-fns";
import styles from "./date-picker-list.module.css";
export interface OwnProps {
id: string;
formData: any;
helpText: string;
label: string;
isRequired: boolean;
onChange: (value: InputFieldChange) => void;
}
export default function DatePickerList(props: OwnProps) {
const [dateList, setDateList] = useState<string[]>([]);
const [minDate, setMinDate] = useState<string>("");
const [maxDate, setMaxDate] = useState<string>("");
const [dateValue, setDateValue] = useState<string | null>(null);
const [helpTextId, setHelpTextId] = useState<string | null>(null);
const [validationError, setValidationError] = useState<string>("");
const [validationClass, setValidationClass] =
useState<string>("form-control");
useEffect(() => {
const today = new Date();
const minDate = format(addDays(today, -1), "yyyy-MM-dd");
const maxDate = format(addDays(today, 90), "yyyy-MM-dd");
setMinDate(minDate);
setMaxDate(maxDate);
setDateValue(null);
}, []);
useEffect(() => {
if (props.helpText == null) {
setHelpTextId(null);
} else {
setHelpTextId(props.id + "_helper");
}
var dateListValues = props.formData[props.id];
if (dateListValues) {
setDateList(dateListValues.split(","));
}
}, [props.helpText, props.id, props.formData]);
function isValid(valueList: string[]): boolean {
let validationError = "";
if (props.isRequired && valueList.length == 0) {
validationError = "This is a required field";
}
if (validationError != "") {
setValidationClass("form-control is-invalid");
} else {
if (props.isRequired) {
setValidationClass("form-control is-valid");
} else {
setValidationClass("form-control");
}
}
setValidationError(validationError);
return validationError == "";
}
const addDateToList = (e: any) => {
console.log("add date to tlist event CHANGE: ", e)
const dateToAdd = e.target.value;
let dateExists = dateList.filter((d) => d == dateToAdd);
if (dateExists.length == 0) {
let valueList = dateList.filter((d) => d == d);
valueList.push(dateToAdd);
isValid(valueList);
setDateList(valueList);
setDateValue(null);
props.onChange({ field: props.id, value: valueList.toString(), validationMessage: "" });
}
};
const removeDateFromList = (dateToRemove) => {
let valueList = dateList.filter((d) => d != dateToRemove);
setDateList(valueList);
if (isValid(valueList)) {
props.onChange({ field: props.id, value: valueList.toString(), validationMessage: "" });
}
else {
props.onChange({ field: props.id, value: valueList.toString(), validationMessage: "This is a required field" });
}
};
return (
<>
<label htmlFor={props.id} className="form-label">
{props.label}
{props.isRequired == true ? <span className="label-required">
*
</span> : null}
</label>
<input
type="date"
className={validationClass}
id={props.id}
name={props.id}
onChange={(e) => addDateToList(e)}
required={props.isRequired}
value={dateValue}
min={minDate}
max={maxDate}
aria-describedby={props.helpText == null ? null : helpTextId}
/>
<div className={styles.dateListContainer}>
{dateList?.map((date, index) => (
<div className={styles.dateListPill} key={index}>
<div className={styles.dateListItem}>{date}</div>
<div
className={styles.xCircleFill}
aria-label={"Remove " + date}
onClick={() => removeDateFromList(date)}
>
X
</div>
</div>
))}
</div>
{validationError == "" ? null : (
<div className="invalid-feedback">{validationError}</div>
)}
{props.helpText == null ? null : (
<div id={helpTextId} className="form-text">
{props.helpText}
</div>
)}
</>
);
}

React runs via Axios Mysql Query twice

I'm running into a double execution of my axios request in a functional react-app, which by random inserts 1 or 2 rows instead of always just 1 row into the database. Tried to wrap it in a useEffect-Hook...did not help. By logging the execution function it seems only to run once. But on the php-side it's kind of executed twice. The strange thing is, that I've implemented the same thing in two other parts of the app (just different items) and there the same code just works fine...any help very appreciated! Thx in advance!
Js-Code in React:
function ReservationObjectsDialogAdd() {
const appState = useContext(StateContext)
const appDispatch = useContext(DispatchContext)
const [name, setName] = useState()
const handleKeyPressAddDialog = e => {
if (e.which == 13) {
setReservationObject()
}
}
// add new category
async function setReservationObject() {
try {
// set new Category
const response = await Axios.post("/Main.php?cmd=setReservationObject", { name })
console.log(response.data)
appDispatch({ type: "getReservationObjects" })
appDispatch({ type: "setOpenAddDialog", data: false })
} catch (e) {
console.log(e.message)
console.log(lang.reservationObjectAddProblem)
}
}
return (
<Dialog open={appState.openAddDialog} onClose={e => appDispatch({ type: "setOpenAddDialog", data: false })} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">{lang.addTimeName}</DialogTitle>
<DialogContent>
<TextField onChange={e => setName(e.target.value)} autoFocus margin="dense" id="name" label={lang.timeName} type="text" fullWidth required={true} onKeyPress={handleKeyPressAddDialog} />
</DialogContent>
<DialogActions>
<Button onClick={e => appDispatch({ type: "setOpenAddDialog", data: false })} color="primary">
{lang.cancel}
</Button>
<Button onClick={setReservationObject} color="primary">
{lang.add}
</Button>
</DialogActions>
</Dialog>
)
}
export default ReservationObjectsDialogAdd
PHP-Side:
case "setReservationObject":
$conn = new DBConnection($host, $dbuser, $dbpassword, $db);
$post = json_decode(file_get_contents('php://input'), true);
$maxOrder = $conn->query("SELECT MAX(orderNumber) as maxorder FROM reservationObjects", [])->fetch(PDO::FETCH_ASSOC);
$maxOrder = $maxOrder['maxorder'] + 1;
$activeCategory = $conn->query("SELECT id FROM reservationCategories WHERE active=?", [1])->fetch(PDO::FETCH_ASSOC);
$conn->query("INSERT INTO reservationObjects (category,name,orderNumber,visible) values(?,?,?,?)", [$activeCategory['id'], $post['name'], $maxOrder, 1]);
break;
Here the rendering-code:
function ReservationObjects() {
const classes = useStyles()
const appState = useContext(StateContext)
const appDispatch = useContext(DispatchContext)
const [reservationObjects, setReservationObjects] = useState([])
const [images, setImages] = useState()
//sort categories
function onSortEnd({ oldIndex, newIndex }) {
let newReservationObjects = reservationObjects.map((el, i) => {
return el
})
newReservationObjects = arrayMove(newReservationObjects, oldIndex, newIndex)
setReservationObjects(newReservationObjects)
async function sortObjects(newReservationObjects) {
try {
// sort Data in DB
const response = await Axios.post("/Main.php?cmd=sortObjects", { reservationObjects: newReservationObjects })
appDispatch({ type: "getReservationObjects" })
appDispatch({ type: "getReservationItems" })
} catch (e) {
console.log(e.message)
console.log(lang.categorySortProblem)
}
}
sortObjects(newReservationObjects)
}
// sort events- part 1
function handleDragEndSortObjects(event) {
const { active, over } = event
if (active.id !== over.id) {
const tempReservationObjects = reservationObjects.map((el, i) => {
return el
})
let oldIndex = null
let newIndex = null
tempReservationObjects.map((el, i) => {
if (active.id == el.id) {
oldIndex = i
}
if (over.id == el.id) {
newIndex = i
}
})
onSortEnd({ oldIndex, newIndex })
}
}
function handleDragEndAssignObjects(event) {
console.log(event)
}
// in Sort-Mode check if the clicked target is a interface-entity
function shouldCancelStart(e) {
console.log("enter should cancel")
if (e.target.hasAttribute("isadmin")) {
if (e.target.attributes.isadmin.value) {
console.log("enter should cancel return false")
return false
}
}
if (e.target.hasAttribute("interface")) {
if (e.target.attributes.interface.value) {
console.log("enter should cancel return true")
return true
}
}
}
// initial loading of reservation objects
useEffect(() => {
async function getReservationObjects() {
try {
const response = await Axios.post("/Main.php?cmd=getReservationObjects", { isadmin: appState.isAdmin, category: appState.activeCategoryNumber }).then(response => {
setReservationObjects(response.data)
appDispatch({ type: "getReservationTimes" })
})
} catch (e) {
console.log(lang.reservationCategoriesProblem)
}
}
getReservationObjects()
}, [appState.getReservationObjectsTrigger])
//initial loading of images
useEffect(() => {
async function loadImages() {
try {
const response = await Axios.post("/Main.php?cmd=getImages")
//console.log(response.data)
setImages(response.data)
} catch (e) {
console.log(e.message)
}
}
loadImages()
}, [])
// handle mouse leave -> background Image
function handleObjectMouseLeave(e) {
appDispatch({ type: "setBackgroundImage", data: "" })
}
//handle mouse enter -> background Image
function handleObjectMouseEnter(e) {
if (e.target.hasAttribute("image")) {
let image = e.target.attributes.image.value
appDispatch({ type: "setBackgroundImage", data: image })
}
}
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates
})
)
function Draggable(props) {
const { attributes, listeners, setNodeRef, transform, isDragging, over } = useDraggable({
id: props.id,
category: props.category
})
const style = transform
? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`
}
: undefined
return (
<div ref={setNodeRef} className="reservationArea__reservationObjectDraggable" style={style} {...listeners} {...attributes}>
<ReservationObject category={props.category} key={props.id} id={props.id} name={props.name} hover={appState.hoverObjectId == props.id ? "hovering" : ""} visible={props.visible} isadmin={appState.isAdmin.toString()} id={props.id} isactive={props.active} hovered={appState.reservationItems} image={props.image} onMouseEnter={handleObjectMouseEnter} onMouseLeave={handleObjectMouseLeave} />
</div>
)
}
function sortableVsDroppable() {
if (appState.objectsSortable) {
return (
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEndSortObjects}>
<SortableContext
items={reservationObjects.map(item => {
return item.id
})}
strategy={horizontalListSortingStrategy}
className="reservationArea__reservationObjects"
>
<div className="reservationArea__reservationObjects">
{reservationObjects.map((item, i) => (
<ReservationObject key={item.id} id={item.id} name={item.name} hover={appState.hoverObjectId == item.id ? "hovering" : ""} visible={item.visible} isadmin={appState.isAdmin.toString()} id={item.id} isactive={item.active} hovered={appState.reservationItems} image={item.image} onMouseEnter={handleObjectMouseEnter} onMouseLeave={handleObjectMouseLeave} />
))}
</div>
{appState.isAdmin ? (
<Link to="/" onClick={e => appDispatch({ type: "setOpenAddDialog", data: true })} className="reservationArea__addObject">
<AddCircleOutlineIcon />
</Link>
) : (
""
)}
</SortableContext>
</DndContext>
)
} else {
console.log("assignable")
return (
<>
<div className="reservationArea__reservationObjects">
{reservationObjects.map((item, i) => (
<Draggable key={item.id} category={item.category} id={item.id} index={item.id} name={item.name} hover={appState.hoverObjectId == item.id ? "hovering" : ""} visible={item.visible} isadmin={appState.isAdmin.toString()} id={item.id} isactive={item.active} hovered={appState.reservationItems} image={item.image} onMouseEnter={handleObjectMouseEnter} onMouseLeave={handleObjectMouseLeave} />
))}
</div>
{appState.isAdmin ? (
<Link to="/" onClick={e => appDispatch({ type: "setOpenAddDialog", data: true })} className="reservationArea__addObject">
<AddCircleOutlineIcon />
</Link>
) : (
""
)}
</>
)
}
}
return (
<div className="reservationArea__reservationObjectsContainer">
<ReservationObjectsImage />
{sortableVsDroppable()}
<ReservationObjectsDialogAdd />
<ReservationObjectsDialogEdit />
<ReservationObjectsDialogDelete />
</div>
)
}
export default ReservationObjects
Finally solved. It was in the php-part. I replaced the fetching of the activeCategory directly in mysql with a parameter that I sent with Axios. Somehow the activeCategory-fetching led to to this strange behaviour, that it randmly executed the insert-statement once or twice.

Angular 6 error doesn't fire

I am learning angular and for my example using Firebase createUserWithEmailAndPassword for sign-up. This returns a promise which i have changed to observable using from.
In firebase minimum password length is 6 characters. When i provide 5 characters, in the console i see the error message but in my sign-up event, success message shows rather than error. What am i missing here?
AuthService
import * as firebase from 'firebase';
import { throwError, from } from 'rxjs';
export class AuthService{
//user sign up, its a promise so listen for errors and log
signUpUser(email: string, password: string){
//return an observable using from
return from(
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(
(authData) => {
//good
console.log("User created successfully with payload-", authData);
return authData;
}
)
.catch(
(error) => {
//error
console.log(error);
return throwError(error);;
}
)
);
}
}
Sign-up component
onSignup(form: NgForm){
const email = form.value.email;
const password = form.value.password;
this.authService.signUpUser(email, password).subscribe(
(authData) => {
alert("Signup successful");
this.router.navigate(['/sign-in']);
},
(error) => {
alert(error.message);
}
);
}
Also i am using then in the authService method. How can i do .pipe(map(return authData.json()))?
Update 1:
Following helped and i am getting my error, on successful registration i am getting redirected to the sign-in view.
Convert promise to observable
AuthService
import { from } from 'rxjs';
signUpUserNew(email: string, password: string){
var subscription = from(firebase.auth().createUserWithEmailAndPassword(email, password));
return subscription;
}
Sign-up Component
//property to hold result of sign-up error
error = '';
onSignup(form: NgForm){
const email = form.value.email;
const password = form.value.password;
//this.authService.signUpUser(email, password);
this.authService.signUpUserNew(email, password)
.subscribe(
(firebaseUser) => {
console.log(firebaseUser);
this.router.navigate(['/sign-in']);
},
(error) => {
this.error = error.message;
}
);
}
View
<h2>Register</h2>
<div class="row">
<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
<form (ngSubmit)="onSignup(f)" #f="ngForm">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" ngModel class="form-control" #email="ngModel" required email>
<span class="help-block" *ngIf="!email.valid && email.touched">Please enter a valid email!</span>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" ngModel class="form-control" #password="ngModel" required minlength="6">
<span class="help-block" *ngIf="!password.valid && password.touched && !password.errors?.minlength">Please enter a valid password!</span>
<span class="help-block" *ngIf="!password.valid && password.touched && password.errors?.minlength">Password must be at least 6 characters long</span>
</div>
<p class="error" *ngIf="error">{{ error }}</p>
<button class="btn btn-primary" type="submit" [disabled]="!f.valid">Sign Up</button>
</form>
</div>
</div>
Result
Pending
Now i still need help implementing pipe and map operators.
I am getting the following error on .json:
[ts] Property 'json' does not exists on type 'UserCredential'
onSignup(form: NgForm){
const email = form.value.email;
const password = form.value.password;
//this.authService.signUpUser(email, password);
this.authService.signUpUserNew(email, password)
.pipe(
map(
(firebaseUser) => {
return firebaseUser.json();
}
)
)
.subscribe(
(firebaseUser) => {
console.log(firebaseUser);
this.router.navigate(['/sign-in']);
},
(error) => {
this.error = error.message;
}
);
}
Firstly, I guess you should call fromPromise instead of from, so try the following:
import 'rxjs/add/observable/fromPromise';
import { Observable } from "rxjs/Observable";
signUpUser(email: string, password: string){
//return an observable using fromPromise
const obs$ = fromPromise(
firebase.auth().createUserWithEmailAndPassword(email, password)
);
// you can call .pipe() here, and it will return an observable
return obs$.pipe(
map(x => console.log('PUT YOUR MAP FUNCTION HERE.')),
filter(x => console.log('Call filter() if you want'))
);
}
And you can subscribe to this observable
const subscription = this.authService.signUpUser(email, password).subscribe(
(firebaseUser) => {
console.log('firebase user: ', firebaseUser);
alert("Signup successful");
this.router.navigate(['/sign-in']);
},
(error) => {
alert(error.message);
}
);

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.