Cant get data from api - reactjs

I trying to make request geocoding to yandex maps.
ymaps.geocode(cityname) returning a promise.
I using somthing like that
action>index.js
export function addWay(text) {
return async dispatch => {
try {
const request = await window.ymaps.geocode(text)
debugger
dispatch({
type: 'ADD_WAY',
payload: request
})
}
catch (e) {}
}
}
MarkAdd.js
import React, { Component} from 'react';
import {addWay} from '../actions/index';
import { connect } from 'react-redux';
class MarkerAdd extends Component {
constructor(props) {
super(props);
this.state = {value:''}
}
onInputChange = e => {
this.setState({ value: e.target.value})
}
keyPress = e => {
if(e.keyCode === 13){
console.log('enter', e.target.value);
this.props.addWay(this.state.value);
this.setState({ value: ''})
}
}
render() {
return(
<div className="field">
<div className="control">
<input className="input is-medium"
type="text"
placeholder="Add mark"
onKeyDown={this.keyPress}
onChange={this.onInputChange}
value={this.state.value}
>
</input>
</div>
</div>
)
}
}
export default connect(null, {addWay})(MarkerAdd);
But error say: Actions must be plain objects. Use custom middleware for async actions.
(Redux Thunk is installed and connected)
Whats wrong?
If i launch it via console it actually return promise.

If you have redux-thunk installed then you can dispatch actions from component this way:
import {addWay} from '../actions/index';
...
keyPress = e => {
if(e.keyCode === 13){
this.props.dispatch(addWay(this.state.value)); // <-- dispatch action
this.setState({ value: ''})
}
}
The action itslef must return a function that accepts dispatch:
export function addWay(text) {
return async dispatch => {
try {
const request = await window.ymaps.geocode(text)
dispatch({
type: 'ADD_WAY',
payload: request
})
}
catch (e) {}
}
}

Related

redux props receiving data twice

I am using redux to get the async data and response. In the below component when i post recipe and from server i get response through redux the success modal popup twice. reducer is running only once i have checked eveything, only component has problem. the problem could be with the lifecycle method.
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import {withRouter} from 'react-router-dom';
import {connect} from 'react-redux';
import * as actionCreators from '../../actions/recipe-action/index';
import { Modal, Button } from "antd";
import Spinner from '../../UI/spinner';
class PostRecipe extends Component {
state = {
url: '',
visible: false,
}
showModal = () => {
this.setState({ visible: true });
};
onChangeHandler = (e) => {
this.setState({[e.target.name]: e.target.value});
}
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: ""});
this.setState({ visible: false });
};
handleCancel = e => {
this.setState({ visible: false });
};
render() {
const { postRecipes } = this.props;
if(postRecipes.loading) {
return <Spinner />;
}else if(postRecipes.success.ok) {
// this success model popup twice after uploading the recipe
Modal.success({
content: "Recipe Uploaded"
});
}else if(postRecipes.failure.error) {
Modal.error({
title: "Error while uploading recipe",
});
}
return (
<div>
<div>
<Button type="primary" onClick={this.showModal}>
Add Recipe
</Button>
<Modal
title="Add Recipe"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<input
style={{ width: "100%", padding: "5px", fontSize: "15px" }}
type="text"
placeholder="enter the url"
name="url"
value={this.state.url}
onChange={this.onChangeHandler}
/>
</Modal>
</div>
</div>
);
}
}
const mapStateToProps = ({ postRecipeReducers }) => {
const { recipe: { post: postRecipes } } = postRecipeReducers;
return {
postRecipes
}
};
const mapStateToDispatch = dispatch => {
return {
recipe: (url) => dispatch(actionCreators.postRecipes(url))
}
}
export default withRouter(connect(mapStateToProps, mapStateToDispatch)(PostRecipe));
// my action creators
import {POST_RECIPE_LOADING, POST_RECIPE_SUCCESS, POST_RECIPE_FAILURE, POST_RECIPE_RESET} from '../types';
import {GET_RECIPE_LOADING, GET_RECIPE_SUCCESS, GET_RECIPE_FAILURE, GET_RECIPE_RESET} from '../types';
import Parse from 'parse';
export const postRecipes = (url) => async(dispatch) => {
try {
dispatch({type: POST_RECIPE_LOADING, payload: null});
const {data} = await Parse.Cloud.run('post_recipe', {url: url});
dispatch({type: POST_RECIPE_SUCCESS, payload: data});
} catch(e) {
dispatch({type: POST_RECIPE_FAILURE, payload: {message: e.message}})
}
}
export const getRecipes = () => async (dispatch) => {
try {
dispatch({type: GET_RECIPE_LOADING, payload: null});
const {data} = await Parse.Cloud.run('get_recipe');
dispatch({type: GET_RECIPE_SUCCESS, payload: data});
} catch(e) {
dispatch({type: GET_RECIPE_FAILURE, payload: {message: e.message}})
}
};
Try this:
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: "", visible: false});
};
state variable of class is a object with two keys: url and visible. You have to set both at once.
I would try implementing a constructor function to make sure that you have this bound to your local state.
In this code block,
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: ""});
this.setState({ visible: false });
};
you could set the whole state in one line like this,
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: "", visible: false});
}
I don't know that this will fix your problem. Just a bit of house keeping.

Target not defined react

sorry for the noob question, but I am getting a target undefined. I've tried passing the componentDidMount on my onformsubmit however React is telling me the query variable is not defined.
import React, { Component } from 'react'
import DisplayData from './DisplayData';
export default class stockSearch extends Component {
state = {
searchResult: {},
}
componentDidMount = (e) => {
const query = e.target.elements.query.value
fetch(`https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH,IOT&tsyms=USD`)
.then((response) => response.json())
.then(data => {
this.setState({ searchResult: data });
console.log(this.state.searchResult);
});
}
render() {
const { searchResult } = this.state;
return (
<form onSubmit={this.props.componentDidMount}>
<label>
Name:
<input type="text" name="query" placeholder="Search Crypto" />
</label>
<button>Search Crypto</button>
<DisplayData results={searchResult} />
</form>
);
}
}
componentDidMount is one of the React Component lifecycle methods so you shouldn't pass it as the onSubmit handler. Instead, you should create a new method, e.g fetchData, which you pass to the form's onSubmit.
If you want to also fetch data on mount, you can call your handler in componentDidMount
export default class StockSearch extends Component {
state = {
searchResult: {},
queryValue: ''
}
componentDidMount() {
fetchData('default');
}
fetchData = (query) => {
fetch(`http://something.com/${query}`)
.then(...)
.then(data => {
this.setState({ searchResult: data })
});
}
render() {
return (
<form onSubmit={() => fetchData(this.state.queryValue)}>
<input
value={this.state.queryValue}
onChange={(e) => this.setState(e.target.value)}
/>
</form>
)
}
}
A few other things I've changed:
1. React components should be UpperCamelCase
2. Generally you'll manage state in your component, for example input values
.

In React-Redux app, trying to pre-fill the default value in Edit Component with current api calls value

In my reactredux app, There is a peculiar situaton where I am currently trying to pre-fill my input field in Edit component but the thing is ,Its getting filled but not with current api calls but with last api calls that happens inside componentDidMount().I tried to clear the object too but all in vain. Kindly suggest
ProfileEdit.js component
import React, { Component } from 'react';
import '../App.css';
import {connect} from 'react-redux';
import {profileFetchDetail} from '../actions/profile';
import { withRouter } from 'react-router-dom';
class ProfileEdit extends Component {
constructor(props){
super(props);
this.state = {
firstName: '',
lastName: '',
emailId: '',
}
}
componentDidMount(){
const id = this.props.match.params.id;
this.props.profileFetchDetail(id);
this.setState({
firstName: this.props.profile.firstName,
lastName: this.props.profile.lastName,
emailId: this.props.profile.emailId
})
}
render() {
const {firstName,lastName,emailId} = this.state;
console.log(this.props.profile);
return (
<form name="profileCreate" className="profile-form">
<div className="form-control">
<label htmlFor="firstName">First Name</label><br/>
<input type="text" id="firstName" defaultValue={firstName}
name="firstName" placeholder="First Name"
/>
</div>
<div className="form-control">
<label htmlFor="LastName">Last Name</label><br/>
<input type="text" id="LastName" defaultValue={lastName}
name="lastName" placeholder="Last Name"
/>
</div>
<div className="form-control">
<label htmlFor="email">Email</label><br/>
<input type="email" id="email" defaultValue={emailId}
/>
</div>
<div className="form-action">
<input type="submit" value="Click here" />
</div>
</form>
)
}
}
const mapStateToProps = state => ({
profile: state.profile.profile
})
export default connect(mapStateToProps, {profileFetchDetail})(withRouter(ProfileEdit));
Action creators, here profileFetchDetail() is of our interest
import api from '../api';
// profile create
export const profileAdd = (formData, history) => async dispatch => {
console.log(formData);
const config = {
headers : { 'Content-Type': 'application/json' }
}
try {
await api.post('/api/profile/create', formData, config);
dispatch({ type: 'CREATE_PROFILE', payload: formData });
history.push('/list');
} catch (error) {
console.log(error);
}
}
// profile get all list
export const profileFetch = () => async dispatch => {
try {
const res = await api.get('/api/profile/list');
dispatch({ type: 'GET_PROFILE', payload: res.data });
} catch (error) {
console.log(error);
}
}
// profile get single list item corresponding to id
export const profileFetchDetail = (id) => async dispatch => {
dispatch({ type: 'CLEAR_PROFILE' });
try {
const res = await api.get(`/api/profile/${id}`);
dispatch({ type: 'GET_PROFILE_SINGLE', payload: res.data });
} catch (error) {
console.log(error);
}
}
// profile delete
export const profileDelete = (id) => async dispatch => {
dispatch({ type: 'CLEAR_PROFILE' });
try {
const res = await api.delete(`/api/profile/${id}/delete`);
dispatch({ type: 'DELETE_PROFILE', payload: res.data });
dispatch(profileFetch());
} catch (error) {
console.log(error);
}
}
ProfileReducers
const initialState = {
profiles:[],
profile:{}
};
export default (state = initialState, action) => {
switch (action.type) {
case 'CREATE_PROFILE':
return {...state, profiles: [...state.profiles, action.payload]};
case 'GET_PROFILE':
return {...state, profiles: action.payload};
case 'GET_PROFILE_SINGLE':
return {...state, profile: action.payload};
case 'CLEAR_PROFILE':
return {...state, profile: {}};
case 'DELETE_PROFILE':
return {...state, profiles: state.profiles.filter( item => item._id !== action.payload) };
default:
return state;
}
};
First time it loads perfectly on clicking edit button then the issue happens on clicking any other edit button.Pasting the example of 2 api calls inside componentDidMount().
In the attached image, the last api request in sequence displayed is the currently made request.Api made detail
Note: Till now I am not trying to edit it just prefilling data,where issue happening.

Reducer not holding the server response object - reactjs

I am trying to learn react and got an issue in redux.
The code is as follows.
import * as postActions from '../../redux/actions/postActions';
class PostForm extends Component {
handleSubmit = (e) => {
this.props.getBooks()
}
render() {
return (
<div>
<h1>Create Post</h1>
<form onSubmit={this.handleSubmit}>
<input required type="text" ref={(input)=>this.getTitle = input}
placeholder="Enter Post Title"/>
<br /><br />
<textarea required rows="5" ref={(input)=>this.getMessage = input} cols="28"
placeholder="Enter Post" />
<br /><br />
<button>Post</button>
</form>
</div>
);
}
}
export default connect(state => ({
...state.books,
}),{
...postActions,
})(PostForm);
As you can see, when the form is submitted, this.props.getBooks() action is called.
The action is defined as follows.
import * as types from '../constants/actionTypes';
export function getBooks(obj={}) {
const api = types.API_URL_BOOKS;
return dispatch => {
return dispatch({
type: types.ACTION_BOOK_LIST,
promise: client => client.get(api).then((data) => {
return data;
}),
});
};
}
I am using axios for making api calls. The issue is that I am not getting the server response in reducer. The reducer is as follows.
import * as types from '../constants/actionTypes';
export default function reducer(state = {}, action = {}) {
switch (action.type) {
case types.ACTION_BOOK_LIST:
return {
...state,
books : action.result.data.response.books
};
default:
return state;
}
}
On debugging, i found that the action is having only the following
{type: "BOOK_LIST"}
After that, in the apiMiddlewareCreator (which is defined in clientMiddleware.js), i am getting the server response
function apiMiddlewareCreator(client) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, client);
}
const { promise, type, hideLoader, ...rest } = action;
if (!promise) {
return next(action);
}
next({ ...rest, type: `${type}` });
const actionPromise = promise(client);
actionPromise
.then(result => {
debugger
if(result.data.success === false) throw result.data.message;
if (result && result.data && result.data.response) {
switch(action.type) {
default:
//nothing
}
}
return next({ ...rest, result, type: `${type}_SUCCESS`, originalType: type })
})
return actionPromise;
};
}
reducers/index.js
import { combineReducers } from 'redux';
//import { routerReducer as routing } from 'react-router-redux';
import postReducer from './postReducer';
const appReducer = combineReducers({
// routing,
books: postReducer,
});
const rootReducer = (state, action) => {
return appReducer(state, action)
};
export default rootReducer;
actionTypes.js
export const ACTION_BOOK_LIST = 'BOOK_LIST';
I need the data to be available in the reducer. That is, action.result.data.response.books should contain the response from server.
I am not sure on how to fix this.
Any help would be appreciated.
I think it's because you're dispatching the action before the promise is resolved.
Only dispatch the action once the promise has been resolved, e.g:
import * as types from '../constants/actionTypes';
export function getBooks(obj={}) {
const api = types.API_URL_BOOKS;
return dispatch => {
client.get(api).then(data => {
dispatch({
type: types.ACTION_BOOK_LIST,
books: data.response.books
});
});
};
}
Your reducer will need to be updated to:
case types.ACTION_BOOK_LIST:
return { ...state,
books: action.books
};

How to pass my error message from server called by axios to my component

I'm really new to React. I have an axios request in my actions I want my error message to pass on the component I have this code :
import axios from 'axios';
import setAuthorizationToken from '../utils/setAuthorizationToken';
import jwtDecode from 'jwt-decode';
import { SET_CURRENT_USER, BASE_URL } from './types';
const instance = axios.create({
baseURL: BASE_URL
});
export function setCurrentUser(user) {
return {
type: SET_CURRENT_USER,
user
};
}
export function logout() {
return dispatch => {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
setAuthorizationToken(false);
dispatch(setCurrentUser({}));
}
}
export function login(data) {
return dispatch => {
return instance.post('/authenticate', data).then(function(response) {
const token = response.data.accessToken;
const refreshToken = response.data.refreshToken;
localStorage.setItem('accessToken', token);
localStorage.setItem('refreshToken', refreshToken);
setAuthorizationToken(token);
dispatch(setCurrentUser(jwtDecode(token)));
})
.catch(function(error){
console.log('error: ', error.response.data);
});
}
}
Here is my Component:
import React from 'react';
import TextFieldGroup from '../common/TextFieldGroup';
import validateInput from '../../server/validations/login';
import { connect } from 'react-redux';
import { login } from '../../actions/authActions';
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
errors: {},
isLoading: false
};
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
isValid() {
const { errors, isValid } = validateInput(this.state);
if (!isValid) {
this.setState({ errors });
}
return isValid;
}
onSubmit(e) {
e.preventDefault();
if (this.isValid()) {
this.setState({ errors: {}, isLoading: true });
this.props.login(this.state).then(
(res) => this.context.router.push('/'),
(error) => this.setState({ errors: error.response.data , isLoading: false }),
);
}
}
onChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
render() {
const { errors, username, password, isLoading } = this.state;
return (
<form onSubmit={this.onSubmit}>
<h1>Login</h1>
{ errors.message && <div className="alert alert-danger">{errors.message}</div> }
<TextFieldGroup
field="username"
label="Username"
value={username}
error={errors.username}
onChange={this.onChange}
/>
<TextFieldGroup
field="password"
label="Password"
value={password}
error={errors.password}
onChange={this.onChange}
type="password"
/>
<div className="form-group"><button className="btn btn-primary btn-lg" disabled={isLoading}>Login</button></div>
</form>
);
}
}
LoginForm.propTypes = {
login: React.PropTypes.func.isRequired
}
LoginForm.contextTypes = {
router: React.PropTypes.object.isRequired
}
export default connect(null, { login })(LoginForm);
Here is the console.log
error: Object {code: "UNAUTHORIZED", message: "Invalid username or password."}
Currently I don't know to pass my error message to component. I'm really new to React and Redux
First you have to add the initial state on reducer. For example
authReducer.js
const initialState = {
... // another state
errors: {}
}
function yourReducer(state = initialState, action) {
case 'SHOW_ERROR':
return {
...state,
errors: action.message
}
default:
return state
}
On login action dispatch the 'SHOW_ERROR'
authActions.js
export function login(data) {
return dispatch => {
return instance.post('/authenticate', data).then(function(response) {
...
// success
})
.catch(function(error){
// fail
dispatch({ type: 'SHOW_ERROR', message: error.response.data })
});
}
}
Then you need to map redux state to be a props on your component
LoginComponent.js
function mapStateToProps(state) {
return {
you: may.return.another.state.here,
errors: state.yourReducerName.errors
}
}
export default connect(mapStateToProps, { login })(LoginForm);
Finally, you can call errors as a props on your Component
class LoginForm extends React.Component {
...
render() {
const { errors, username, password, isLoading } = this.state;
const { errors } = this.props // errors from redux state
return (
<form onSubmit={this.onSubmit}>
<p>{errors.message}</p>
<h1>Login</h1>
...
<div className="form-group"><button className="btn btn-primary btn-lg" disabled={isLoading}>Login</button></div>
</form>
);
}
}
Don't forget to validate the prop types. Good luck!

Resources