JSON return [Object][Object] - reactjs

I'm trying to get data from json file, but it always return [object][object]
Here is json file https://my-json-server.typicode.com/khanh21011999/demo/user
Here is request function to get data
export function requestGetUser() {
return axios({
method: 'get',
url: 'https://my-json-server.typicode.com/khanh21011999/demo/user',
});
}
Here is the method i use to get data
function* loginSaga(action) {
console.log('Saga is working')
const getJson = yield call(requestGetUser) //same
const getJsonData = JSON.stringify(getJson)
const getJsonUsername = String(getJsonData.username)
console.log('GetJson '+getJson)
const getJsonPassword = String(getJsonData.password)
if (String(action.data.username) === getJsonUsername) {
if (String(action.data.password) === getJsonPassword) {
console.log('saga login success')
yield put({type: 'LOGIN_SUCCESS'})
SaveToAsyncStorage(action.data)
}
else {
console.log('saga password fail')
}
}
else {
console.log("saga user fail")
}
}
export {loginSaga}
It return like this
Weird things is i use a online tutorial to get data, it work with that(data show in above image)
worked method
export function* handleGetUser(action) {
try {
const response = yield call(requestGetUser); //same
const { data } = response;
yield put(setUser(data));
} catch (error) {
console.log(error);
}
}
setUser
export const setUser = (user) => ({
type: actionList.SET_USER,
user,
});
GetUserInfo
export const GetUserInfo = (user, password) => {
return{
type: actionList.GET_USER_INFO,
data: {user, password}, //same??
}
};
Here is export function
export function* watchSaga() {
yield takeLatest(GET_USER, handleGetUser); //work
yield takeLatest(GET_USER_INFO,loginSaga) //notwork
}
One different is the worked method have reducer
const initState = {
user: undefined,
};
const User = (state = initState, action) => {
switch (action.type) {
case actionList.SET_USER:
const {user} = action;
return {...state,user};
default:
return state;
}
};
export default User;
But my method have none,(i thoght data was save in state action)

console.log('GetJson ' + getJson); You're printing a concatenation of strings and objects. Modify it to console.log('GetJson ', getJson);
Besides, you should return the res.data from axios.get() method, see Response Schema. You will get the plain object of JavaScript, there is no need to use JSON.stringify().
import axios from 'axios';
export function requestGetUser() {
return axios({
method: 'get',
url: 'https://my-json-server.typicode.com/khanh21011999/demo/user',
}).then((res) => res.data);
}

Related

React - useReducer with asynchronous CRUD operations

I am trying to figure out how to use useReducer with asynchronous CRUD operations (using the fetch API).
In my mind the reducer function would look like this:
async function reducer(action, state) {
const newState = {...state};
switch (action.type) {
case types.ADD_ITEM:
try {
const {item} = await addItem(action.payload.item);
newState.items.push(item);
}
catch (e) {
newState.error = e.message;
}
break;
case types.REMOVE_ITEM:
try {
await removeItem(action.payload.itemId);
newState.items = newState.items.filter(value.id !== action.payload);
}
catch (e) {
newState.error = e.message;
}
break;
case types.EDIT_ITEM:
try {
const {item} = await editItem(action.payload.itemId, action.payload.item);
newState.items[newState.items.findIndex(value => value.id === action.payload.itemId)] = item;
}
catch (e) {
newState.error = e.message;
}
break;
}
return newState;
}
These would be the fetch functions:
async function addItem(item) {
const response = await fetch('addItemRoute', {
method: "POST",
body: JSON.stringify({
item
})
});
return response.json();
}
async function removeItem(itemId) {
const response = await fetch('removeItemRoute/' + itemId, {
method: "DELETE"
});
return response.json();
}
async function editItem(itemId, item) {
const response = await fetch('editItemRoute/'+ itemId, {
method: "PUT",
body: JSON.stringify({
item
})
});
return response.json();
}
But the reducer function cannot be an async function.
What would be the standard way to handle concepts like this?
Any help/reference is truly appreciated.
I think you misunderstood the role of reducer. In React world, there is a thing call global state (a way to pass values down to children without having to pass as props), which traditionally being handled by another package called Redux. The reducer only handle taking whatever you dispatch, decide what action to take to update the global state based on the type of action which is not asynchronous. The action is what you use to decide what to dispatch and also the way for you to get the data to dispatch so usually all the HTTP calls occurs here. Since useReducer will returns for you the current state and the dispatch function as well, you can basically pass this dispatch to your action. You can take a look at my example below based on your example for clearer image of what you might want to do:
You may want to put all your action in a action file called action.js like this:
async function addItem(item, dispatch) {
const response = await fetch('addItemRoute', {
method: "POST",
body: JSON.stringify({
item
})
});
return dispatch({
type: "ADD_ITEM",
payload: response.json()});
}
async function removeItem(itemId, dispatch) {
const response = await fetch('removeItemRoute/' + itemId, {
method: "DELETE"
});
return dispatch({
type: "ADD_ITEM",
payload: response.json()
});
}
async function editItem(itemId, item, dispatch) {
const response = await fetch('editItemRoute/'+ itemId, {
method: "PUT",
body: JSON.stringify({
item
})
});
return dispatch({
type: "ADD_ITEM",
payload: response.json()
});
}
Then in your reducer, you can do the regular without having to call the fetch or async calls like this:
async function reducer(action, state) {
const newState = {...state};
switch (action.type) {
case types.ADD_ITEM:
try {
const {item} = action.payload;
newState.items.push(item);
}
catch (e) {
newState.error = e.message;
}
break;
case types.REMOVE_ITEM:
try {
newState.items = newState.items.filter(value.id !== action.payload);
}
catch (e) {
newState.error = e.message;
}
break;
case types.EDIT_ITEM:
try {
const {item} = action.payload;
newState.items[newState.items.findIndex(value => value.id === action.payload.itemId)] = item;
}
catch (e) {
newState.error = e.message;
}
break;
}
return newState;
}
Then in your component with the button that you want to execute this, you can do something like this:
const MyComponent = ()=> {
...
const [state, dispatch] = useReducer(reducer, initialState);
...
return (
...
<button onClick={addItem(item, dispatch)}/>
...
}
You can reference the core concept of redux here which IMO explains very clearly the functionality of reducer, dispatch, actions and global state. If you want you can also tried out Redux as well, here's their tutorial.

It seems to ignore the Async / Await

On a React page, I have a method called goOut. This method calls upon a Redux action > Node controller > Redux reducer. I can confirm that the correct data values are returned inside the Redux action, the controller method, and the reducer. However, nonetheless, at point 1 below inside the goOut method, it returns undefined.
What am I doing wrong / how could it return undefined if the the reducer is returning the correct values? It is as if the await inside the goOut method is not working...
React page:
import { go_payment } from "../../appRedux/actions/paymentAction";
<button onClick={this.goOut}>
Button
</button>
async goOut(ev) {
try {
const data = { user: parseInt(this.state.userId, 10) };
let result = await this.props.go_payment({data});
console.log(result);
// 1. RETURNS UNDEFINED. As if it tries to execute this line before it has finished the previous line.
{
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{go_payment}, dispatch
);
};
Redux Action:
export const go_payment = (data) => {
let token = getAuthToken();
return (dispatch) => {
axios
.post(`${url}/goController`, data, { headers: { Authorization: `${token}` } })
.then((res) => {
if (res.status === 200) {
// console.log confirms correct data for res.data
return dispatch({ type: GO_SUCCESS, payload: res.data });
})
}
}
Node controller method:
Returns the correct data in json format.
Reducer:
export default function paymentReducer(state = initial_state, action) {
switch (action.type) {
case GO_SUCCESS:
// console.log confirms action.payload contains the correct data
return { ...state, goData: action.payload, couponData: "" };
}
}

How to chain promise to the return of API call using redux-pack in a React and Redux app?

I'm getting the error .then() is not a function while attempting to write a React app test w/ jest/enzyme. I don't think the test code is the problem, but for reference, I am following this Redux example.
Here's the code that throws the error:
return store.dispatch(actions.fetchFiles()).then(() => {
expect(store.getActions()).toEqual(expectedActions)
});
My client's codebase uses redux-pack and some conventions that I am not familiar with and I'm having a hard time deciphering where the actual promise is being executed and thus how to chain a "then" function when calling it from my test code as shown in the redux example link posted above.
I've tried to do some debug logging and some variations on the syntax, for example, I attempted to call .then directly on actions.fetchFiles() since I was under the impression that it was the call that actually returned the promise, but it didn't work. I'm getting a tad lost in all this code and questioning where the promise is actually getting executed/returned.
The basic structure of the code is as follows:
A connected Container component that fetches a list of files from an API and then dispatches.
The actual page works fine, but my attempts to test (like the Redux article referenced above) blow up.
Here are what I believe to be the relevant blocks of code in play:
Container component
componentDidMount() {
const { actions } = this.props;
actions.fetchUpload();
}
const mapStateToProps = state => ({
...state.upload,
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators({
...uploadActions,
}, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(UploadContainer);
actions.js
import api from '../../core/api';
export const FETCH_FILES = 'upload/fetch-files';
const actions = {
fetchFiles: () => ({
type: FETCH_FILES,
promise: api.upload.getFiles()
})
};
actions.fetchUpload = () => (dispatch) => {
dispatch(actions.fetchFiles());
};
export default actions;
reducer.js
import { handle } from 'redux-pack';
import { FETCH_FILES } from './actions';
const initialState = {
files: []
};
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_FILES:
return handle(state, action, { // this is redux-pack syntax
success: (s, a) => ({ ...s, files: a.payload.files })
});
default:
return state;
}
};
upload.js (api.upload.getFiles)
export default http => ({
getFiles: () => http.get('/file')
});
api.js - uses Axios
import Axios from 'axios';
import { SubmissionError } from 'redux-form';
import queryString from 'query-string';
import upload from './upload';
const axios = Axios.create({
baseURL: '/api/',
withCredentials: true,
paramsSerializer: params => queryString.stringify(params),
});
class HttpError extends Error {
constructor(status, error, errors = {}) {
super(`Http Error: ${status}`);
this.status = status;
this.error = error;
this.errors = errors;
}
getReduxFormError = defaultError => new SubmissionError({
_error: this.error || defaultError,
...this.errors,
});
}
const handleUnauth = (method, url, options) => (err) => {
const { status } = err.response;
if (status === 401) {
return axios.get('/users/refresh')
.then(() => method(url, options))
.catch(() => Promise.reject(err));
}
return Promise.reject(err);
};
const handleHttpError = (err) => {
const { status, data: { message = {} } } = err.response;
if (typeof message === 'string') {
return Promise.reject(new HttpError(status, message));
}
return Promise.reject(new HttpError(status, null, message));
};
const http = {
get: (url, options) => axios.get(url, options)
.then(response => response.data)
.catch(handleUnauth(axios.get, url, options))
.catch(handleHttpError),
post: (url, data, options) => axios.post(url, data, options)
.then(response => response.data)
.catch(handleUnauth(axios.post, url, options))
.catch(handleHttpError),
patch: (url, data, options) => axios.patch(url, data, options)
.then(response => response.data)
.catch(handleUnauth(axios.patch, url, options))
.catch(handleHttpError),
delete: (url, options) => axios.delete(url, options)
.then(response => response.data)
.catch(handleUnauth(axios.delete, url, options))
.catch(handleHttpError),
};
export default {
upload: upload(http)
};
I was expecting the tests to pass because the returned object should match the expected actions, but it errors. Here's the full message:
FAIL src/modules/upload/upload.test.js
Upload module actions › Returns an array of files when calling actions.fetchFiles
TypeError: store.dispatch(...).then is not a function
43 | // );
44 |
> 45 | return store.dispatch(actions.fetchFiles()).then(() => {
|
46 | expect(store.getActions()).toEqual(expectedActions);
47 | });
48 | });
at Object.then (src/modules/upload/upload.test.js:45:52)
When/where is the promise getting returned and how can I chain a .then function like the Redux test example linked above?

render view after a post request in react/redux

I have post method helper where I'm making the rest calls to the server which is basically running but the view/container is not rerendering after the call.
export function postData(action, errorType, isAuthReq, url, dispatch, data) {
const requestUrl = API_URL + url;
let headers = {};
if (isAuthReq) {
headers = {headers: {'Authorization': cookie.load('token')}};
}
axios.post(requestUrl, data, headers)
.then((response) => {
dispatch({
type: action,
payload: response.data
});
})
.catch((error) => {
errorHandler(dispatch, error.response, errorType)
});
}
I'm getting the the following error: dispatch is not defined in the browser when I'm calling this method
my call from the container is as followed:
handleFavorite(buildingId) {
const url = `/building/${buildingId}/toogle-favorite`;
postData(FETCH_All_BUILDING, AUTH_ERROR, true, url, this.props.dispatch, {});
}
This is how my connect method is looks like:
function mapStateToProps(state) {
return {
buildings: state.building.buildings,
error: state.building.error,
userId: state.auth.userId
}
}
export default connect(mapStateToProps, {buildingsAll})(BuildingAll);
My Question is...
How can I re render my view? This dispatch that I want to give to the method is not available. Is there a possibility to bind that rest to the state perhaps with mapDispatchToProps. Any idea how I can solve that problem, I'm fairly new to react/redux - it's my first side project in that lib.
Thanks
Update 1
I have updated the code but getting the next error and my view is now not rendering (nothing showing).
mapDispatchToProps() in Connect(BuildingAll) must return a plain object. Instead received function
bundle.js:26 Uncaught TypeError: finalMergeProps is not a function
const mapDispatchToProps = (dispatch) => bindActionCreators(postDataThunk, dispatch);
export default connect(mapStateToProps, mapDispatchToProps, {buildingsAll})(BuildungAll);
You need to bind your action creators in your container
const { bindActionCreators } = require("redux");
const mapStateToProps = (state) => {
return {
buildings: state.building.buildings,
error: state.building.error,
userId: state.auth.userId
}
}
const mapDispatchToProps = (dispatch) => bindActionCreators(YourActions, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(BuildingAll);
And then your action becomes something like this:
import thunk from 'redux-thunk';
const postData = (action, errorType, isAuthReq, url, data) => {
return (dispatch) => {
const requestUrl = API_URL + url;
let headers = {};
if (isAuthReq) {
headers = { headers: { 'Authorization': cookie.load('token') } };
}
axios.post(requestUrl, data, headers)
.then((response) => {
dispatch({
type: action,
payload: response.data
});
})
.catch((error) => {
errorHandler(dispatch, error.response, errorType)
});
};
};
Because your postData might have a few side effects because it's fetching something asynchronously, you'll need a thunk
Read this article on it: http://redux.js.org/docs/advanced/AsyncActions.html

how to async/await redux-thunk actions?

action.js
export function getLoginStatus() {
return async(dispatch) => {
let token = await getOAuthToken();
let success = await verifyToken(token);
if (success == true) {
dispatch(loginStatus(success));
} else {
console.log("Success: False");
console.log("Token mismatch");
}
return success;
}
}
component.js
componentDidMount() {
this.props.dispatch(splashAction.getLoginStatus())
.then((success) => {
if (success == true) {
Actions.counter()
} else {
console.log("Login not successfull");
}
});
}
However, when I write component.js code with async/await like below I get this error:
Possible Unhandled Promise Rejection (id: 0): undefined is not a function (evaluating 'this.props.dispatch(splashAction.getLoginStatus())')
component.js
async componentDidMount() {
let success = await this.props.dispatch(splashAction.getLoginStatus());
if (success == true) {
Actions.counter()
} else {
console.log("Login not successfull");
}
}
How do I await a getLoginStatus() and then execute the rest of the statements?
Everything works quite well when using .then(). I doubt something is missing in my async/await implementation. trying to figure that out.
The Promise approach
export default function createUser(params) {
const request = axios.post('http://www...', params);
return (dispatch) => {
function onSuccess(success) {
dispatch({ type: CREATE_USER, payload: success });
return success;
}
function onError(error) {
dispatch({ type: ERROR_GENERATED, error });
return error;
}
request.then(success => onSuccess, error => onError);
};
}
The async/await approach
export default function createUser(params) {
return async dispatch => {
function onSuccess(success) {
dispatch({ type: CREATE_USER, payload: success });
return success;
}
function onError(error) {
dispatch({ type: ERROR_GENERATED, error });
return error;
}
try {
const success = await axios.post('http://www...', params);
return onSuccess(success);
} catch (error) {
return onError(error);
}
}
}
Referenced from the Medium post explaining Redux with async/await: https://medium.com/#kkomaz/react-to-async-await-553c43f243e2
Remixing Aspen's answer.
import axios from 'axios'
import * as types from './types'
export function fetchUsers () {
return async dispatch => {
try {
const users = await axios
.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => res.data)
dispatch({
type: types.FETCH_USERS,
payload: users,
})
} catch (err) {
dispatch({
type: types.UPDATE_ERRORS,
payload: [
{
code: 735,
message: err.message,
},
],
})
}
}
}
import * as types from '../actions/types'
const initialErrorsState = []
export default (state = initialErrorsState, { type, payload }) => {
switch (type) {
case types.UPDATE_ERRORS:
return payload.map(error => {
return {
code: error.code,
message: error.message,
}
})
default:
return state
}
}
This will allow you to specify an array of errors unique to an action.
Another remix for async await redux/thunk. I just find this a bit more maintainable and readable when coding a Thunk (a function that wraps an expression to delay its evaluation ~ redux-thunk )
actions.js
import axios from 'axios'
export const FETCHING_DATA = 'FETCHING_DATA'
export const SET_SOME_DATA = 'SET_SOME_DATA'
export const myAction = url => {
return dispatch => {
dispatch({
type: FETCHING_DATA,
fetching: true
})
getSomeAsyncData(dispatch, url)
}
}
async function getSomeAsyncData(dispatch, url) {
try {
const data = await axios.get(url).then(res => res.data)
dispatch({
type: SET_SOME_DATA,
data: data
})
} catch (err) {
dispatch({
type: SET_SOME_DATA,
data: null
})
}
dispatch({
type: FETCHING_DATA,
fetching: false
})
}
reducers.js
import { FETCHING_DATA, SET_SOME_DATA } from './actions'
export const fetching = (state = null, action) => {
switch (action.type) {
case FETCHING_DATA:
return action.fetching
default:
return state
}
}
export const data = (state = null, action) => {
switch (action.type) {
case SET_SOME_DATA:
return action.data
default:
return state
}
}
Possible Unhandled Promise Rejection
Seems like you're missing the .catch(error => {}); on your promise. Try this:
componentDidMount() {
this.props.dispatch(splashAction.getLoginStatus())
.then((success) => {
if (success == true) {
Actions.counter()
} else {
console.log("Login not successfull");
}
})
.catch(err => {
console.error(err.getMessage());
}) ;
}
use dispatch(this.props.splashAction.getLoginStatus()) instead this.props.dispatch(splashAction.getLoginStatus())

Resources