Why I get undefined when I use react redux RTK query useByIdQuery - reactjs

I created store where are a all endpoints and I have a issues with getById endpoint.
Store.tsx
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
import { Character } from "../interface/types";
export const characterAPI = createApi({
reducerPath: "characterAPI",
baseQuery: fetchBaseQuery({ baseUrl: "https://www.breakingbadapi.com/api/" }),
tagTypes: ["Characters"],
endpoints: (builder) => ({
getAll: builder.query<Character[], void>({
query: () => `characters`,
providesTags: [{ type: "Characters", id: "LIST" }],
}),
getById: builder.query<Character, string>({
query: (char_id) => `characters/${char_id}`,
providesTags: [{ type: "Characters", id: "LIST" }],
}),
}),
});
CharacterContainer.tsx
But after loading I can see whole data, but when i wanna console.log(getById) its's says undefined
import React from "react";
import { useParams } from "react-router-dom";
import { characterAPI } from "../store/store";
const CharacterContainer = () => {
const { char_id } = useParams();
const { data: getById, isLoading } = characterAPI.useGetByIdQuery(
char_id as string
);
console.log(getById);
const { name, birthday } = getById || {};
if (isLoading) {
return <div>Loading...</div>;
}
return (
<div>
<p>{name}</p>
<p>{birthday}</p>
</div>
);
};
And what I see in tools:
CharacterContainer.tsx:22 undefined
CharacterContainer.tsx:22 undefined
CharacterContainer.tsx:22
[{…}]0: appearance: (5) [1, 2, 3, 4, 5]
better_call_saul_appearance: []
birthday: "09-07-1958"
category: "Breaking Bad"
char_id: 1
img: "https://images.amcnetworks.com/amc.com/wpcontent/uploads/2015/04/cast_bb_700x1000_walter-white-lg.jpg"
name: "Walter White"
nickname: "Heisenberg"
occupation: (2)
['High School Chemistry Teacher', 'Meth King Pin']
portrayed: "Bryan Cranston"status:
"Presumed dead"[[Prototype]]: Objectlength: 1[[Prototype]]: Array(0)

This is because you are trying to console.log the result immediately, and this means you are trying to output getById when the query is still in it's loading phase. You could do something like:
console.log(isLoading ? "Loading result" : getById)
Your output would then be something like:
CharacterContainer.tsx:22 undefined
CharacterContainer.tsx:22 undefined
CharacterContainer.tsx:22
[{…}]0: appearance: (5) [1, 2, 3, 4, 5]
better_call_saul_appearance: []
birthday: "09-07-1958"
category: "Breaking Bad"
char_id: 1
img: "https://images.amcnetworks.com/amc.com/wpcontent/uploads/2015/04/cast_bb_700x1000_walter-white-lg.jpg"
name: "Walter White"
nickname: "Heisenberg"
occupation: (2)
['High School Chemistry Teacher', 'Meth King Pin']
portrayed: "Bryan Cranston"status:
"Presumed dead"[[Prototype]]: Objectlength: 1[[Prototype]]: Array(0)

Related

How to access smart contract functions from React JS?

I am making lottery contract , but as I try to access my method of manager I get this error in ReactJS.
[![enter image description here][1]][1]
Please help me with the solution. I have developed my contract I am having problems with connecting it to the frontend.
This is the error .
[1]: https://i.stack.imgur.com/QFc2o.png
App.js File
import detectEthereumProvider from "#metamask/detect-provider";
// import { loadContract } from "./utils/LoadContracts";
import Web3 from "web3";
import lottery from "./lottery";
import {
useEffect,
useState
} from "react";
function App() {
const [balance, setBalance] = useState("");
const [changedAccount, setChangedAccount] = useState([]);
const loader = async() => {
let provider = await detectEthereumProvider();
let web3 = new Web3(provider);
return {
provider,
web3
};
};
useEffect(async() => {
let newLoad = await loader();
let account = await newLoad.web3.eth.getAccounts();
setChangedAccount(account);
}, []);
useEffect(async() => {
let newLoad = await loader();
newLoad.provider.on("accountsChanged", function(accounts) {
let newAccount = accounts;
if (newAccount) {
setChangedAccount(newAccount);
}
});
}, []);
useEffect(async() => {
let newLoad = await loader();
let accountBal =
changedAccount.length > 0 &&
(await newLoad.web3.eth.getBalance(changedAccount[0]));
setBalance(accountBal);
}, [changedAccount]);
useEffect(async() => {
let newLoad = await loader();
let account = await newLoad.web3.eth.getAccounts();
setChangedAccount(account);
const manager = await lottery.methods.manager().call();
}, []);
return ( <
div >
<
p >
Account address {
changedAccount[0]
}, Its balance is: {
balance
} <
/p> <
/div>
);
}
export default App;
lottery.js (containing ABI and contract Address)
import Web3 from "web3";
let web3 = new Web3();
const address = "0xBEbdb8eC68A5803d0f5E93bACe9EB9E4227f5A20";
const abi = [{
inputs: [],
stateMutability: "nonpayable",
type: "constructor"
},
{
inputs: [],
name: "manager",
outputs: [{
internalType: "address",
name: "",
type: "address"
}],
stateMutability: "view",
type: "function",
},
{
inputs: [{
internalType: "uint256",
name: "",
type: "uint256"
}],
name: "players",
outputs: [{
internalType: "address",
name: "",
type: "address"
}],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "enter",
outputs: [],
stateMutability: "payable",
type: "function",
},
{
inputs: [],
name: "getBalance",
outputs: [{
internalType: "uint256",
name: "",
type: "uint256"
}],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "pickWinner",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [],
name: "getPlayers",
outputs: [{
internalType: "address[]",
name: "",
type: "address[]"
}],
stateMutability: "view",
type: "function",
},
];
export default new web3.eth.Contract(abi, address);
You have not provided any 'provider' in lottery.js on this line
let web3 = new Web3();
which isn't giving you correct contract instance and thus you cannot access it's methods

Cannot access 'transactionId' before initialization

Why is transactionId not activating inside the useEffect hook?
It is an edit route through the context api. It strange because react-router-dom picks up the transactionId in the url.
edit transaction modal
let history = useHistory();
const { transaction, editTransaction } = useContext(GlobalContext);
const [selectedTransaction, setSelectedTransaction] = useState({
id: null,
category: "",
heading: "",
description: "",
subHeading: "",
author: "",
});
const transactionId = match.params.id;
useEffect(() => {
const transactionId = transactionId;
const selectedTransaction = transaction.find(
(t) => t.id === parseInt(transactionId)
);
setSelectedTransaction(selectedTransaction);
}, [transactionId, transaction]);
const [open, setOpen] = useState(true);
const cancelButtonRef = useRef(null);
const onSubmit = (e) => {
editTransaction(selectedTransaction);
history.push("/");
};
const handleOnChange = (transactionKey, val) =>
setSelectedTransaction({
...selectedTransaction,
[transactionKey]: val,
});
let formData = {
name: setSelectedTransaction.name,
amount: setSelectedTransaction.amount,
category: setSelectedTransaction.category,
};
global state
import React, { createContext, useReducer, useEffect } from "react";
import AppReducer from "./AppReducer";
//Initial State
const initialState = {
transactions: [
{
id: "1",
name: "Rent",
href: "#",
category: "expense",
amount: 1000,
currency: "USD",
status: "processing",
date: "July 1, 2020",
datetime: "2020-07-11",
type: "Bills",
},
{
id: "2",
name: "IRS",
href: "#",
category: "income",
amount: 5000,
currency: "USD",
status: "success",
date: "July 18, 2020",
datetime: "2020-07-18",
type: "Extra Income",
},
{
id: "3",
name: "Paypal",
href: "#",
category: "income",
amount: 15000,
currency: "USD",
status: "success",
date: "July 18, 2020",
datetime: "2020-07-18",
type: "Income",
},
{
id: "4",
name: "AT&T",
href: "#",
category: "expense",
amount: 2000,
currency: "USD",
status: "success",
date: "July 11, 2020",
datetime: "2020-07-11",
type: "Phone",
},
],
totalTransactionCount: 4,
};
//Create context
export const GlobalContext = createContext(initialState);
//Provider component
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
const { totalTransactionCount, transactions } = state;
//Actions
function deleteTransaction(id) {
dispatch({
type: "DELETE_TRANSACTION",
payload: id,
});
}
function addTransaction(transaction) {
dispatch({
type: "ADD_TRANSACTION",
payload: transaction,
});
}
const editTransaction = (transaction) => {
dispatch({
type: "EDIT_TRANSACTION",
payload: transaction,
});
};
useEffect(() => {
dispatch({
type: "SET_TRANSACTION_COUNT",
payload: transactions.length,
});
}, [transactions]);
return (
<GlobalContext.Provider
value={{
transactions: state.transactions,
totalTransactionCount,
deleteTransaction,
addTransaction,
editTransaction,
}}>
{children}
</GlobalContext.Provider>
);
};
reducer
export default (state, action) => {
switch (action.type) {
case "DELETE_TRANSACTION":
return {
...state,
transactions: state.transactions.filter(
(transaction) => transaction.id !== action.payload
),
};
case "EDIT_TRANSACTION":
const updatedTransaction = action.payload;
const updatedTransactions = state.transactions.map((transaction) => {
if (transaction.id === updatedTransaction.id) {
return updatedTransaction;
}
return transaction;
});
return {
...state,
transactions: updatedTransactions,
};
case "ADD_TRANSACTION":
return {
...state,
transactions: [action.payload, ...state.transactions],
};
case "SET_TRANSACTION_COUNT":
return {
...state,
totalTransactionCount: action.payload,
};
default:
return state;
}
};

Testing Axios in Jest

I'm new to testing.
I'm trying to test an asynchronous data fetching function but I can't figure out why the test doesn't pass.
I have mocked Axios with jest and gave Axios' get method a mock implementation to resolve a promise.
The error says it can't read the property of name with means the data obj is undefined I reckon.
Here's Yelp.test.js
import Yelp from './Yelp';
import axios from 'axios';
jest.mock('axios');
describe('searchRestaurantsInfo', () => {
test('returns object with restaurant infos', async () => {
const data = {
name: 'Casa Romana',
address: '5 Albion Street',
coordinates: { lat: 52.6322649, lng: -1.1314474 },
city: 'Leicester LE1 6GD',
rating: 4.5,
photos: [
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
],
phone: '+441162541174',
price: '£££',
categories: 'Italian',
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm',
reviews: [
{
id: 'i_Q39aN9hwZzGDUb-IWpYw',
rating: 5,
text:
'Proper Italian restaurant. Not Italian-themed, or serving Italian fusion cuisine, just a place with an Italian owner who makes solid, straightforward...',
time_created: '2014-10-02 03:49:36',
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&hrid=i_Q39aN9hwZzGDUb-IWpYw&utm_campaign=yelp_api_v3&utm_me',
user: {
id: '6tPD46XZSFllvgn2vTh51A',
image_url:
'https://s3-media3.fl.yelpcdn.com/photo/A4Ww6Ks2P9WsALqOFy9cOA/o.jpg',
name: 'Espana S.',
profile_url:
'https://www.yelp.com/user_details?userid=6tPD46XZSFllvgn2vTh51A',
},
},
],
};
axios.get.mockImplementationOnce(() => Promise.resolve(data));
await expect(
Yelp.searchRestaurantsInfo('q_IoMdeM57U70GwqjXxGJw')
).resolves.toEqual(data);
});
});
And Yelp.js
import axios from 'axios';
let YELP_API_KEY = process.env.REACT_APP_YELP_API_KEY;
const Yelp = {
// Provides infos about a single restaurant
async searchRestaurantsInfo(id) {
try {
let response = await axios.get(
`https://cors-anywhere.herokuapp.com/https://api.yelp.com/v3/businesses/${id}`,
{
headers: {
Authorization: `Bearer ${YELP_API_KEY}`,
'X-Requested-With': 'XMLHttpRequest',
'Access-Control-Allow-Origin': '*',
},
}
);
let responseRew = await axios.get(
`https://cors-anywhere.herokuapp.com/https://api.yelp.com/v3/businesses/${id}/reviews`,
{
headers: {
Authorization: `Bearer ${YELP_API_KEY}`,
'X-Requested-With': 'XMLHttpRequest',
'Access-Control-Allow-Origin': '*',
},
}
);
const parameters = {
name: response.data.name,
address: response.data.location.display_address[0],
coordinates: {
lat: response.data.coordinates.latitude,
lng: response.data.coordinates.longitude,
},
city: response.data.location.display_address[1],
rating: response.data.rating,
photos: response.data.photos,
phone: response.data.phone,
price: response.data.price,
categories: response.data.categories[0].title,
url: response.data.url,
reviews: responseRew.data.reviews,
};
console.log({ parameters, id });
return parameters;
} catch (e) {
console.log(e);
return e;
}
}}
The error I get is
searchRestaurantsInfo
× returns array of restaurnats obj (66ms)
● searchRestaurantsInfo › returns array of restaurnats obj
expect(received).resolves.toEqual(expected) // deep equality
- Expected
+ Received
- Object // data object. I removed it from this error message because too long
+ [TypeError: Cannot read property 'name' of undefined]
47 | await expect(
48 | Yelp.searchRestaurantsInfo('q_IoMdeM57U70GwqjXxGJw')
> 49 | ).resolves.toEqual(data);
| ^
50 | });
51 | });
52 |
at Object.toEqual (node_modules/react-scripts/node_modules/expect/build/index.js:202:20)
at Object.<anonymous> (src/helpers/Yelp.test.js:49:16)
console.log src/helpers/Yelp.js:91
TypeError: Cannot read property 'name' of undefined
at Object.searchRestaurantsInfo (C:\Users\Turi\Desktop\project\RestaurantsRedux\src\helpers\Yelp.js:72:29)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at Object.<anonymous> (C:\Users\Turi\Desktop\project\RestaurantsRedux\src\helpers\Yelp.test.js:47:5)
Thanks in advance for your help!
There might be a problem with how you wait for the result (possible compilation issue), try writing the test like this.
// note make sure the test() function is async
const result = await Yelp.searchRestaurantsInfo('q_IoMdeM57U70GwqjXxGJw')
expect(result).toEqual(data);
I've managed to find the solution.
Like suggested I had to add another mock since in the function there are two different request.
In addition to that I realised I couldn't use data in both
axios.get.mockImplementationOnce(() => Promise.resolve(data));
and
Yelp.searchRestaurantsInfo('q_IoMdeM57U70GwqjXxGJw')
).resolves.toEqual(data);```
since the function wasn't returning data but an object with some parts from data.
Therefore I created a new object params to be compared with the function returned object.
import Yelp from './Yelp';
import axios from 'axios';
jest.mock('axios');
describe('searchRestaurantsInfo', () => {
test('returns object with restaurant infos', async () => {
const response = {
data: {
name: 'Casa Romana',
location: {
display_address: [
"12 Upper Saint Martin's Lane",
'London WC2H 9FB',
'United Kingdom',
],
},
coordinates: { latitude: 52.6322649, longitude: -1.1314474 },
rating: 4.5,
photos: [
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
],
phone: '+441162541174',
price: '£££',
categories: [{ alias: 'indpak', title: 'Indian' }],
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm',
},
};
const responseRev = {
data: {
reviews: [
{
id: 'i_Q39aN9hwZzGDUb-IWpYw',
rating: 5,
text:
'Proper Italian restaurant. Not Italian-themed, or serving Italian fusion cuisine, just a place with an Italian owner who makes solid, straightforward...',
time_created: '2014-10-02 03:49:36',
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&hrid=i_Q39aN9hwZzGDUb-IWpYw&utm_campaign=yelp_api_v3&utm_me',
user: {
id: '6tPD46XZSFllvgn2vTh51A',
image_url:
'https://s3-media3.fl.yelpcdn.com/photo/A4Ww6Ks2P9WsALqOFy9cOA/o.jpg',
name: 'Espana S.',
profile_url:
'https://www.yelp.com/user_details?userid=6tPD46XZSFllvgn2vTh51A',
},
},
],
},
};
const params = {
name: 'Casa Romana',
address: "12 Upper Saint Martin's Lane",
coordinates: { lat: 52.6322649, lng: -1.1314474 },
city: 'London WC2H 9FB',
rating: 4.5,
photos: [
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
'https://s3-media1.fl.yelpcdn.com/bphoto/4VUq4j1FF-n5bgXjtoC0Xw/o.jpg',
],
phone: '+441162541174',
price: '£££',
categories: 'Indian',
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm',
reviews: [
{
id: 'i_Q39aN9hwZzGDUb-IWpYw',
rating: 5,
text:
'Proper Italian restaurant. Not Italian-themed, or serving Italian fusion cuisine, just a place with an Italian owner who makes solid, straightforward...',
time_created: '2014-10-02 03:49:36',
url:
'https://www.yelp.com/biz/casa-romana-leicester?adjust_creative=7GHt4FY-2vjNyIPhQV7wcw&hrid=i_Q39aN9hwZzGDUb-IWpYw&utm_campaign=yelp_api_v3&utm_me',
user: {
id: '6tPD46XZSFllvgn2vTh51A',
image_url:
'https://s3-media3.fl.yelpcdn.com/photo/A4Ww6Ks2P9WsALqOFy9cOA/o.jpg',
name: 'Espana S.',
profile_url:
'https://www.yelp.com/user_details?userid=6tPD46XZSFllvgn2vTh51A',
},
},
],
};
axios.get.mockImplementationOnce(() => Promise.resolve(response));
axios.get.mockImplementationOnce(() => Promise.resolve(responseRev));
await expect(
Yelp.searchRestaurantsInfo('q_IoMdeM57U70GwqjXxGJw')
).resolves.toEqual(params);
});
});

How to use spread operator to update array inside an object?

What the fetch returns is a list of items. I want to add those into state.
const [state, setState] = useState({
list: {
items: [],
}
});
fetch('http://example.com/list/')
// GET response: [{ name: 'foo' }, { name: 'bar' }, { name: 'baz' }]
.then((resList) => resList.json())
.then((list) => {
list.forEach(({ name }) => {
const itemUrl = `https://example.com/list/${name}`;
fetch(itemUrl)
// GET responses:
// { name: 'foo', desc: '123' }
// { name: 'bar', desc: '456' }
// { name: 'baz', desc: '789' }
.then((itemRes) => itemRes.json())
.then((item) => {
setState((prevState) => ({
...prevState,
list: {
items: [...state.list.items, item]
},
});
})
})
}
})
console.log(state);
// result: [{ name: 'baz', desc: '789' }]
// but wanted: [{ name: 'foo', desc: '123' }, { name: 'bar', desc: '456' }, { name: 'baz', desc: '789' }]
In your case no need to use prevState in setState.
I prepared an example for you. Just be careful at using hooks.
https://codesandbox.io/s/recursing-wood-4npu1?file=/src/App.js:0-567
import React, { useState } from "react"
import "./styles.css"
export default function App() {
const [state, setState] = useState({
list: {
items: [
{ name: "foo", desc: "123" },
{ name: "bar", desc: "456" },
],
},
})
const handleClick = () => {
setState(() => ({
list: {
items: [...state.list.items, { name: "baz", desc: "789" }],
},
}))
}
return (
<div className="App">
<button onClick={handleClick}>Click Me </button>
<hr />
{JSON.stringify(state)}
</div>
)
}
You can't directly access the callback for useState hooks. This is how you can update state after fetching the data:
setState({
...state,
list: {
items:[...state.list.items, item]
},
});

React redux not updating component when adding to array

So no matter what I do I can't get the component to refresh when I add a item to an array in the redux store.
What I use in my reducer to add to the redux state:
case ADD_NOTE_META:
return [
...state,
action.note,
];
The connector:
import { connect } from 'react-redux';
import NoteSection from './NoteSection.component';
const mapStateToProps = state => ({
notes: state.NotesMeta,
});
const mapDispatchToProps = () => ({});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(NoteSection);
The component:
import React from 'react';
import PropTypes from 'prop-types';
import NoteSelectorContainer from './noteselector/NoteSelector.connector';
import DeleteConfirmationMessage from './deletenoteconfirmationmessage/DeleteConfirmationMessage.connector';
function NoteSection(props) {
const { notes } = props;
return (
<div id="divSelectNoteContainer">
{notes.map(item => (
<NoteSelectorContainer
note={item}
key={item.id}
/>
))}
<DeleteConfirmationMessage />
</div>
);
}
NoteSection.defaultProps = {
notes: [],
};
NoteSection.propTypes = {
notes: PropTypes.array,
};
export default NoteSection;
The state in redux is structured like:
{
NotesMeta: [
{
id: '5b6cd6c49a46d',
title: 'folder note',
tags: [
'test'
],
parentid: '5b6cd6c49a48d'
},
{
id: '5b6cd6c496ad2',
title: 'test note',
tags: [],
parentid: null
},
]
}
Output of console.log(notes) before add new note is run:
0: {id: "5b6cd6c49a46d", title: "folder note", tags: Array(1), parentid: "5b6cd6c49a48d"}
1: {id: "5b6cd6c496ad2", title: "test note", tags: Array(0), parentid: null}
After:
0: {id: "5b6cd6c49a46d", title: "folder note", tags: Array(1), parentid: "5b6cd6c49a48d"}
1: {id: "5b6cd6c496ad2", title: "test note", tags: Array(0), parentid: null}
2: {id: "5bb48aaae94c1", title: "New Note Title", tags: Array(0)}
I can see that the new note is added in both the redux store and the Notesection props however a new NoteSelectorContainer is never created.

Resources