Parsing JSON response data in React action - javascript

I'm making a call to my server to get some json data from a 3rd party API (Yelp). I'm using axios and redux-promise to set the state in redux so I can use it in react. The data that gets returned is confusing me.
I want to set the state to an array of business data returned from the Yelp API. Currently, this action works if I pass in a basic string as the payload. I'm not sure how I get this JSON data from the api response and then manipulate it in react from the redux state.
Here is what the data looks like
Here is my action js file
import axios from 'axios';
export const ZIP_CODE = 'ZIP_CODE';
const ROOT_URL = 'http://www.localhost:3000/v1/location/1/1/2/4000'
function getBusinesses(json) {
const business_data = json['data']['businesses']
console.log(business_data)
return business_data;
}
/*
* Updates the users location
*/
export function updateZip(zipCode) {
const request = axios.get(ROOT_URL)
.then(getBusinesses)
return {
type: ZIP_CODE,
payload: request
}
}

I found a fix for my solution which involved some awkward tinkering and fixing bugs on my end.
Fix 1 - Updating the store
I've added thunk and it works well
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, composeEnhancers(
applyMiddleware(ReduxThunk)
));
Fix 2 - Integration of thunk into action
I parsed the json in an intial function then returned it to the action that gets sent to the reducer
import axios from 'axios';
export const ZIP_CODE = 'ZIP_CODE';
const ROOT_URL = 'http://www.localhost:3000/v1/location/1/1/2/4000'
function getBusinesses(res) {
return {
type: ZIP_CODE,
payload: res['data']['businesses']
}
}
export function updateZip(zipCode) {
return function(dispatch) {
axios.get(ROOT_URL)
.then((response) => {
dispatch(getBusinesses(response))
})
}
}

Related

How can I assign data from Http response in const Component?

My goal is to ultimately create add a shopping cart functionality using context API. In order to do so I need to get the products from my database and store it in an array.
Currently, the challenge I'm facing is how to retrieve the data from the axios response and store it in a variable that will be passed on within a const component. Apparently, the issue is that the variable gets passed to the child before the Axios response is complete.
I tried using the await keyword, but got an error regarding not being in an async function. Hence, I tried plugging the async keyword but that didn't work as it yielded errors.
I was able to retrieve data from axios within class components with success, however, I am unable to do so in these const.
Here is my code:
Context.js
import { createContext, useContext, useReducer } from "react";
import React from "react";
import ProductService from "../services/ProductService";
import { cartReducer } from "./Reducers";
const Cart = createContext();
const Context = ({ children }) => {
let products = [];
console.log("part1", products);
ProductService.getAllProducts().then((res) => {
products = res.data; //Also tried setState({ products : res.data})
console.log("response: ", products);
});
const [state, dispatch] = useReducer(cartReducer, {
products: products,
cart: [],
});
return <Cart.Provider value={{ state, dispatch }}>{children}</Cart.Provider>;
};
export const CartState = () => {
return useContext(Cart);
};
export default Context;
ProductService.js
import axios from "axios";
const PRODUCT_BASE_URL = "http://localhost:8080/api/v1/";
class ProductService {
getAllProducts() {
return axios.get(PRODUCT_BASE_URL + "products");
}
getProductsByCategory(category) {
return axios.get(PRODUCT_BASE_URL + "products/" + category);
}
getProductById(id) {
return axios.get(PRODUCT_BASE_URL + "product/" + id);
}
}
export default new ProductService();
Reducers.js
export const cartReducer = (state, action) => {
switch (action.type) {
default:
return state;
}
};
HomeComponenet.jsx
import React from "react";
import SlideShowComponent from "./SlideShowComponent";
import HomeCategoriesComponent from "./HomeCategoriesComponent";
import FeaturedProductsComponent from "./FeaturedProductsComponent";
import { CartState } from "../context/Context";
// class HomeComponent extends React.Component
function HomeComponent() {
const { state } = CartState();
console.log("Cart Inside the Home Component: ", state);
return (
<>
<SlideShowComponent />
<div>
<HomeCategoriesComponent />
</div>
<div>
<FeaturedProductsComponent />
</div>
</>
);
}
export default HomeComponent;
First of all, I've created a fixed example here: https://stackblitz.com/edit/react-jqtcia?file=src/Context.js
You are correct, you need to await the result from axios. In React, we use the useEffect hook for things with side effects or that should not be done as part of the render. Renders in react should be non blocking, that is they should not be dependent on things like data fetching.
A simple example of this would be if we needed it in local state. This example renders without the data, then re-renders once the data is available.
const [products, setProducts] = useState([]);
useEffect(async () => {
const { data } = await ProductService.getAllProducts();
setProducts(data);
}, []);
return <div>{products.length > 0 ? `${products.length} products` : 'Loading...'</div>
NOTE: the , []); means that this will fire once, when the first render happens.
This fixes the first part of your problem, getting the result out of the request/axios.
However, the second part and most important part is that you weren't using this value. You were attempting to insert the result as part of the initial state, but this was empty by the time it was created. As you are using reducers (useReducer), this means you need to dispatch an action for each event and handle all the relevant events to data fetching in the reducer. That means, you should need to be able to handle:
Some data is in a loading state (e.g., pagination or first load)
The data failed to load
The data is partially loaded
The data has fully loaded
I've created a minimal happy example (there is no pagination and data fetching always succeeds):
Context.js
useEffect(async () => {
const { data: loadedProducts } = await ProductService.getAllProducts();
console.log('response: ', JSON.stringify(loadedProducts));
dispatch({ type: 'PRODUCTS_LOADED', products: loadedProducts });
console.log(state);
}, []);
Reducers.js
export const cartReducer = (state, action) => {
console.log(action);
switch (action.type) {
case 'PRODUCTS_LOADED':
const newState = { ...state, products: action.products };
console.log(newState);
return newState;
default:
return state;
}
};
In fact, if you'd done this with your original code, it would've worked:
ProductService.getAllProducts().then((res) => {
dispatch({ type: 'PRODUCTS_LOADED', products : res.data});
console.log("response: ", products);
});
However, this has a different bug: It will refetch the data and then dispatch the event each time Context.js is re-rendered.
Since you've asked for more information in your comment, I'll provide it here (comments were not big enough).
I've linked the relevant API documentation for the hooks above, these provide pretty good information. React does a pretty good job of explaining the what, and why of these hooks (and the library itself). Seriously, if you haven't read their documentation, you should do so.
Additional resources:
What, when and how to use useEffect - In short, if you have something to do that isn't rendering such as data fetching, it's a side-effect and should be in a useEffect.
What is a reducer in JavaScript/React/Redux - Reducers are a pattern to make shared state easier to manage and test. The basic idea is that you define a reducer, which takes an initial state and an event/action, and produces a new state. The key idea is that there must be no side-effects, the same action and state will always produce the same result, no matter the date/time, network state, etc, etc. This makes testing and reasoning about things easier, but at the cost of a more complex state management.
However, something important that I ignored in your original question is that you are kind of reinventing the wheel here. There is already a library that will centralise your state and make it available via context, it's the library that originally invented reducers: redux. I'm guessing you have read something like this article about using context instead of redux, however the advantage of redux for you is that there is a litany of documentation about how to use it and solve these problems.
My recommendation for you is to make sure you need/want redux/reducers. It has it's value and I personally love the pattern but if you are just getting started on React, you would be better off just using useState in my opinion.

How to get an RTK Query API endpoint state (isLoading, error, etc) in a React Class component?

Ok, I think I've gone through almost all of RTK Query's docs, and read up on RTK Query's caching. Seems like it's a pretty big part of it, even if its not something I need at the moment.
So, I'm trying to do a simple query using RKT Query in a class-based component, and then select from the Redux Store the isLoading state for the endpoint call. However, currently in the render() {} of my LoginPage.jsx, the endpoint.<name>.select()(state) call on mapStateToProps within LoginPageContainer.jsx doesn't seem to be working. (See code below).
From looking at the examples from the docs on using RTK Query on classes, it looks like I'm missing a "cache key" in the .select(<cache_key>)(state) call. However, I haven't incorporated tags in my endpoint yet (I believe I don't have a need for them yet).
My question:
Can someone shed some light on what's the proper use on RTK Query generated endpoint's select() method used outside of React Hooks? I understand the idea behind cache tags for automatic re-fetching (but that's not likely what's wrong here), but I'm not sure how or what cache key I'm missing here to just get the running endpoint query state in a class component. Thanks, everyone!
The Code:
// LoginPage.jsx
import React, { Component } from 'react'
import PT from 'prop-types'
import LoginForm from './components/LoginForm'
export default class LoginPage extends Component {
static propTypes = {
loginWithUsername: PT.func.isRequired,
loginWithUsernameState: PT.object.isRequired
}
render() {
// This value never updates
const { isLoading } = this.props.loginWithUsernameState
// always outputs "{"status":"uninitialized","isUninitialized":true,"isLoading":false,"isSuccess":false,"isError":false}"
// Even during and after running the `loginWithUsername` endpoint query
console.log(this.props.loginWithUsernameState)
return (
<div>
{isLoading && 'Loading ...'}
<LoginForm
onSubmit={(values) => this.props.loginWithUsername(values)} />
</div>
)
}
}
// LoginPageContainer.jsx
import { connect } from 'react-redux'
import { teacherApi } from './api'
import LoginPage from './LoginPage'
const { loginWithUsername } = teacherApi.endpoints
const mapStateToProps = (state) => ({
loginWithUsernameState: loginWithUsername.select()(state)
})
const mapDispatchToProps = (dispatch) => ({
loginWithUsername: (payload) => dispatch(loginWithUsername.initiate(payload))
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginPage)
// api.js
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react'
export const teacherApi = createApi({
reducerPath: 'teacherApi',
baseQuery: fetchBaseQuery({ baseUrl: '/teacher/' }),
endpoints: (builder) => ({
loginWithUsername: builder.query({
query: (data) => ({
url: 'login',
method: 'post',
body: data,
headers: { 'Content-Type': 'application/json' }
}),
}),
}),
})
The "cache key" passed to endpoint.select() is the same variable you're passing to your hook:
useGetSomeItemQuery("a");
useGetSomeItemQuery("b");
const selectSomeItemA = endpoint.select("a");
const selectSomeItemB = endpoint.select("b");
const itemAREsults = selectSomeItemA(state);
const itemBResults = selectSomeItemB(state);
This results in looking up state => state[apiSlice.reducerPath].queries["getSomeItem('a')"], or whatever the exact cached data field is for that item.
const result = api.endpoints.getPosts.select()(state)
const { data, status, error } = result
Note that unlike the auto-generated query hooks, derived booleans such
as isLoading, isFetching, isSuccess are not available here. The raw
status enum is provided instead.
https://redux-toolkit.js.org/rtk-query/usage/usage-without-react-hooks

getServerSideProps functions response cannot be serialized as JSON in Next.js

I am building a Next.js application with multiple pages with dynamic routing. Each page has multiple axios calls to the backend that are called with useEffect. My goal is to instead call these functions with getServerSideProps functions for speed purposes as the application is scaled to accomodate a larger user database.
My issue is when i try to recieve emails from the database, I get the error:
Error: Error serializing .allEmails.config.transformRequest[0] returned from getServerSideProps in "/emails".
Reason: function cannot be serialized as JSON. Please only return JSON serializable data types.
I want to recieve emails and pass it into props where i can then access the data on the page.
import React, { useState, useEffect, useContext } from 'react';
import axios from 'axios';
import jsHttpCookie from 'cookie';
import jsCookie from 'js-cookie';
const Emails = ({allEmails}) => {
const [emails, setEmails] = useState(allEmails);
return (
<></>
)
}
export async function getServerSideProps({req, res}) {
const {token} = jsHttpCookie.parse(req.headers.cookie);
const allEmails = await axios.get("http://localhost:8000/api/allCompanyEmails");
console.log(allEmails, "all data")
return {
props: {
allEmails
}
}
}
export default Emails;
allEmails is actually AxiosResponse type, it is not the data you get from api. And it contains non-serializable stuff like functions and etc.
To get the data you need to access data property of this response:
export async function getServerSideProps({req, res}) {
const {token} = jsHttpCookie.parse(req.headers.cookie);
const response = await axios.get("http://localhost:8000/api/allCompanyEmails");
console.log(response, "all data")
return {
props: {
allEmails: response.data
}
}
}

react-admin: dispatch action to store meta data from dynamic request

Hi this question is a continue to this one!
I'm getting my routes dynamically via an ajax request (following this article in the official docs "Declaring resources at runtime"), I'm using an async function to return a list of resources from an ajax request.
What is the best way to dispatch an action to store meta data, which I got form ajax request in redux, for later access?
Also when user has not yet logged in, this function will not return anything, after logging in, user will have access to a couple of resources. What is the best way to reload resources?
The best option is to use redux-saga. https://redux-saga.js.org/docs/introduction/BeginnerTutorial.html
Then
export function* async() {
yield fetch(); //your Ajax call function
yield put({ type: 'INCREMENT' }) //call your action to update your app
}
Incase you can't use redux-saga, I like your solution with private variable. You should go ahead with that.
To get this to work, I added a private variable, which I store the data mentioned in the question, and I access it via another function, which I exported from that file.
This gives me what I need, but I don't know if it's the best way to go.
https://github.com/redux-utilities/redux-actions
redux-actions is really simple to setup. Configure the store and then you can setup each state value in a single file:
import { createAction, handleActions } from 'redux-actions'
let initialState = { myValue: '' }
export default handleActions({
SET_MY_VALUE: (state, action) => ({...state, myValue: action.payload})
})
export const setMyValue = createAction('SET_MY_VALUE')
export const doSomething = () => {
return dispatch => {
doFetch().then(result => {
if (result.ok) dispatch(setMyValue(result.data))
})
}
}
Then in your component you just connect and you can access the state value
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
class MyComponent extends React.Component {
render = () => (
<span>{this.props.myValue}</span>
)
}
MyComponent.propTypes = {
myValue: PropTypes.string.isRequired
}
const mapStateToProps = (state) => ({
myValue: state.myState.myValue
})
const mapDispatchToProps = () => ({})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

React/Redux how to access the state in the networkservice

I have created a Network service component which deals with the API call. I want to retrieve state from other components which update the store.
Im having trouble getting the state so I started using Redux, but I havent used Redux before and still trying to find a way to pass the state to the NetworkService. Any help would be great, thanks!
Here is my NetworkService.js
import RequestService from './RequestService';
import store from '../store';
const BASE_URL = 'api.example.com/';
const REGION_ID = //Trying to find a way to get the state here
// My attempt to get the state, but this unsubscribes and
// doesnt return the value as it is async
let Updated = store.subscribe(() => {
let REGION_ID = store.getState().regionId;
})
class NetworkService {
getForecast48Regional(){
let url =`${BASE_URL}/${REGION_ID }`;
return RequestService.getRequest(url)
}
}
export default new NetworkService();
store.js
import {createStore} from 'redux';
const initialState = {
regionId: 0
};
const reducer = (state = initialState, action) => {
if(action.type === "REGIONAL_ID") {
return {
regionId: action.regionId
};
}
return state;
}
const store = createStore(reducer);
export default store;
My folder heirarchy looks like this:
-App
----Components
----NetworkService
----Store
Do not import store directly. Use thunks/sagas/whatever for these reasons.
NetworkService should not know about anything below.
Thunks know only about NetworkService and plain redux actions.
Components know only about thunks and store (not store itself, but Redux's selectors, mapStateToProps, mapDispatchToProps).
Store knows about plain redux actions only.
Knows - e.g. import's.
//////////// NetworkService.js
const networkCall = (...args) => fetch(...) // say, returns promise
//////////// thunks/core/whatever.js
import { networkCall } from 'NetworkService'
const thunk = (...args) => (dispatch, getState) => {
dispatch(startFetch(...args))
const componentData = args
// I'd suggest using selectors here to pick only required data from store's state
// instead of passing WHOLE state to network layer, since it's a leaking abstraction
const storeData = getState()
networkCall(componentData, storeData)
.then(resp => dispatch(fetchOk(resp)))
.catch(err => dispatch(fetchFail(err)))
}
//////////// Component.js
import { thunk } from 'thunks/core/whatever'
const mapDispatchToProps = {
doSomeFetch: thunk,
}
const Component = ({ doSomeFetch }) =>
<button onClick={doSomeFetch}>Do some fetch</button>
// store.subscribe via `connect` from `react-redux`
const ConnectedComponent = connect(..., mapDispatchToProps)(Component)

Categories