I'm trying to test the component below using mock axios, however, it looks like the components are not rendered as expected, could someone help me on that? I have been stuck for quite a while. The component is fetching an api every 1 second.
const RealtimePrice = () => {
var [cryptoFeed, setCryptoFeed] = useState<cryptoFeed>([]);
var [currency, setCurrency] = useState(currencyList[0]);
var [cryptoSearch, setCryptoSearch] = useState("");
const url = `https://api.coingecko.com/api/v3/coins/markets?ids=${ids}&vs_currency=${currency}`;
const intervalRef = useRef<NodeJS.Timer>();
const onCurrencyChangeHandler = useCallback((newValue: string) => {
setCurrency(newValue);
}, []);
const onCryptoSearchChangeHandler = useCallback((newValue: string) => {
setCryptoSearch(newValue);
}, []);
useEffect(() => {
const getCryptoFeed = () => {
axios.get(url).then((response: any) => {
if (response.data) {
console.debug("The state is set");
setCryptoFeed(response.data);
} else {
console.debug("The state is not set");
setCryptoFeed([]);
}
});
};
getCryptoFeed();
intervalRef.current = setInterval(getCryptoFeed, 1000);
return () => {
clearInterval(intervalRef.current);
};
}, [url]);
const priceBlocks = cryptoFeed
.filter((crypto) =>
crypto.name.toLowerCase().includes(cryptoSearch.toLowerCase())
)
.map((crypto: any) => {
return (
<PriceBlock
key={crypto.id}
id={crypto.id}
name={crypto.name}
price={crypto.current_price}
volume={crypto.total_volume}
change={crypto.price_change_24h}
></PriceBlock>
);
});
return (
<div className={styles.container}>
<div className={styles["header-section"]}>
<h1>Cryptocurrency Realtime Price</h1>
<div className="input-group">
<Selectbox
onChange={onCurrencyChangeHandler}
defaultOption={currencyList[0]}
options={currencyList}
/>
<Inputbox
placeHolder="Enter crypto name"
onChange={onCryptoSearchChangeHandler}
/>
</div>
</div>
<div className={styles.priceblocks}>{priceBlocks}</div>
</div>
);
};
The test is the defined as the following, findByText gives error, it couldn't find the element.
import { render, screen } from "#testing-library/react";
import RealtimePrice from "../RealtimePrice";
describe("Realtime Price", () => {
it("should render the Bitcoin price block", async () => {
render(<RealtimePrice />);
const pb = await screen.findByText("Bitcoin");
expect(pb).toBeInTheDocument();
});
});
And in package.json I have set
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}"
],
"resetMocks": false
}
In src/mocks/axios.js
const mockGetResponse = [
{
id: "bitcoin",
name: "Bitcoin",
price: 20000,
volume: 12004041094,
change: -12241,
},
{
id: "solana",
name: "Solana",
price: 87,
volume: 200876648,
change: 122,
},
];
const mockResponse = {
get: jest.fn().mockResolvedValue(mockGetResponse),
};
export default mockResponse;
With our comments seems clear the issue is that mock is not returning a proper response.data (that's why u are setting an empty array as the state)
Try doing:
const mockResponse = {
get: jest.fn().mockResolvedValue({data: mockGetResponse}),
};
Related
I was following the documentation to implement google map on demand rides and deliveries solution (ODRD) here.
And my Map component in React:
const MapComponent = ({ styles }) => {
const ref = useRef(null);
const tripId = useRef<string>('');
const locationProvider =
useRef<google.maps.journeySharing.FleetEngineTripLocationProvider>();
const [error, setError] = useState<string | undefined>();
const mapOptions = useRef<MapOptionsModel>({
showAnticipatedRoutePolyline: true,
showTakenRoutePolyline: true,
destinationMarker: ICON_OPTIONS.USE_DEFAULT,
vehicleMarker: ICON_OPTIONS.USE_DEFAULT,
});
const [trip, setTrip] = useState<TripModel>({
status: null,
dropOff: null,
waypoints: null,
});
const setTripId = (newTripId: string) => {
tripId.current = newTripId;
if (locationProvider.current) locationProvider.current.tripId = newTripId;
};
const setMapOptions = (newMapOptions: MapOptionsModel) => {
mapOptions.current.showAnticipatedRoutePolyline =
newMapOptions.showAnticipatedRoutePolyline;
mapOptions.current.showTakenRoutePolyline =
newMapOptions.showTakenRoutePolyline;
mapOptions.current.destinationMarker = newMapOptions.destinationMarker;
mapOptions.current.vehicleMarker = newMapOptions.vehicleMarker;
setTripId(tripId.current);
};
const authTokenFetcher = async () => {
const response = await fetch(
`${PROVIDER_URL}/token/consumer/${tripId.current}`
);
const responseJson = await response.json();
return {
token: responseJson.jwt,
expiresInSeconds: 3300,
};
};
useEffect(() => {
locationProvider.current =
new google.maps.journeySharing.FleetEngineTripLocationProvider({
projectId: PROVIDER_PROJECT_ID,
authTokenFetcher,
tripId: tripId.current,
pollingIntervalMillis: DEFAULT_POLLING_INTERVAL_MS,
});
locationProvider.current.addListener(
'error',
(e: google.maps.ErrorEvent) => {
setError(e.error.message);
}
);
locationProvider.current.addListener(
'update',
(
e: google.maps.journeySharing.FleetEngineTripLocationProviderUpdateEvent
) => {
if (e.trip) {
setTrip({
status: e.trip.status,
dropOff: e.trip.dropOffTime,
waypoints: e.trip.remainingWaypoints,
});
setError(undefined);
}
}
);
const mapViewOptions: google.maps.journeySharing.JourneySharingMapViewOptions =
{
element: ref.current as unknown as Element,
locationProvider: locationProvider.current,
anticipatedRoutePolylineSetup: ({ defaultPolylineOptions }) => {
return {
polylineOptions: defaultPolylineOptions,
visible: mapOptions.current.showAnticipatedRoutePolyline,
};
},
takenRoutePolylineSetup: ({ defaultPolylineOptions }) => {
return {
polylineOptions: defaultPolylineOptions,
visible: mapOptions.current.showTakenRoutePolyline,
};
},
destinationMarkerSetup: ({ defaultMarkerOptions }) => {
if (
mapOptions.current.destinationMarker !== ICON_OPTIONS.USE_DEFAULT
) {
defaultMarkerOptions.icon =
mapOptions.current.destinationMarker.icon;
}
return { markerOptions: defaultMarkerOptions };
},
vehicleMarkerSetup: ({ defaultMarkerOptions }) => {
if (mapOptions.current.vehicleMarker !== ICON_OPTIONS.USE_DEFAULT) {
// Preserve some default icon properties.
if (defaultMarkerOptions.icon) {
defaultMarkerOptions.icon = Object.assign(
defaultMarkerOptions.icon,
mapOptions.current.vehicleMarker.icon
);
}
}
return { markerOptions: defaultMarkerOptions };
},
};
const mapView = new google.maps.journeySharing.JourneySharingMapView(
mapViewOptions
);
// Provide default zoom & center so the map loads even if trip ID is bad or stale.
mapView.map.setOptions(DEFAULT_MAP_OPTIONS);
}, []);
return (
<div style={styles.map} ref={ref} />
);
};
And my App component like this:
import React from 'react';
import { Wrapper, Status } from '#googlemaps/react-wrapper';
import MapComponent from './src/components/MapComponent';
import { API_KEY } from './src/utils/consts';
const render = (status: Status) => <Text>{status}</Text>;
const App = () => {
return (
<Wrapper
apiKey={API_KEY}
render={render}
version={'beta'}
// #ts-ignore
libraries={['journeySharing']}
>
<MapComponent />
</Wrapper>
);
};
Everything will works fine but I do not know how to destroy the map when component unmount in React. That's why my App always call API update the trip info.
I was tried to use clean up function in useEffect:
useEffect(() => {
locationProvider.current =
new google.maps.journeySharing.FleetEngineTripLocationProvider({
projectId: PROVIDER_PROJECT_ID,
authTokenFetcher,
tripId: tripId.current,
pollingIntervalMillis: DEFAULT_POLLING_INTERVAL_MS,
});
locationProvider.current.addListener(
'error',
(e: google.maps.ErrorEvent) => {
setError(e.error.message);
}
);
const updateEvent = locationProvider.current.addListener(
'update',
(
e: google.maps.journeySharing.FleetEngineTripLocationProviderUpdateEvent
) => {
if (e.trip) {
setTrip({
status: e.trip.status,
dropOff: e.trip.dropOffTime,
waypoints: e.trip.remainingWaypoints,
});
setError(undefined);
}
}
);
const mapViewOptions: google.maps.journeySharing.JourneySharingMapViewOptions =
{
element: ref.current as unknown as Element,
locationProvider: locationProvider.current,
anticipatedRoutePolylineSetup: ({ defaultPolylineOptions }) => {
return {
polylineOptions: defaultPolylineOptions,
visible: mapOptions.current.showAnticipatedRoutePolyline,
};
},
takenRoutePolylineSetup: ({ defaultPolylineOptions }) => {
return {
polylineOptions: defaultPolylineOptions,
visible: mapOptions.current.showTakenRoutePolyline,
};
},
destinationMarkerSetup: ({ defaultMarkerOptions }) => {
if (
mapOptions.current.destinationMarker !== ICON_OPTIONS.USE_DEFAULT
) {
defaultMarkerOptions.icon =
mapOptions.current.destinationMarker.icon;
}
return { markerOptions: defaultMarkerOptions };
},
vehicleMarkerSetup: ({ defaultMarkerOptions }) => {
if (mapOptions.current.vehicleMarker !== ICON_OPTIONS.USE_DEFAULT) {
// Preserve some default icon properties.
if (defaultMarkerOptions.icon) {
defaultMarkerOptions.icon = Object.assign(
defaultMarkerOptions.icon,
mapOptions.current.vehicleMarker.icon
);
}
}
return { markerOptions: defaultMarkerOptions };
},
};
const mapView = new google.maps.journeySharing.JourneySharingMapView(
mapViewOptions
);
// Provide default zoom & center so the map loads even if trip ID is bad or stale.
mapView.map.setOptions(DEFAULT_MAP_OPTIONS);
return () => {
mapView.map = null // or mapView.map.setmap(null);
google.maps.event.removeListener(updateEvent);
};
}, []);
But it was not working. Hope anyone can help me find out this. Thanks
I am trying to render some dynamic data in an element using useEffect Hook, which is not working.
Below is abc.tsx which has byTestOne(globalThis.test) which is responsible to send data back to us using globalThis.test value
const abc = () => {
const [data, setData] = useState<any>([]);
// globalThis.test is something that changes and when it changes, byTestOne triggers and get the data based on globalThis.test
useEffect(() => {
async function fetchData() {
const data = byTestOne(globalThis.test).then((data: any) => {
return data
})
setData(await data)
}
fetchData();
}, [globalThis.test]);
return (
<ImageBackground
source={require("../assets/images/abc.png")}
style={styles.bg}>
<View style={styles.containerHome}>
<CardStack>
{
// Below Element is not getting rendered
data.map((item: any) => (
<Card key={item.id}>
<CardItemForSwiper
name={item.name}
description={item.description}
/>
</Card>
)
)
}
</CardStack>
</View>
</ImageBackground>
);
};
export default ABC;
byTestOne() looks like this:
let data = [{
name: "test1",
description: "desc",
test: "one"
},
{
name: "test2",
description: "desc",
test: "two"
}]
export const byTestOne = (test: string) => {
const dataA = new Promise((resolve) => {
const filData = data.filter((rawData) => {
return rawData.test == test
})
if (filData.length > 0) {
globalThis.test = test
}
return resolve(filData)
})
return dataA
}
I wonder if calling api for every element rendering is possible. The code below didn't work for me.
export default function App() {
const items = [
{ title: 1, description: "description1" },
{ title: 2, description: "description2" }
];
const finalTitleByApi = async (title) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${title}`
).then((response) => response.json());
return response;
};
return (
<div>
{items.map((item) => {
return (
<p>
{finalTitleByApi(item.title).title}
</p>
);
})}
</div>
);
}
What is wrong with the code above. Any help is will be appreciated. Thank you.
This is the example codesandbox link https://codesandbox.io/s/for-each-rendered-element-that-calls-api-pmcnn6?file=/src/App.js:879-886
Try to use react-async library , hope will be helpful react-async
To trigger the async function use useEffect to invoke it during initial rendering of the component as follows. Additionally, you can use a state to manage it as well.
const [responseState, setResponseState] = useState(null);
const finalTitleByApi = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
).then((response) => response.json());
console.log("Response: ", response);
setResponseState(response);
};
useEffect(() => {
finalTitleByApi();
}, []);
useEffect(() => {
console.log("Response State: ", responseState);
}, [responseState]);
One solution that I think is
import React, { useEffect } from "react";
import "./styles.css";
import {useApplicationContext} from './Context';
export default function App() {
const {titles, setTitles} = useApplicationContext();
const items = [
{ title: "1", description: "description1" },
{ title: "5", description: "description2" },
{ title: "8", description: "description2" },
{ title: "9", description: "description2" },
{ title: "10", description: "description2" },
{ title: "24", description: "description2" }
];
const makeDivs = () => {
let a = {};
items.map(async (item) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${item.title}`
).then(response => response.json());
a[item.title] = res.title;
setTitles((prevState) => {
return {...a}
});
})
}
React.useEffect(()=> {
makeDivs()
}, [])
// console.log(a )
return (
<div>
{JSON.stringify(titles)}
{items.map((item, index) => {
return (
<p
key={Math.random()}
style={{
color: "black",
backgroundColor: "yellow",
height: 400,
width: 400
}}
>
<span>index: {item.title} {titles && titles[item.title]}</span>
</p>
);
})}
</div>
);
}
used Context provider for not re-render component state
link of sandbox is Sandbox
There seems to be something wrong with the way I update state, as it gets overwritten...
import Servis from "./funkc/servisni";
import React, { useState, useEffect } from "react";
export default function ContactUpdate(props) {
const initialState = {
ime: props.item.Ime,
prezime: props.item.Prezime,
datum: props.item.Datum,
kontakt: props.item.Kontakt,
published: props.item.Published,
id: props.Id,
};
const [theItem, setTheItem] = useState();
const [message, setMessage] = useState();
useEffect(() => {
setTheItem(props.item);
console.log(theItem);
}, []);
const handleInputChange = (event) => {
const { name, value } = event.target;
setTheItem({ ...theItem, [name]: value });
console.log(theItem, props.Id);
};
the problem seems to be in the following:
const updateItem = (theItem) => {
let data = {
Ime: theItem.Ime,
Prezime: theItem.Prezime,
Kontakt: theItem.Kontakt,
Datum: theItem.Datum,
Published: true,
Id: theItem.id,
};
Servis.update(theItem.id, data)
.then(() => {
setMessage("Uspjesno ste izmijenili unos!");
})
.catch((e) => {
console.log(e);
});
};
as visible in the console.log
return (
<div className="container">
{console.log(("theItem", props.Id, theItem))}
{theItem ? (
<div className="edit-form">
<h4>Kontakt</h4>
...
<button type="submit" onClick={updateItem}>
Update
</button>
<p>{message}</p>
</div>
) : (
<div>
<br />
<p>Odaberi jedan broj...</p>
</div>
)}{" "}
</div>
);
}
The call on the updateItem function by clicking on the 'Update' button results in the error : Function DocumentReference .update() called with invalid data. Unsupported field value...
Resolved through being careful about naming variables...
</div>
<ContactUpdate item={item} id={theId} />
</div>
and then
const updateItem = () => {
let data = {
Ime: theItem.Ime,
Prezime: theItem.Prezime,
Kontakt: theItem.Kontakt,
Datum: theItem.Datum,
published: true,
id: props.id,
};
Servis.update(props.id, data)
.then(() => {
setMessage("Uspjesno ste izmijenili unos!");
})
.catch((e) => {
console.log(e);
});
};
I'm test driving a pattern I found online known as meiosis as an alternative to Redux using event streams. The concept is simple, the state is produced as a stream of update functions using the scan method to evaluate the function against the current state and return the new state. It works great in all of my test cases but when I use it with react every action is called twice. You can see the entire app and reproduce the issue at CodeSandbox.
import state$, { actions } from "./meiosis";
const App = () => {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState({
title: "",
status: "PENDING"
});
useEffect(() => {
state$
.pipe(
map(state => {
return state.get("todos")
}),
distinctUntilChanged(),
map(state => state.toJS())
)
.subscribe(state => setTodos(state));
}, []);
useEffect(() => {
state$
.pipe(
map(state => state.get("todo")),
distinctUntilChanged(),
map(state => state.toJS())
)
.subscribe(state => setNewTodo(state));
}, []);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{genList(todos)}
<div className="formGroup">
<input
type="text"
value={newTodo.title}
onChange={evt => actions.typeNewTodoTitle(evt.target.value)}
/>
<button
onClick = {() => {
actions.addTodo()
}}
>
Add TODO
</button>
<button
onClick={() => {
actions.undo();
}}
>UNDO</button>
</div>
</header>
</div>
);
};
Meisos
import { List, Record } from "immutable";
import { Subject } from "rxjs";
const model = {
initial: {
todo: Record({
title: "",
status: "PENDING"
})(),
todos: List([Record({ title: "Learn Meiosis", status: "PENDING" })()])
},
actions(update) {
return {
addTodo: (title, status = "PENDING") => {
update.next(state => {
console.log(title);
if (!title) {
title = state.get("todo").get("title");
}
const todo = Record({ title, status })();
return state.set("todos", state.get("todos").push(todo));
});
},
typeNewTodoTitle: (title, status = "PENDING") => {
update.next(state => {
return state.set("todo", Record({ title, status })())
});
},
resetTodo: () => {
update.next(state =>
state.set("todo", Record({ title: "", status: "PENDING" })())
);
},
removeTodo: i => {
update.next(state => state.set("todos", state.get("todos").remove(i)));
}
};
}
}
const update$ = new BehaviorSubject(state => state) // identity function to produce initial state
export const actions = model.actions(update$);
export default update$;
Solve my problem. It stemmed from a misunderstanding of how RXJS was working. An issue on the RxJS github page gave me the answer. Each subscriptions causes the observable pipeline to be re-evaluated. By adding the share operator to the pipeline it resolves this behavior.
export default update$.pipe(
scan(
(state, updater) =>
updater(state),
Record(initial)()
),
share()
);