Why my state is undefined on refresh? React Reducer - javascript

I'm working on my first app. I put response from the server in an empty array in the state, but when I try to work with this array it throws undefined. On the first render everything seems OK but any futher manipulations fail. Please, help. I'm desperate here. My code:
App component:
import { React, useEffect, useContext } from "react";
import { getExchangeCourse } from "./api/currencyApi";
import { ADD_CURRENCY } from "./reducer/currencyReducer";
import { CurrenciesConverterContextComponent } from "./context/Context";
import Header from "./components/Header";
function App() {
const [, dispatch] = useContext(CurrenciesConverterContextComponent);
useEffect(() => {
getExchangeCourse().then(data => dispatch({
type: ADD_CURRENCY,
payload: data
}))
}, [])
return (
<div className="App">
<Header />
</div>
);
}
Header component:
export default function Header() {
const [{currencies}] = useContext(CurrenciesConverterContextComponent);
let USD = currencies.find(e => e.cc === "USD");
let EUR = currencies.find(e => e.cc === "EUR");
let GBP = currencies.find(e => e.cc === "GBP");
**these functions above work on a first render and then fail**
return (
<div className='converter-header'>
<span>{USD.cc} : {USD.rate}</span>
<span>{EUR.cc} : {EUR.rate}</span>
<span>{GBP.cc} : {GBP.rate}</span>
</div>
)
}
Reducer:
export const initialState = {
currencies: [],
}
export const ADD_CURRENCY = '[CURRENCIES] Add Currency';
export const currenciesReducer = ( state = initialState, action) => {
switch(action.type) {
case ADD_CURRENCY:
return {
...state,
// currencies: [...state.currencies, action.payload]
currencies: action.payload
};
default: {
return {
...state
}
}
}
}
API request:
import axios from 'axios';
const currencyAPI = axios.create({
baseURL: 'https://bank.gov.ua/NBUStatService/v1/statdirectory/exchange?json',
headers: {"Content-type": "application/json"},
})
export async function getExchangeCourse() {
const {data} = await currencyAPI.get()
console.log(data);
return data;
}

Related

react saga - Filter data based on query's body

In my backend, I use the post request type to fetch the data, so I can send a query or any other information I want in the body, so that the data is fetched on its basis
On the front end, I use React with Saga, and I can fetch data from the server without any problem
The case in which I need help is as follows.
I have a table containing products in the database and I want to divide these products into three pages based on a field in the table
Of course, I was able to do the filtering process in Saga as follows, and there is no problem
How can I pass this query in the image variable according to my need?
Suppose I want to create three pages in React, and for each page I will send a different filter, how do I do that?
I will attach the full code that I tried to do
Waiting for your help
this action
import {
GET_SERVICES_SUPPORT_LIST,
API_RESPONSE_SUCCESS,
API_RESPONSE_ERROR,
} from "./actionType";
// common success
export const ServicesSupportApiResponseSuccess = (actionType, data) => ({
type: API_RESPONSE_SUCCESS,
payload: { actionType, data },
});
// common error
export const ServicesSupportApiResponseError = (actionType, error) => ({
type: API_RESPONSE_ERROR,
payload: { actionType, error },
});
export const getServicesSupportList = services => ({
type: GET_SERVICES_SUPPORT_LIST,
payload: services,
});
this action type
// Actions
export const API_RESPONSE_SUCCESS = "API_RESPONSE_SUCCESS";
export const API_RESPONSE_ERROR = "API_RESPONSE_ERROR";
export const GET_SERVICES_SUPPORT_LIST = "GET_SERVICES_SUPPORT_LIST";
this saga
import { call, put, takeEvery, all, fork } from "redux-saga/effects";
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import {
GET_SERVICES_SUPPORT_LIST,
} from "./actionType";
import {
ServicesSupportApiResponseSuccess,
ServicesSupportApiResponseError
} from "./action";
//Include Both Helper File with needed methods
import {
getServicesSupportList as getServicesSupportApi
}
from "../../helpers/backend_helper";
function* getServicesSupport({ payload: services }) {
try {
const response = yield call(getServicesSupportApi, services);
yield put(ServicesSupportApiResponseSuccess(GET_SERVICES_SUPPORT_LIST, response.data));
} catch (error) {
yield put(ServicesSupportApiResponseError(GET_SERVICES_SUPPORT_LIST, error));
}
}
export function* watchGetServicesSupportList() {
yield takeEvery(GET_SERVICES_SUPPORT_LIST, getServicesSupport);
}
function* servicesSupportSaga() {
yield all(
[
fork(watchGetServicesSupportList),
]
);
}
export default servicesSupportSaga;
this hook
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux'
import { getServicesSupportList} from "../../../store/actions";
const AllServicesSupportHook = () => {
const dispatch = useDispatch();
//to get state from redux
const { servicesSupportList, loder,error} = useSelector((state) => ({
servicesSupportList: state.ServicesSupport.servicesSupportList,
loder: state.ServicesSupport.loder,
error: state.ServicesSupport.error,
}));
//when first load
useEffect(() => {
if (loder) {
dispatch(getServicesSupportList());
}
}, [dispatch]);
let test ;
try {
if (servicesSupportList.paginator)
test = {query: {categoryTickets : 2} //This is definitely wrong
} catch (e) { }
const getPage = (services) => {
dispatch(getServicesSupportList(services));
}
return [servicesSupportList, loder,error,getPage]
};
export default AllServicesSupportHook;
get list
export const getServicesSupportList = (services) => api.create(url.GET_SERVICES_SUPPORT_LIST,services);
this reducer
import {
GET_SERVICES_SUPPORT_LIST,
API_RESPONSE_SUCCESS,
API_RESPONSE_ERROR,
} from "./actionType";
const INIT_STATE = {
servicesSupportList: [],
loder: true
};
const ServicesSupport = (state = INIT_STATE, action) => {
switch (action.type) {
case API_RESPONSE_SUCCESS:
switch (action.payload.actionType) {
case GET_SERVICES_SUPPORT_LIST:
return {
...state,
servicesSupportList: action.payload.data,
loder: false,
};
default:
return { ...state };
}
case API_RESPONSE_ERROR:
switch (action.payload.actionType) {
case GET_SERVICES_SUPPORT_LIST:
return {
...state,
error: action.payload.error,
loder: true,
};
default:
return { ...state };
}
case GET_SERVICES_SUPPORT_LIST: {
return {
...state,
loder: false,
};
}
default:
return { ...state };
}
};
export default ServicesSupport;
this components
import React from "react";
import { Row, Container ,Alert} from "reactstrap";
import ServicesSupportListItems from "./ServicesSupportListCard";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import Loader from "../../Common/Loader";
const ServicesSupportList = ({ data, loder, error }) => {
return (
<Container fluid={true}>
<Row>
{loder === false ? (
data.data ? (
data.data.map((item, index) => {
return (
<ServicesSupportListItems
key={index}
id={item.id}
categoryTickets={item.categoryTickets}
title={item.titleProduct}
description={item.description}
point={item.point}
/>
);
})
) : (<Alert color="danger"> {error} </Alert>)
) : ( <Loader error={error} />
)}
<ToastContainer closeButton={false} />
</Row>
</Container>
);
};
export default ServicesSupportList;
I found the solution, thank you
function* getServicesSupport({ payload: services }) {
try {
var response;
if (services === "Support") {
response = yield call(getServicesSupportApi,{query: {categoryTickets : 2}});
}
if (services === "CardFree") {
response = yield call(getServicesSupportApi,{query: {categoryTickets : 6}});
}
yield put(ServicesSupportApiResponseSuccess(GET_SERVICES_SUPPORT_LIST, response.data));
} catch (error) {
yield put(ServicesSupportApiResponseError(GET_SERVICES_SUPPORT_LIST, error));
}
}

Server Error Error: Invalid hook call. Hooks can only be called inside of the body of a function component in _app.js

I am a newbie in React and Next JS, I want to set initial auth user data on initial load from the __app.js. But using dispatch throwing error "Invalid hook call". I know according to docs calling hooks in render function is wrong. but I am looking for an alternate solution to this.
How I can set auth data one-time so that will be available for all the pages and components.
I am including my code below.
/contexts/app.js
import { useReducer, useContext, createContext } from 'react'
const AppStateContext = createContext()
const AppDispatchContext = createContext()
const reducer = (state, action) => {
switch (action.type) {
case 'SET_AUTH': {
return state = action.payload
}
default: {
throw new Error(`Unknown action: ${action.type}`)
}
}
}
export const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, {})
return (
<AppDispatchContext.Provider value={dispatch}>
<AppStateContext.Provider value={state}>
{children}
</AppStateContext.Provider>
</AppDispatchContext.Provider>
)
}
export const useAuth = () => useContext(AppStateContext)
export const useDispatchAuth = () => useContext(AppDispatchContext)
/_app.js
import 'bootstrap/dist/css/bootstrap.min.css'
import '../styles/globals.css'
import App from 'next/app'
import Layout from '../components/Layout'
import { mutate } from 'swr'
import { getUser } from '../requests/userApi'
import { AppProvider, useDispatchAuth } from '../contexts/app'
class MyApp extends App {
render() {
const dispatchAuth = useDispatchAuth()
const { Component, pageProps, props } = this.props
// Set initial user data
const setInitialUserData = async () => {
if (props.isServer) {
const initialData = {
loading: false,
loggedIn: (props.user) ? true : false,
user: props.user
}
const auth = await mutate('api-user', initialData, false)
dispatchAuth({
type: 'SET_AUTH',
payload: auth
})
}
}
//----------------------
// Set initial user data
setInitialUserData()
//----------------------
return (
<AppProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</AppProvider>
)
}
}
MyApp.getInitialProps = async (appContext) => {
let isServer = (appContext.ctx.req) ? true : false
let user = null
let userTypes = {}
// Get user server side
if (isServer) {
await getUser()
.then(response => {
let data = response.data
if (data.status == true) {
// Set user
user = data.data.user
userTypes = data.data.user_types
//---------
}
})
.catch(error => {
//
})
}
//---------------------
return {
props: {
user,
userTypes,
isServer
}
}
}
export default MyApp
I believe this is the intended use of the useEffect hook with an empty array as its second argument:
https://reactjs.org/docs/hooks-effect.html
import {useEffect} from 'react'
class MyApp extends App {
useEffect(()=> {
setInitialUserData()
},[])
render() {
...
}
}

React-Redux - TypeError: state.menu is not iterable

I am working on a React application and I am using Redux to store the state. I have the following code:
menu.reducer.js:
import { INCREASE_CATEGORY_RANK, DECREASE_CATEGORY_RANK } from './menu.types';
const INITIAL_STATE = []
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case INCREASE_CATEGORY_RANK: {
console.log("Printing state");
console.log(state);
const menuArray = [...state.menu];
menuArray.sort((a,b) => (a.rank > b.rank) ? 1 : -1);
return {
...state,
menu: menuArray
}
}
case DECREASE_CATEGORY_RANK: {
return state.map(category => {
if (category._id === action.payload._id && category.rank !== -1) {
const oldrank = category.rank;
return {
...category,
rank: oldrank - 1
}
} else {
return category;
}
})
}
default:
return state;
}
}
menu.types.js:
export const INCREASE_CATEGORY_RANK = "INCREASE_CATEGORY_RANK";
export const DECREASE_CATEGORY_RANK = "DECREASE_CATEGORY_RANK";
menu.actions.js:
import { apiUrl, apiConfig } from '../../util/api';
import { INCREASE_CATEGORY_RANK, DECREASE_CATEGORY_RANK } from './menu.types';
export const getMenu = () => async dispatch => {
const response = await fetch(`${apiUrl}/menu`);
if (response.ok) {
const menuData = await response.json();
dispatch({ type: GET_MENU, payload: menuData })
}
}
export const increaseCategoryRank = category => dispatch => {
dispatch({ type: INCREASE_CATEGORY_RANK, payload: category })
}
export const decreaseCategoryRank = category => dispatch => {
dispatch({ type: DECREASE_CATEGORY_RANK, payload: category })
}
category-arrows.component.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increaseCategoryRank, decreaseCategoryRank } from '../../redux/menu/menu.actions';
import './category-arrows.styles.scss';
class CategoryArrows extends Component {
render() {
const { category, increaseCategoryRank, decreaseCategoryRank } = this.props;
return (
<div class="arrows-container">
<div class="up-arrow" onClick={() => this.props.increaseCategoryRank(category)}></div>
<div class="category-rank">
<p>{category.rank}</p>
</div>
<div class="down-arrow" onClick={() => this.props.decreaseCategoryRank(category)}></div>
</div>
)
}
}
export default connect(null, { increaseCategoryRank, decreaseCategoryRank } )(CategoryArrows);
For my Reducer function, the initial state is retrieved from a database. The Reducer code deals with the menu array, which is an array of objects:
I want to copy the menu array from the state in my Reducer function, so that I can sort it, and then reassign the sorted menu array to the state.
I have tried to console.log() the state to see what it is in my Reducer function. When I click on the up-arrow div in category-arrows.component.jsx, the INCREASE_CATEGORY_RANK action is dispatched. When I check the Console, I get the following:
However when I copy the menu array from the state in the INCREASE_CATEGORY_RANK case in my Reducer function, I get the following error:
I am not sure why I am getting the above error and how to resolve it. Any insights are appreciated.
It looks like the reducer expect an array not an object:
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case INCREASE_CATEGORY_RANK: {
return [...state].sort((a,b) => (a.rank > b.rank) ? 1 : -1);
}
}
}

Call fetch function in functional component React

I'v tried so many way to fetch data only once before rendering but have some issue:
1) I Can't call dispatch in componentDidMount because there is the rule that I can do it in Functional component only
2) If I try to call fetch function in the beginning of a Functional component it starts to rerender infinitely because fetch function calls every time and change a state in a redux store
3) I found a solution with useEffect but it generate exception "Invalid hook call" like in first point
How can I call fetch function only once in this component?
here is my component:
import React, { useEffect } from "react";
import { useParams as params} from "react-router-dom";
import { VolunteerCardList } from "./VolunteerCardList";
import { AnimalNeeds } from "./AnimalNeeds";
import { AppState } from "../reducers/rootReducer";
import { connect } from "react-redux";
import { Page404 } from "./404";
import { fetchAnimal } from "../actions/animalAction";
import { Dispatch } from "redux";
import { IAnimalCard } from "../interfaces/Interfaces";
const AnimalCard: React.FC<Props> = ({animal, loading, fetch}) => {
useEffect(() => {
fetch(); //invalid hook call????
}, [])
return (
<div className="container">
some html
</div>
)
}
interface RouteParams {
shelterid: string,
animalid: string,
}
interface mapStateToPropsType {
animal: IAnimalCard,
loading : boolean
}
const mapStateToProps = (state: AppState) : mapStateToPropsType=> {
return{
animal: state.animals.animal,
loading: state.app.loading
}
}
interface mapDispatchToPropsType {
fetch: () => void;
}
const mapDispatchToProps = (dispatch: Dispatch<any>) : mapDispatchToPropsType => ({
fetch : () => {
const route = params<RouteParams>();
dispatch(fetchAnimal(route.shelterid, route.animalid));
}
})
type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;
export default connect(mapStateToProps, mapDispatchToProps as any)(AnimalCard);
this is my reducer:
export const animalReducer = (state: AnimalReducerType = initState, action: IAction) => {
switch (action.type) {
case AnimalTypes.FETCH_ANIMAL:
return {...state, animal: action.payload};
break;
default:
return state;
break;
}
this is action:
export interface IFetchAnimalAction {
type: AnimalTypes.FETCH_ANIMAL,
payload: IAnimalCard
}
export type IAction = IFetchAnimalAction;
export const fetchAnimal = (shelterId : string, animalId: string) => {
return async (dispatch: Dispatch) => {
const response = await fetch(`https://localhost:44300/api/animals/${animalId}`);
const json = await response.json();
dispatch<IFetchAnimalAction>({type: AnimalTypes.FETCH_ANIMAL, payload: json})
}
}
This runs as old lifecycle method componentDidMount:
useEffect(() => {
fetch(); //invalid hook call????
}, [])
I guess the behaviour you want to replicate is the one iterated by componentWillMount, which you cannot do by any of the standard hooks. My go-to solution for this is to let the acquire some loadingState, most explicitly as:
const AnimalCard: React.FC<Props> = ({animal, loading, fetch}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
fetch().then(res => {
// Do whatever with res
setIsLoading(true);
}
}, [])
if(!isLoading){
return null
}
return (
<div className="container">
some html
</div>
)
}

getPastEvents undefined(re)

I had asked this question before.
I looked carefully at the exchange section, which I advised, and I think there is no problem with the exchange section.
At least in my opinion there is no problem and I hardly know the cause of the problem.
And I was so frustrated that I put everything in the code.
If anyone can provide us with a clue to this problem, please reply to me.
interactions.js
import Web3 from 'web3'
import {
web3Loaded,
web3AccountLoaded,
tokenLoaded,
exchangeLoaded,
cancelledOrdersLoaded
} from './actions'
import Token from '../abis/Token.json'
import Exchange from '../abis/Exchange.json'
export const loadWeb3 = (dispatch) => {
const web3 = new Web3(Web3.givenProvider || 'http://localhost:7545')
dispatch(web3Loaded(web3))
return web3
}
export const loadAccount = async (web3, dispatch) => {
const accounts = await web3.eth.getAccounts()
const account = accounts[0]
dispatch(web3AccountLoaded(account))
return account
}
export const loadToken = async (web3, networkId, dispatch) => {
try {
const token = new web3.eth.Contract(Token.abi, Token.networks[networkId].address)
dispatch(tokenLoaded(token))
return token
} catch (error) {
console.log('Contract not deployed to the current network. Please select another network with Metamask.')
return null
}
}
export const loadExchange = async (web3, networkId, dispatch) => {
try {
const exchange = new web3.eth.Contract(Exchange.abi, Exchange.networks[networkId].address)
dispatch(exchangeLoaded(exchange))
return exchange
} catch (error) {
console.log('Contract not deployed to the current network. Please select another network with Metamask.')
return null
}
}
export const loadAllOrders = async (exchange, dispatch) => {
// if (exchange) { // Make sure exchange has been defined
// const exchange = new web3.eth.Contract(Exchange.abi, Exchange.networks[networkId].address)
const cancelStream = await exchange.getPastEvents('Cancel', { fromBlock: 0, toBlock: 'latest' })
// // await loadAllOrders(this.props.exchange, dispatch)
console.log(cancelStream)
}
App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Navbar from './Navbar'
import Web3 from 'web3';
import Content from './Content'
import { connect } from 'react-redux'
// import Token from '../abis/Token.json'
import {
loadWeb3,
loadAccount,
loadToken,
loadExchange
} from '../store/interactions'
import { contractsLoadedSelector } from '../store/selectors'
class App extends Component {
componentWillMount() {
this.loadBlockchainData(this.props.dispatch)
}
async loadBlockchainData(dispatch) {
const web3 = loadWeb3(dispatch)
const network = await web3.eth.net.getNetworkType()
const networkId = await web3.eth.net.getId()
const accounts = await loadAccount(web3, dispatch)
const token = await loadToken(web3, networkId, dispatch)
if(!token) {
window.alert('Token smart contract not detected on the current network. Please select another network with Metamask.')
return
}
const exchange = await loadExchange(web3, networkId, dispatch)
if(!exchange) {
window.alert('Exchange smart contract not detected on the current network. Please select another network with Metamask.')
return
}
}
render() {
return (
<div>
<Navbar />
{ this.props.contractsLoaded ? <Content /> : <div className="content"></div> }
</div>
);
}
}
function mapStateToProps(state) {
return {
contractsLoaded: contractsLoadedSelector(state)
}
}
export default connect(mapStateToProps)(App);
reducers.js
import { combineReducers } from 'redux';
function web3(state={}, action) {
switch (action.type) {
case 'WEB3_LOADED':
return { ...state, connection: action.connection }
case 'WEB3_ACCOUNT_LOADED':
return { ...state, account: action.account }
default:
return state
}
}
function token(state = {}, action) {
switch (action.type) {
case 'TOKEN_LOADED':
return { ...state, loaded: true, contract: action.contract }
default:
return state
}
}
function exchange(state = {}, action) {
switch (action.type) {
case 'EXCHANGE_LOADED':
return { ...state, loaded: true, contract: action.contract }
case 'CANCELLED_ORDERS_LOADED':
return { ...state, cancelledOrders: { loaded: true, data: action.cancelledOrders } }
// case 'FILLED_ORDERS_LOADED':
// return { ...state, filledOrders: { loaded: true, data: action.filledOrders } }
// case 'ALL_ORDERS_LOADED':
// return { ...state, allOrders: { loaded: true, data: action.allOrders } }
default:
return state
}
}
const rootReducer = combineReducers({
web3,
token,
exchange
})
export default rootReducer
Content.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { exchangeSelector } from '../store/selectors'
import { loadAllOrders } from '../store/interactions'
class Content extends Component {
componentWillMount() {
this.loadBlockchainData(this.props.dispatch)
}
// async loadBlockchainData(exchange, dispatch) {
async loadBlockchainData(dispatch) {
await loadAllOrders(this.props.exchange, dispatch)
// this.loadBlockchainData(this.props.exchange)
// await loadAllOrders(exchange, dispatch)
}
function mapStateToProps(state) {
return {
exchange: state.exchangeSelector
}
}
export default connect(mapStateToProps)(Content)
selectors.js
import { get } from 'lodash'
import { createSelector } from 'reselect'
const account = state => get(state, 'web3.account')
export const accountSelector = createSelector(account, a => a)
const tokenLoaded = state => get(state, 'token.loaded', false)
export const tokenLoadedSelector = createSelector(tokenLoaded, tl => tl)
const exchangeLoaded = state => get(state, 'exchange.loaded', false)
export const exchangeLoadedSelector = createSelector(exchangeLoaded, el => el)
const exchange = state => get(state, 'exchange.contract')
export const exchangeSelector = createSelector(exchange, e => e)
export const contractsLoadedSelector = createSelector(
tokenLoaded,
exchangeLoaded,
(tl, el) => (tl && el)
)
Check exchange to make sure not undefined
export const loadAllOrders = async (exchange, dispatch) => {
const cancelStream = exchange ?
await exchange.getPastEvents('Cancel', { fromBlock: 0, toBlock: 'latest' })
: null // Check if exchange defined then call getPastEvents
console.log(cancelStream)
}

Categories