I have a component that loads data from an API which I mocked for my test but it is not loaded as the test cannot find the element which contain the data.
component:
import { useDispatch, useSelector } from "react-redux";
import { useState, useEffect, useCallback } from "react";
import { businessDataActions } from "../store/business-data";
import { fetchBusinessListing } from "../services/business-listing";
import styles from "../styles/BizCard.module.css";
import BizCardItem from "./BizCardItem";
const BizCard = (props) => {
const dispatch = useDispatch();
const [listing, setListing] = useState([]);
//load all listing
const fetchListing = useCallback(async () => {
dispatch(businessDataActions.setIsLoading({ isLoading: true }));
const ListingService = await fetchBusinessListing();
if (ListingService.success) {
setListing(ListingService.data);
} else {
dispatch(
businessDataActions.setNotify({
severity: "error",
message: "Problem when fetching listing.",
state: true,
})
);
}
dispatch(businessDataActions.setIsLoading({ isLoading: false }));
}, []);
useEffect(() => {
fetchListing();
}, []);
const businessList = listing.map((item) => (
<BizCardItem
key={item.key}
id={item.id}
name={item.name}
shortDescription={item.shortDescription}
imageUrl={item.imageUrl}
/>
));
return (
<div className={styles.grid} role="grid">
{businessList}
</div>
);
};
test file:
const bizListing = [
...some fake data
];
jest.mock("../../services/business-listing", () => {
return function fakeListing() {
return { success: true, data: bizListing };
}
});
afterEach(cleanup);
describe('BizCard', () => {
test("loading listing", async () => {
useSession.mockReturnValueOnce([null, false]);
await act(async () => {render(
<BizCard />
)});
const itemGrid = await screen.findAllByRole("gridcell");
expect(itemGrid).not.toHaveLength(0);
});
});
services/business-listing:
export const fetchBusinessListing = async() => {
try {
const response = await fetch(
"/api/business"
);
if (!response.ok) {
throw new Error('Something went wrong!');
}
const data = await response.json();
const loadedBusiness = [];
for (const key in data) {
let imgUrl =
data[key].imageUrl !== "undefined" && data[key].imageUrl !== ""
? data[key].imageUrl
: '/no-image.png';
loadedBusiness.push({
key: data[key]._id,
id: data[key]._id,
name: data[key].businessName,
shortDescription: data[key].shortDescription,
imageUrl: imgUrl,
});
}
return { success: true, data: loadedBusiness };
} catch (error) {
return ({success: false, message: error.message});
}
}
The test executed with these returned:
TypeError: (0 , _businessListing.fetchBusinessListing) is not a function
48 | // }
49 |
> 50 | const ListingService = await fetchBusinessListing();
Unable to find role="gridcell"
I can confirm gridcell is rendered when I am using browser.
Can anyone please shed some light on my problem
Manage to solve the problem myself, problem is with the mock:
jest.mock("../../services/business-listing", () => {
return {
fetchBusinessListing: jest.fn(() => { return { success: true, data: bizListing }}),
}
});
Related
I have this function inside a helper:
export const useDAMProductImages = (imageId: string) => {
const {
app: { baseImgDomain },
} = getConfig();
const response: MutableRefObject<string[]> = useRef([]);
useEffect(() => {
const getProductImages = async (imageId: string) => {
try {
const url = new URL(FETCH_URL);
const res = await fetchJsonp(url.href, {
jsonpCallbackFunction: 'callback',
});
const jsonData = await res.json();
response.current = jsonData;
} catch (error) {
response.current = ['error'];
}
};
if (imageId) {
getProductImages(imageId);
}
}, [imageId]);
return response.current;
};
In test file:
import .....
jest.mock('fetch-jsonp', () =>
jest.fn().mockImplementation(() =>
Promise.resolve({
status: 200,
json: () => Promise.resolve({ set: { a: 'b' } }),
}),
),
);
describe('useDAMProductImages', () => {
beforeEach(() => {
jest.clearAllMocks();
cleanup();
});
it('should return empty array', async () => {
const { result: hook } = renderHook(() => useDAMProductImages('a'), {});
expect(hook.current).toMatchObject({ set: { a: 'b' } });
});
});
The problem is that hook.current is an empty array. Seems that useEffect is never called. Can someone explain to me what I'm doing wrong and how I should write the test? Thank you in advance
Hey folks really hope someone can help me here. I'm successfully updating my object in my mongo cluster, it updates but it does not render that update straight away to the browser. It will only update after a reload or when I run my update function again, it doesn't fetch that update straight away and I can't understand why. Does anyone have any suggestions?
I'm using context and reducer.
PlantDetails
import { usePlantsContext } from "../hooks/usePlantsContext";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { useState } from "react";
import CalendarComponent from "./CalendarComponent";
const PlantDetails = ({ plant }) => {
const [watered, setWatered] = useState(false)
const [newWaterDate, setNewWaterDate] = useState("")
const { dispatch } = usePlantsContext();
const handleClick = async () => {
const response = await fetch("/api/plants/" + plant._id, {
method: "DELETE",
});
const json = await response.json();
if (response.ok) {
dispatch({ type: "DELETE_PLANT", payload: json });
}
};
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
console.log('updated')
setWatered(false)
}
return (
<div className="plant-details">
<h4>{plant.plantName}</h4>
<p>{plant.quickInfo}</p>
<p>
{formatDistanceToNow(new Date(plant.createdAt), { addSuffix: true })}
</p>
<span onClick={handleClick}>delete</span>
<div>
<p>next water date: {plant.nextWaterDate}</p>
<input onChange={(e) => setNewWaterDate(e.target.value)}/>
<button onClick={updatePlant}>update</button>
<input value={watered} type="checkbox" id="toWater" onChange={() => setWatered(true)}/>
<label for="toWater">watered</label>
{watered && <CalendarComponent updatePlant={updatePlant} setNextWaterDate={setNewWaterDate}/>}
</div>
</div>
);
};
export default PlantDetails;
Context which wraps my
import { createContext, useReducer } from 'react'
export const PlantsContext = createContext()
export const plantsReducer = (state, action) => {
switch(action.type) {
case 'SET_PLANTS':
return {
plants: action.payload
}
case 'CREATE_PLANT':
return {
plants: [action.payload, ...state.plants]
}
case 'DELETE_PLANT':
return {
plants: state.plants.filter((p) => p._id !== action.payload._id)
}
case 'UPDATE_PLANT':
return {
plants: state.plants.map((p) => p._id === action.payload._id ? action.payload : p )
}
default:
return state
}
}
export const PlantsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(plantsReducer, {
plants: null
})
return (
<PlantsContext.Provider value={{...state, dispatch}}>
{ children }
</PlantsContext.Provider>
)
}
My plantController (update)
const updatePlant = async (req, res) => {
const { id } = req.params
if(!mongoose.Types.ObjectId.isValid(id)) {
return res.status(404).json({ error: "No plant" })
}
const plant = await Plant.findByIdAndUpdate({ _id: id }, {
...req.body
})
if (!plant) {
return res.status(400).json({ error: "No plant" })
}
res.status(200)
.json(plant)
}
Home component
import { useEffect } from "react";
import PlantDetails from "../components/PlantDetails";
import PlantForm from "../components/PlantForm";
import CalendarComponent from "../components/CalendarComponent";
import { usePlantsContext } from "../hooks/usePlantsContext";
const Home = () => {
const { plants, dispatch } = usePlantsContext();
useEffect(() => {
const fetchPlants = async () => {
console.log("called");
// ONLY FOR DEVELOPMENT!
const response = await fetch("/api/plants");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_PLANTS", payload: json });
}
};
fetchPlants();
}, [dispatch]);
return (
<div className="home">
<div className="plants">
{plants &&
plants.map((plant) => <PlantDetails key={plant._id} plant={plant} />)}
</div>
<PlantForm />
</div>
);
};
export default Home;
Any help would be greatly appreciated.
My patch requests were going through smoothly but my state would not update until I reloaded my page. It was not returning the document after the update was applied.
https://mongoosejs.com/docs/tutorials/findoneandupdate.html#:~:text=%3B%20//%2059-,You,-should%20set%20the
How would I avoid the infinite loop issue?
I'm getting an error while rendering the following component:
Too many re-renders. React limits the number of renders to prevent an infinite loop.?
TeamContent.js re-renders multiple times, how can I set an initial render on load?
Error given
TeamContent.js
import { useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
fetchTeamPlayers,
fetchUpcomingGames,
fetchPreviousGames,
fetchLiveGames,
} from "../../../data/UserInfo/infoActions";
import TeamPlayers from "./TeamPlayers";
import TeamNext from "./TeamNext";
import TeamPrevious from "./TeamPrevious";
import LiveEvent from "./Live.js/LiveEvent";
function TeamContent(props) {
console.log("test");
let containsLiveGame = false;
const dispatch = useDispatch();
const liveGames = useSelector((store) => store.userInfo.live.games.all);
const status = useSelector((store) => store.userInfo.playersLoadStatus);
const UpcomingGamesstatus = useSelector(
(store) => store.userInfo.upcomingGamesStatus
);
const previousGamesStatus = useSelector(
(store) => store.userInfo.previousGamesStatus
);
const liveStatus = useSelector((store) => store.userInfo.live.games.status);
liveGames.map((game) => {
const verifyHomeTeam = +game.idHomeTeam === +props.teamID;
const verifyAwayTeam = +game.idAwayTeam === +props.teamID;
if (verifyAwayTeam || verifyHomeTeam) {
containsLiveGame = true;
}
});
// -----> request team data
useEffect(() => {
dispatch(fetchTeamPlayers(props.teamID));
dispatch(fetchUpcomingGames(props.teamID));
dispatch(fetchPreviousGames(props.teamID));
dispatch(fetchLiveGames());
}, [dispatch, props.teamID]);
useEffect(() => {
dispatch(fetchLiveGames());
const interval = setInterval(() => {
dispatch(fetchLiveGames());
}, 30000);
return () => clearInterval(interval);
}, [dispatch]);
return (
<div className="teamDash">
<div className="dashLeft">
<div
className="dashLeftHead"
style={{
backgroundImage: `url(${props.stadiumImg})`,
}}
>
<div className="dashLeftHeadAbs"></div>
<div className="dashLeftHeadIntro">
<span>{props.stadiumName}</span>
<h3>{props.teamName}</h3>
</div>
</div>
{liveStatus !== "error" && containsLiveGame && <LiveEvent />}
{status !== "error" && (
<div className="dashLeftPlayers">
<TeamPlayers />
</div>
)}
<div className="dashLeftDesc">
<p>{props.teamDesc}</p>
</div>
</div>
<div className="dashRight">
{UpcomingGamesstatus === "error" ? (
console.log("unable to load upcoming games")
) : (
<div className="upcomingGames">
<TeamNext id={props.teamID} />
</div>
)}
{previousGamesStatus === "error" ? (
console.log("unable to load previous games")
) : (
<div className="previousGames">
<TeamPrevious />
</div>
)}
</div>
</div>
);
}
export default TeamContent;
infoActions.js
import { API_URL } from "../Api";
import { infoActions } from "./infoSlice";
export function fetchTeams() {
return (dispatch) => {
dispatch(infoActions.loadStatusHandler({ status: "loading" }));
async function getTeams() {
try {
const rq = await fetch(`${API_URL}Lookup_all_teams.php?id=4387`);
const res = await rq.json();
const data = res.teams;
dispatch(infoActions.loadTeamsHandler({ teams: data }));
dispatch(infoActions.loadStatusHandler({ status: "done" }));
} catch (error) {
dispatch(infoActions.loadStatusHandler({ status: "error" }));
}
}
getTeams();
};
}
export function fetchTeamPlayers(id) {
return (dispatch) => {
async function getPlayers() {
dispatch(infoActions.statusPlayersHandler({ status: "loading" }));
try {
const rq = await fetch(`${API_URL}lookup_all_players.php?id=${id}`);
const res = await rq.json();
const data = res.player;
dispatch(infoActions.loadPlayersHandler({ players: data }));
dispatch(infoActions.statusPlayersHandler({ status: "done" }));
} catch (error) {
dispatch(infoActions.statusPlayersHandler({ status: "error" }));
}
}
getPlayers();
};
}
export function fetchUpcomingGames(id) {
return (dispatch) => {
dispatch(infoActions.statusUGHandler({ status: "loading" }));
async function getGames() {
try {
const rq = await fetch(`${API_URL}eventsnext.php?id=${id}`);
const res = await rq.json();
const data = res.events;
dispatch(infoActions.upcomingGamesHandler({ games: data }));
dispatch(infoActions.statusUGHandler({ status: "done" }));
} catch (error) {
dispatch(infoActions.statusUGHandler({ status: "error" }));
}
}
getGames();
};
}
export function fetchPreviousGames(id) {
return (dispatch) => {
dispatch(infoActions.statusPGHandler({ status: "loading" }));
async function getGames() {
try {
const rq = await fetch(`${API_URL}eventslast.php?id=${id}`);
const res = await rq.json();
const data = res.results;
dispatch(infoActions.previousGamesHandler({ games: data }));
dispatch(infoActions.statusPGHandler({ status: "done" }));
} catch (error) {
dispatch(infoActions.statusPGHandler({ status: "error" }));
}
}
getGames();
};
}
export function fetchLiveGames() {
return (dispatch) => {
dispatch(infoActions.statusLiveGames({ status: "loading" }));
async function getGames() {
try {
const rq = await fetch(
`https://www.thesportsdb.com/api/v2/json/40130162/livescore.php?l=4387`
);
const res = await rq.json();
const data = res.events;
dispatch(infoActions.statusLiveGames({ status: "done" }));
dispatch(infoActions.loadLiveGames({ liveGames: data }));
} catch (error) {
dispatch(infoActions.statusLiveGames({ status: "error" }));
}
}
getGames();
};
}
Try remove dispatch from the array you passed to
useEffect(() => {
...
}, [dispatch, props.teamID])
and
useEffect(() => {
...
}, [dispatch])
dispatch is a function, and if you include it into the useEffect listener, the useEffect will trigger on every render
I am working on this project in React JS where I fetch data from this API URL for my frontend development.
I have made my custom hooks to fetch the data into several files following this medium article as follows:
useApiResult.js
import { useState, useEffect } from "react";
export const useApiResult = (request) => {
const [results, setResults] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch(request)
.then(async (response) => {
if (response.ok) {
setResults(await response.json());
setError(null);
} else {
setError(await response.text())
}
})
.catch((err) => {
setError(err.message);
});
}, [request]);
return [results, error];
};
useImages.js
import { useMemo } from "react";
import { useApiResult } from "./useApiResult";
const BASE_URL = "http://api.vidyarajkumari.com";
const createUrl = (base, path) => `${base}${path}`;
const getImages = () => [
createUrl(BASE_URL, "/images/"),
{
method: "GET",
}
];
export const useImages = () => {
const request = useMemo(() => getImages(), []);
return useApiResult(request);
}
React component: Images.js
import React from "react";
import { useImages } from "../../hooks/useImages";
export default function Images() {
const [images, error] = useImages();
//console.log(images);
//console.log(error);
return (
<>
<div className="row">
{
images.map((item, index) => {
<div key={index} className="col-md-4 animate-box">
...
// Rest of code goes here
}
}
</>
</>
)
}
The problem is that I am unable to get the data in the Images.js component from the useImages hook. The console.log values of images return null. This has been bugging me for a while now and I would greatly appreciate a solution to this. What am I doing wrong here and how can I work around this?
P.S. The API Url is live; so feel free to reference it. Thank you for your time.
I Have a better way to do this using useReducer and custom hook, check this:
By the way, I think your API URL has some problems! (I added input for fetching another URL for test)
const IMAGE_URL = "http://api.vidyarajkumari.com/images/";
const initialState = { loading: true };
function fetchReducer(state, action) {
switch (action.type) {
case "fetch":
return {
...state,
error: undefined,
loading: true,
};
case "data":
return {
...state,
data: action.data,
loading: false,
};
case "error":
return {
...state,
error: "Error fetching data. Try again",
loading: false,
};
default:
return state;
}
}
function useFetch(url) {
const [state, dispatch] = React.useReducer(fetchReducer, initialState);
React.useEffect(() => {
dispatch({ type: "fetch" });
fetch(url, {
headers: {
accept: "application/json",
},
})
.then((res) => res.json())
.then((data) => dispatch({ type: "data", data }))
.catch((e) => {
console.warn(e.message);
dispatch({ type: "error" });
});
}, [url]);
return {
loading: state.loading,
data: state.data,
error: state.error,
};
}
function FetchComponent({url}) {
const { loading, data, error } = useFetch(url);
console.log(data);
if (loading) {
return <p>Fetching {url}...</p>;
}
if (error) {
return <p>{error}</p>
}
return <div>{JSON.stringify(data)}</div>
}
const App = () => {
const [url, setUlr] = React.useState(IMAGE_URL)
const inputEl = React.useRef(null);
const changeUrl = () => setUlr(inputEl.current.value)
return (
<React.Fragment>
<input defaultValue="https://icanhazdadjoke.com/" ref={inputEl} type="text" />
<button onClick={changeUrl}>Fetch</button>
{url && <FetchComponent url={url}/>}
</React.Fragment>
)
}
ReactDOM.render(<App/>, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Give results and error also, in the dependency array, So that component get render when result is updated.
import { useState, useEffect } from "react";
export const useApiResult = (request) => {
const [results, setResults] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch(request)
.then(async (response) => {
if (response.ok) {
setResults(await response.json());
setError(null);
} else {
setError(await response.text())
}
})
.catch((err) => {
setError(err.message);
});
}, [request, results, error]);
return [results, error];
};
I have question about debounce async function. Why my response is undefined? validatePrice is ajax call and I receive response from server and return it (it is defined for sure).
I would like to make ajax call after user stops writing and update state after I get reponse. Am I doing it right way?
handleTargetPriceDayChange = ({ target }) => {
const { value } = target;
this.setState(state => ({
selected: {
...state.selected,
Price: {
...state.selected.Price,
Day: parseInt(value)
}
}
}), () => this.doPriceValidation());
}
doPriceValidation = debounce(async () => {
const response = await this.props.validatePrice(this.state.selected);
console.log(response);
//this.setState({ selected: res.TOE });
}, 400);
actions.js
export function validatePrice(product) {
const actionUrl = new Localization().getURL(baseUrl, 'ValidateTargetPrice');
return function (dispatch) {
dispatch({ type: types.VALIDATE_TARGET_PRICE_REQUEST });
dispatch(showLoader());
return axios.post(actionUrl, { argModel: product }, { headers })
.then((res) => {
dispatch({ type: types.VALIDATE_TARGET_PRICE_REQUEST_FULFILLED, payload: res.data });
console.log(res.data); // here response is OK (defined)
return res;
})
.catch((err) => {
dispatch({ type: types.VALIDATE_TARGET_PRICE_REQUEST_REJECTED, payload: err.message });
})
.then((res) => {
dispatch(hideLoader());
return res.data;
});
};
}
Please find below the working code with lodash debounce function.
Also here is the codesandbox link to play with.
Some changes:-
1) I have defined validatePrice in same component instead of taking from prop.
2) Defined the debounce function in componentDidMount.
import React from "react";
import ReactDOM from "react-dom";
import _ from "lodash";
import "./styles.css";
class App extends React.Component {
state = {
selected: { Price: 10 }
};
componentDidMount() {
this.search = _.debounce(async () => {
const response = await this.validatePrice(this.state.selected);
console.log(response);
}, 2000);
}
handleTargetPriceDayChange = ({ target }) => {
const { value } = target;
console.log(value);
this.setState(
state => ({
selected: {
...state.selected,
Price: {
...state.selected.Price,
Day: parseInt(value)
}
}
}),
() => this.doPriceValidation()
);
};
doPriceValidation = () => {
this.search();
};
validatePrice = selected => {
return new Promise(resolve => resolve(`response sent ${selected}`));
};
render() {
return (
<div className="App">
<input type="text" onChange={this.handleTargetPriceDayChange} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Hope that helps!!!
You can use the throttle-debounce library to achieve your goal.
Import code in top
import { debounce } from 'throttle-debounce';
Define below code in constructor
// Here I have consider 'doPriceValidationFunc' is the async function
this.doPriceValidation = debounce(400, this.doPriceValidationFunc);
That's it.