I'm trying to display a particular group detail page from the group's list. I have managed to display all the group lists from the backend, but when it comes to displaying each group detail page, I'm receiving a status of 404. I don't understand what the issue is, why it can't find that group on that particular id.
On Backend, I made a getGroup controller:
export const getGroup = async (req, res) => {
// Route parameters are named URL segments that are used to capture the values specified at their position in the URL.
// Deconstructing it so that we can use it. It's like unpacking it.
const { id } = req.params
try {
// finding a group by it's Id
const group = await Group.findById(id)
// Send it in response
res.status(200).json(group)
} catch (error) {
// In case it didn't work out
res.status(404).json({ message: error.message })
}
}
Then Routes:
router.get('/:id', getGroup)
Main Route:
app.use('/groups', groupRoutes)
On Front End, I'm using Axios to create an API.
export const fetchGroup = (id) => API.get(`/groups/${id}`)
Using Redux for state management, so here is my action creator for a particular group.
// Will get a particular group
export const getGroup = (id) => async (dispatch) => {
try {
// Over here we are fetching all the data from api and dispatching it.
const { data } = await api.fetchGroup(id)
dispatch({type: FETCH_GROUP, payload: data})
} catch (error) {
console.log(error.message)
}
}
Reducer for fetching a group:
// Reducers take state and an action as arguments and return a new state in result.
const groups = ( state = { groups: [] }, action ) => {
switch (action.type) {
case FETCH_ALL:
return {
...state,
groups: action.payload
}
case FETCH_GROUP:
// set to group because we are getting a single group
return { ...state, group: action.payload }
default:
return state
}
}
export default groups
My GroupPage.js, where I'm dispatching my functions.
const GroupPage = () => {
const { group } = useSelector((state) => state.groups)
const dispatch = useDispatch()
const { id } = useParams()
useEffect(()=> {
dispatch(getGroup(id))
}, [id])
//if(!group) return null
return (
isLoading ? <div style={{width: 50, height: 50 }}>
<CircularProgressbar value={66} text={66} />
</div> : (
<div>
<Container>
<Top>
<div>
<h1>{group.groupName}</h1>
<p>{group.location}</p>
<p>{group.members}</p>
</div>
</Top>
</Container>
</div>
)
)
}
export default GroupPage
From the above code, if I check for a group:
if(!group) return null
Then I'm getting the error
typeError: Cannot read property 'groupName' of undefined
Not sure what the problem is. I have also tried the data fetching with the loading state, but that didn't resolve the issue either.
Please, any help would be appreciated.
Related
One of my api response with boolean(with the name: used), my logic is if the response is used will show red_light and green_light if not used.
const red_light = <div className="h-2.5 w-2.5 rounded-full bg-red-700 mr-2"></div>
const green_light = <div className="h-2.5 w-2.5 rounded-full bg-green-400 mr-2"></div>
function lighting(code) {
fetch(`API`)
.then((response) => {
if (!response.ok) {
throw new Error(
`This is an HTTP error: The status is ${response.status}`
);
}
return response.json();
})
.then((actualData) => {
return (actualData.used ? red_light : green_light)
})}
const MembershipLight = (code) => {
return (
lighting(code)
);
};
export default MembershipLight;
but the page gone blank, which part i am doing wrong?
i try to console.log with the actualData, it shows the whole part of the response including used with true/false, but when i console.log("actualData.used"), it shows undefined in the console.
actureData (from postman)
[
{
"used": true,
"create_date": "1644490502",
"update_date": "1666694655"
}
]
You should probably change approach and declare a used state to store the returned boolean value and use conditional rendering to adjust the class accordingly.
Also, as suggested by #KcH, if your response is an array, you should access the element with an index:
import { useState, useEffect } from 'react';
const MembershipLight = (code) => {
const [used, setUsed] = useState(false);
const lighting = () => {
fetch(`API`)
.then((response) => {
if (!response.ok) {
throw new Error(
`This is an HTTP error: The status is ${response.status}`
);
}
return response.json();
})
.then((actualData) => {
if (actualData.length > 0) {
setUsed(actualData[0].used)
}
})
.catch((err) => console.log(err));
}
useEffect(() => {
lighting();
}, []);
return <div className={`h-2.5 w-2.5 rounded-full mr-2 ${used ? 'bg-red-700' : 'bg-green-400'}`}></div>;
};
export default MembershipLight;
Furthermore, you're not returning anything from your lighting function. You should return the result of the fetch. Currently, your MembershipLight returns undefined due to that.
this is my first post. Thank you all for the years, of assistance btw, I hope to pour in, as much as I've gotten from you guys/gals. Let's get started.
I have a Next.js / React-Redux application and here is my problem:
I'm attempting to update the Redux store, with JSON, that is returned from `fs.readFile' (the 'fs/promises' module:
//Product.js
function Product() {
const suggested_products = useSelector((state) => state.user.suggested_products) // json read from /api/products
const updateProducts = () => {
(JSON.parse(JSON.stringify(suggested_products)))?.map((product) => { // Compliler does not like this line
<div>
<input type='checkbox'>
<p> {product.category}</p>
<p>{product.service_name}</p>
</input
</div>
})
}
return (
if (userSelectedProduct) ? updateProducts() : <p>No data found</p>
}
//Form.js
import { useSWR } from 'swr'
const fetcher = (...args) => fetch(...args).then((res) => res.json());
function Form() {
const [url, setURL] = useState('');
const { data, error } = useSWR(url, fetcher);
<input
value={product}
onChange={
dispatch(updateCustomerCSP((JSON.parse(JSON.stringify(e.target.value)))));
setURL(`api/${product}/`); //Attempt to dynamically fetch data, from client side
dispatch(updateSuggestedProducts(data)); //Update data in store returned from client side fetching
}}
</input>
}
// pages/api/products
import fs from 'fs/promises';
export default function handler(req, res) {
const filePath = path.join(process.cwd(),`/data.js'); // /data.js contains JSON array [{"product-1": "value"}, {"product-2": "value"}], which is why I need to use map function.
try {
const fileData = fs.readFile(filePath);
const data = JSON.parse(JSON.stringify(fileData));
res.status(200).json(data);
} catch (err)
{
res.status(500).json({ error: 'Failed to load data' })
}
}
// This error throws ref.map is not a function from the Products.js component.
Here is the only info that I could find in regards to ref's in React.
Also, the JSON.parse and JSON.stringify wrappers, are to keep Redux happy with object going into the store. Thanks for reading community, looking forward to your input. Should any oneed more info, please let me know. Also, here is info in regards to refs, that I could find:
https://reactjs.org/docs/refs-and-the-dom.html
I figured it out. I actually had the input wrapped in a HOC, and the event handler wasn't properly registered to the input element itself., therefore failing to load in the array object into the store to use.
So be careful of that, when building your own components.
For instance,
function Component() {
function loadArrayInStore() {
loadInStore()
}
const MyButton = (props) => {
<input onChange={props.handler} // I had an inline handler here such as onChange={ (e)= { doNotLoadArrayInStoreAndDontWork()}
}
return (
<MyButton handler={loadArrayInStore} />
)
}
So be watchful, when creating HOC's :)
I am new to unit testing and have a question about the mounted method inside the component.
I am testing if the button text is correctly displaying depends on one of the data values, and it passes. However, I have one method in mounted() inside the component and requests API call which is called from nuxt context.
The method is failing and consoling err message from try and catch because looks like it can not find nuxt context inside the test. This is not affecting my test but I wonder if it is fine, or do I need to fix something.
This is my component.
<template>
<button>
{{variations.length > 0 ? 'Select options' : 'add to cart'}}
</button>
</template>
<script>
data() {
return {
variations: [],
}
},
mounted() {
this.getVaridations()
},
methods: {
async getVaridations() {
try {
const variations = await this.$getVatiation.get() // this part is failing and consoling err message from catch
this.variations = variations
} catch (err) {
console.log(err) // consoling as TypeError: Cannot read properties of undefined (reading 'get')
}
},
},
</script>
This is testing
describe('Product.vue', () => {
it('btn display as "Select options" when there is validation', () => {
const wrapper = mount(Product, {})
expect(wrapper.find('.product-btn').text()).toBe('Select options') // This passes
})
})
You can mock any component methods like
import { shallowMount } from "#vue/test-utils";
describe('Product.vue', () => {
it('btn display as "Select options" when there is validation', () => {
const mocks = {
$getVatiation: {
get: () => [] // returns an empty array, change to what you want to return
}
}
const wrapper = shallowMount (Product, {mocks}) // send your mocks as an argument
expect(wrapper.find('.product-btn').text()).toBe('Select options')
})
})
I need to reload a remote JSON every 30 seconds. I currently do it this way in reactJS but since moving to NextJS it does not work
The issue is that the following work fine in my current ReactJS website but as soon as I Moved it to NextJS it printing our errors everywhere.
Mainly with the following
fetchTimeout
sessionStorage
export default function MediaControlCard(props) {
const fetchTimeout = (url, ms, { signal, ...options } = {}) => {
const controller = new AbortController();
const promise = fetch(url, { signal: controller.signal, ...options });
if (signal) signal.addEventListener("abort", () => controller.abort());
const timeout = setTimeout(() => controller.abort(), ms);
return promise.finally(() => clearTimeout(timeout));
};
const controller = new AbortController();
const podcast = props.podcast;
const classes = useStyles();
var token = uuidv4();
// alert(sessionStorage['uuid']);
if(!sessionStorage['uuid']){
sessionStorage.setItem("uuid",token);
}
if(!sessionStorage['station']){
sessionStorage.setItem("station","DRN1");
}
if(!sessionStorage['live']){
sessionStorage.setItem("live",true);
}
var icyStream = "https://api.drn1.com.au:9000/station/"+sessionStorage.station+"?uuid="+sessionStorage['uuid'];
var streamurl = icyStream;//window.com_adswizz_synchro_decorateUrl(icyStream);
React.useEffect(() => {
nowplaying();
document.getElementById("player").muted = false;
});
if(podcast){
alert('test');
}
/*if(!sessionStorage.getItem("station")){
sessionStorage.setItem("station","DRN1");
}*/
function nowplaying(){
// alert("hello");
if(sessionStorage.live === true){
document.getElementById("podcast-only").style.display='none';
}
fetchTimeout(`https://api.drn1.com.au:9000/nowplaying/`+sessionStorage.station+`?uuid=`+sessionStorage['uuid'], 3000, { signal: controller.signal })
.then(res => res.json())
.then(
(result) => {
//console.log("testing player"+result.data);
if(sessionStorage.getItem("live") === 'true'){
switch(result.data[0].track.songtype)
{
case "A":
AdSystem(result.data[0]);
break;
case "S":
Song(result.data[0]);
document.getElementById("Now_Playing_Artist").innerHTML = result.data[0].track.artist;
document.getElementById("Now_Playing_Title").innerHTML = result.data[0].track.title;
document.getElementById("Now_Playing_Cover").style.backgroundImage = "url('"+result.data[0].track.imageurl+"')";
break;
default:
Song(result.data[0]);
document.getElementById("Now_Playing_Artist").innerHTML = result.data[0].track.artist;
document.getElementById("Now_Playing_Title").innerHTML = result.data[0].track.title;
document.getElementById("Now_Playing_Cover").style.backgroundImage = "url('"+result.data[0].track.imageurl+"')";
break;
}
fetch(`https://itunes.apple.com/search?term=${result.data[0].track[0].artist}+${result.data[0].track[0].title}&limit=1`)
.then(res => res.json())
.then(
(result) => {
if(result.results[0]){
document.getElementById("buylink").href = result.results[0].collectionViewUrl;
document.getElementById("buynow").style.display = "block";
}
else
{
document.getElementById("buynow").style.display = "none";
}
})
}
})
.then(console.log)
.catch(error => {
console.error(error);
if (error.name === "AbortError") {
// fetch aborted either due to timeout or due to user clicking the cancel button
} else {
// network error or json parsing error
}
});
setTimeout(function(){nowplaying()}, 10000);
}
return (<>
<Card id="nowplayinginfo_card" className={classes.card}>
<CardMedia
id="Now_Playing_Cover"
className={classes.cover}
image="//tvos.adstichr.com/client/resources/images/stations/Indie/DRN1-Logo.png"
title="Live from space album cover"
/>
<div className={classes.details} id="adstichrNP">
<CardContent className={classes.content} id="song">
<Typography variant="subtitle1">
Now Playing
</Typography>
<Typography id="Now_Playing_Title" component="h6" variant="h6">
{props.artist}
</Typography>
<Typography id="Now_Playing_Artist" variant="subtitle1" color="textSecondary">
{props.song}
</Typography>
</CardContent>
<div id="buynow" className={classes.buynow}>
<a id="buylink" target="_blank" href="#Blank"><img alt="buynow" src="https://linkmaker.itunes.apple.com/assets/shared/badges/en-us/music-lrg-1c05919c6feae5d4731d4399cd656cd72e1fadc4b86d4bd7dc93cb8f3227cb40.svg"/></a>
</div>
<div id="podcast-only" className={classes.controls}>
<audio id="player" className={classes.player} controls controlsList="nodownload" autoPlay muted>
<source src={streamurl}
type="audio/mpeg"
/>
</audio>
</div>
</div>
</Card>
<Card className={classes.card} id="adbanner">
<CardContent className={classes.content} id="adstichr">
</CardContent>
</Card>
</>
)
}
How do I ac achieve this with NextJS. I thought anything I put into component with nextjs would just work the same as ReactJS - clearly not.
NextJS has server-side rendering features for your concern. I believe that you should use getStaticProps there is a special property in it called revalidate it will allow you to make requests on every timeout you wish to use. I took an example from official documentation of latest nextjs(version 11.0)
Docs: https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 10 seconds
revalidate: 10, // In seconds
}
}
export default Blog
You are not showing the errors but I suspect it is related to the server-side rendering feature of next.js.
document is defined only on the browser and since useEffect gets executed only on the browser you are calling nowPlaying inside the useEffect. That is the right thing. However sessionStorage (whatever is the package is) also has to be called on the browser.
You should be always retrieving the data from the storage inside useEffect, before component renders.
Yes, you can't achieve this with proper NextJS. I am using useSWR library, it has some "update" intervals as an option.
You can check it here. ("options" part)
you can use getServerSideProps. Make sure its a page component. getServerSideProps, getStaticProps only works in page component.
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default Page
I'm testing a functional component, that use React-Hooks and Redux-Saga. I can pass parameters in URL for the component, because they are a login page component.
My URL i pass is 'localhost/access/parameter', and when this parameter exists, i need to call a async redux saga, and if the fetch is OK, i put the result in redux-store. When the result is on redux-store, i have a useEffect that verify the result and if is OK, i put her in a input.
I can mock the result with axios, but i'm migrating to use only fetch. i mock the fetch, but when i use
mount(component), provided by enzyme, i do not how to await the redux-saga termine the request and useEffect do your job. I put a console log inside a effect, saga and log the input props to see your value prop, but the value is always empty . I tried to use setImmediate() and process.nextTick().
Links i use to write the code: 1,2, 3
I'm using formik, so they pass some props to me.
My component
const Login = ({
setFieldError, errors, response, fetchDomain, location, values, handleChange, handleBlur, setFieldValue, history,
}) => {
useEffect(() => {
async function fetchUrlDomain() {
const { pathname } = location;
const [, , domain] = pathname.split('/');
if (typeof domain !== 'undefined') {
await fetchDomain(domain);
}
}
fetchUrlDomain();
}, [fetchDomain, location]);
useEffect(() => {
if (typeof response === 'string') {
setFieldError('domain', 'Domain not found');
inputDomain.current.focus();
} else if (Object.keys(response).length > 0) {
setFieldValue('domain', response.Domain);
setFieldError('domain', '');
}
}, [response, setFieldValue, setFieldError]);
return (
<input name="domain" id="domain" value={values.domain} onChange={handleChange} onBlur={handleBlur} type="text" />
);
}
const LoginFormik = withFormik({
mapPropsToValues: () => ({ domain: '' }),
enableReinitialize: false,
validateOnBlur: false,
validateOnChange: false,
})(Login);
const mapStateToProps = () => ({ });
const mapDispatchToProps = dispatch => ({
fetchDomain: (value) => {
dispatch(action({}, constants.RESET_RESPONSE_DOMAIN));
dispatch(action(value, constants.FETCH_DOMAIN_REQUEST));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormik);
My Saga
export function* fetchDomain(action) {
const url = yield `${mainUrl}/${action.payload}`;
try {
const response = yield fetch(url).then(res => res.json());
yield put(reduxAction(response , constants.FETCH_DOMAIN_SUCCESS));
} catch (e) {
yield put(reduxAction(e, constants.FETCH_DOMAIN_FAILURE));
}
}
My Reducer
case constants.FETCH_DOMAIN_FAILURE:
return { ...initialState, response: 'Domain not found' };
case constants.FETCH_DOMAIN_SUCCESS: {
const { payload } = action;
return {
...initialState,
id: payload.Id,
apis: payload.Apis,
response: payload,
};
}
case constants.RESET_RESPONSE_DOMAIN:
return { ...initialState };
My Test
it('input with fetch only', (done) => {
const mockSuccessResponse = {
Id: 'fafafafa',
Apis: [],
Domain: 'NAME',
};
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);
const wrapper = mount(
<Provider store={store}>
<LoginForm
history={{ push: jest.fn() }}
location={{ pathname: 'localhost/login/Domain' }}
/>
</Provider>,
);
process.nextTick(() => {
const input = wrapper.find('#domain');
console.log(input.props());
expect(input.props().value.toLowerCase()).toBe('name');
global.fetch.mockClear();
done();
});
});
I expect my input have value, but he don't. I tried to use jest-fetch-mock but just don't work, and i want to use native jest methods, no thirty party libraries.
I cannot say what's wrong with your current code. But want to propose different approach instead.
Currently you are testing both redux part and component's one. It contradicts with unit testing strategy when ideally you should mock everything except module under the test.
So I mean if you focus on testing component itself it'd be way easier(less mocks to create) and more readable. For that you need additionally export unwrapped component(Login in your case). Then you can test only its props-vs-render result:
it('calls fetchDomain() with domain part of location', () => {
const fetchDomain = jest.fn();
const location = { pathName: 'example.com/path/sub' }
shallow(<Login fetchDomain={fetchDomain} location={location} />);
expect(fetchDomain).toHaveBeenCalledTimes(1);
expect(fetchDomain).toHaveBeenCalledWith('example.com');
});
it('re-calls fetchDomain() on each change of location prop', () => {
const fetchDomain = jest.fn();
const location = { pathName: 'example.com/path/sub' }
const wrapper = shallow(<Login fetchDomain={fetchDomain} location={location} />);
fetchDomain.mockClear();
wrapper.setProps({ location: { pathName: 'another.org/path' } });
expect(fetchDomain).toHaveBeenCalledTimes(1);
expect(fetchDomain).toHaveBeenCalledWith('another.org');
});
And the same for other cases. See with this approach if you replace redux with direct call to fetch() or whatever, or if you refactor that data to come from parent instead of reading from redux store you will not need to rewrite tests removing mocks to redux. Sure, you will still need to test redux part but it also can be done in isolation.
PS and there is no profit to await fetchDomain(...) in useEffect since you don't use what it returns. await does not work like a pause and that code may rather confuse reader.