Loader not running while making request to db - javascript

I'm sorry because this is going to be a long post, but I would really appreciate help. I have been trying to make it work, but with no luck.
I am making a call to the database to grab images, during this call I want the loader to be active. The loader is not showing up during the call (when I hard code it it works). An other problem is the <NoResults /> component. It is supposed to render when a query to the database comes back empty. However, this component is rendering while the api call is running.
TLDR I want a loader during the api call, but instead the <NoResults /> component is rendering and then the data that returned from the db is rendering.
Using mongodb, express, mobx, and react.
Media Store Mobx:
export class MediaStore {
#observable loading = true
#observable trending = []
#action setLoading = (bool) => { this.loading = bool }
#action getTrending = async (category, pageNum, input) => {
this.setLoading(true)
this.error = false
let cancel
axios({
method: 'GET',
url: `http://localhost:3001/media/trending?category=${category}&page=${pageNum}&input=${input}`,
cancelToken: new axios.CancelToken(c => cancel = c)
}).then(res => {
this.trending =
[...new Set([...this.trending, ...res.data.creators]
.map(JSON.stringify))].map(JSON.parse)
this.setHasMore(res.data.creators.length > 0)
this.setLoading(false)
}).catch(e => {
if (axios.isCancel(e)) return
this.error = true
})
return () => cancel
}
}
MediaCards Component:
const MediaCards = inject('userStore', 'mediaStore')(observer((props) => {
const ref = useCreators(props.mediaStore);
const location = useLocation()
const classes = useStyles()
const { isLoggedIn, favorites } = props.userStore;
const { trending, loading } = props.mediaStore;
const { media, header, mediaCard } =
location.pathname === '/dashboard' && (!isLoggedIn || !favorites.length)
? { media: [], header: 'basic', mediaCard: false }
: location.pathname === '/dashboard'
? { media: favorites, header: 'basic', mediaCard: true }
: { media: trending, header: 'explore', mediaCard: true }
const renderMediaCards = (media) => {
return media.map((data, i) => {
let isFavorite = favorites.some(f => data._id === f._id)
if (header === 'explore' && media.length === i + 1) {
return <MediaCard lastRef={ref} id={data._id} img={data.img} isFavorite={isFavorite} twitchName={data.twitch} key={data._id} />
}
return <MediaCard id={data._id} img={data.img} isFavorite={isFavorite} twitchName={data.twitch} key={Math.random()} />
})
}
return (
<>
<Header page={header} />
{header === 'explore' ? <CategoryBar /> : <Paper className={classes.paperTopMedia}></Paper>}
{mediaCard
? <Paper className={classes.paperMedia}>
<Grid container>
<GridList cellHeight={180} className={classes.rootMedia}>
{!trending.length && !loading && <NoResults />}
{!loading && renderMediaCards(media)}
{loading && <Loading />}
</GridList>
</Grid>
</Paper>
: <EmptyCard />
}
</>
)
}))

Related

How to update state in onSubmit form & send data through components?

I have a state which I need to update with the ID returned from an endpoint call so I can call another another endpoint using that ID, I've made a state in the parent component and I use it in my first form to set the ID. I pass that id as a prop to the component that needs it but when I console.log the state, it doesn't change.
How can I pass the ID through the components?
I've added comments on the main places to look at
Here is my first form where I need the ID from -
const AddWebAppTypeForm = (props: any, ref: any) => {
const { setWebAppTypes, setNewAppValues}: AddWebAppTypeFormProps =
props;
const {
handleSubmit,
control,
reset,
formState: { isDirty },
} = useForm();
const onSubmit = (data: any) => {
let formData = new FormData();
formData.append("name", data.Title);
formData.append("description", data.Description);
formData.append("uploaded_file", data.Image[0]);
if (isDirty) {
createWebAppType(formData);
}
};
const createWebAppType = async (body: any) => {
await fetch(`${process.env.REACT_APP_API_URL}/webapptype`, {
method: "POST",
body: body,
})
.then((response) => response.json())
.then((data: IWebAppType) => {
const model: IWebAppType = {
id: data.id,
name: data.name,
image: data.image,
description: data.description,
image_url: data.image_url,
};
setNewAppValues(model.id); // <--- Set the state here
setWebAppTypes((prev) =>
prev.map((item) => (item.id === 0 ? model : item))
);
enqueueSnackbar(`Created App Succesfully`, {
variant: "success",
});
});
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<button hidden={true} ref={ref} type="submit" />
</form>
);
};
export default forwardRef(AddWebAppTypeForm);
My parent component with the states -
function WebAppTypeAccordion({ a, setWebAppTypes }: WebAppTypeAccordionProps) {
const [formEl, setFormEl] = useState(null);
const [addFormEl, setAddFormEl] = useState(null);
const [newAppValues, setNewAppValues] = useState<number>(0); // <--- state with 0 as initialised value
const handleRef = (el: any) => {
if (el !== null) {
setFormEl(el);
}
};
const handleAddRef = (el: any) => {
if (el !== null) {
setAddFormEl(el);
}
};
return (
<Accordion defaultExpanded={a.id === 0}>
<AccordionSummary
// onClick={(e) => handleOnClick(a, e)}
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography>{a.name}</Typography>
</AccordionSummary>
<AccordionDetails>
{a.id === 0 ? (
<AddWebAppTypeForm
setWebAppTypes={setWebAppTypes}
ref={handleAddRef}
setNewAppValues={setNewAppValues} // <--- Passing setState to set id
/>
) : (
null
)}
<MappedAccordion
waobj={a}
key={a.id}
setWebAppTypes={setWebAppTypes}
editRef={formEl}
addRef={addFormEl}
newAppValues={newAppValues} // <--- Passing ID
/>
</AccordionDetails>
</Accordion>
);
}
export default WebAppTypeAccordion;
Here is where I am trying to use the ID to call another endpoint
function MappedAccordion({
waobj,
setWebAppTypes,
editRef,
addRef,
newAppValues,
}: MappedAccordionProps) {
const handleCreate = (data: FieldT) => {
let wtype: string = String(waobj.id);
if (addRef !== null) {
if (newAppValues !== 0) {
wtype = String(newAppValues); // <--- Try to use the ID but get default value
createFetch(data, wtype); // <--- Try to use the ID but get default value
}
}
createFetch(data, wtype);
};
const createFetch = (data: FieldT, wtype: string) => {
let formData = new FormData();
formData.append("name", data.name);
formData.append("link", data.link);
formData.append("wtype", wtype);
fetch(`${process.env.REACT_APP_API_URL}/webapp/`, {
method: "POST",
body: formData,
})
.then((response) => {
if (!response.ok) {
let err = new Error("HTTP status code: " + response.status);
enqueueSnackbar(`Environment already exists`, {
variant: "error",
});
throw err;
}
return response.json();
})
.then((data: IWebApp) => {
const model: FieldT = {
wid: data.id,
name: data.name,
link: data.link,
};
enqueueSnackbar(`Created Environment ${model.wid}`, {
variant: "success",
});
});
};
const onSubmit = (data: FormFields) => {
if (addRef !== null) addRef?.click(); // <--- Submit AddWebAppTypeForm form, set the ID
else editRef?.click();
let onSubmitData: FieldT[] = data.myFieldValues;
onSubmitData.map((a: FieldT, index) => {
let originalField: FieldT = initialFields[index];
if (a.wid === undefined) {
handleCreate(a);
} else {
if (JSON.stringify(a) !== JSON.stringify(originalField)) {
handleEdit(a);
}
}
});
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)} id="environment-form">
<div style={{ paddingTop: 10 }}>
<Button
type="submit" // <--- Submit form
variant="outlined"
size="small"
sx={{ marginRight: 1 }}
>
Save
</Button>
<Button
variant="outlined"
size="small"
onClick={handleAppDelete}
disabled={waobj.id === 0 ? true : false}
>
Delete
</Button>
</div>
</form>
</div>
);
}
export default MappedAccordion;
Thanks for taking a look, I appreciate any help!

Is there a way in Next.js to modularize a Page Element receiving staticProps via a parsedUrlQuery?

Use case is to to open
http://localhost:3000/users/101
This is how I would like my page layout to be and Element pull in the second code snippet in [id].tsx
import CTASection from '../../components/samples/CTASection';
import { User } from "../../interfaces";
import { GetStaticProps, GetStaticPaths } from "next";
import Element from "pages/users/element";
const Element = ({ item, errors }: Props) => {
return (
<Box>
<Element />
<CTASection />
</Box>
)
}
export default Id;
export const getStaticPaths: GetStaticPaths = async () => {
// Get the paths we want to pre-render based on users
const paths = sampleUserData.map((user) => ({
params: { id: user.id.toString() },
}));
return { paths, fallback: false };
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
try {
const id = params?.id;
const item = sampleUserData.find((data) => data.id === Number(id));
return { props: { item } };
} catch (error) {
return { props: { errors: 'ERROR Loading Data' } };
}
};
However it only renders the query parameters if I insert my element.tsx page in a non-modular fashion like this:
...
return (
<Box>
<Grid gap={2}>
<Heading as="h2" fontSize={{ base: "lg", sm: "3xl" }}>
Verifikation
</Heading>
<Box
backgroundColor={colorMode === "light" ? "gray.200" : "gray.500"}
padding={4}
borderRadius={4}
>
<Box fontSize={textSize} title={`${
item ? item.name : "User Detail"
}`}>
{item && <ListDetail item={item} />}
</Box>
</Box>
</Grid>
<CTASection />
</Box>
)
...
This is the ListDetail.tsx:
import * as React from "react";
import { User } from "../../interfaces";
type ListDetailProps = {
item: User;
};
const ListDetail = ({ item: user }: ListDetailProps) => (
<div>
<h1>User: {user.name}</h1>
<p>ID: {user.id}</p>
</div>
);
export default ListDetail;
you can use gerServerSideProps
export const getServerSideProps = async ({ req, res, query, params }) => {
// your code...
// you have access to req, res, query, params, headers, cookies, and many more.
return {
props: {}
}
}

Flatlist item duplication issue

When I render my flatlist, it seems to duplicate items inside it (feedCache has one index and it will render the data for this index twice). Here's the code for the flatlist:
const FeedBody = React.memo(() => {
return (
<FlatList
data={feedCache}
renderItem={({ item }) => {
return (
<FeedPost
item={item}
/>
)
}}
keyExtractor={item => item.id}
/>
)
})
return (
<>
{feedLoaded && feedCache && feedReturnCache
? <FeedBody />
: <AppLoading />
}
</>
)
At the top of the document, in the useEffect, I fetch data from an API and write parts to feedCache and feedReturnCache. Once this completes feedLoaded is set to true and the feed body is supposedly rendered. Despite this, I'm convinced the FeedBody component is still re-rendering due to state updates. This causes duplication of items inside the flatlist. Is there a way to fix/prevent this?
Any help would be appreciated, thanks.
(useEffect code)
const [feedLoaded, setFeedLoaded] = useState(false)
const [feedCache, setFeedCache] = useState(null)
const [feedReturnCache, setFeedReturnCache] = useState(null)
useEffect(() => {
feedMounted = true
server("POST", "/content/feed", {
token: user.token,
cachePresent: false,
metric: "new"
}, (data, http, error) => {
if (!error) {
const toBeStatefulFeedReturnCache = new Array()
for (let i = 0; i < data.length; i++) {
toBeStatefulFeedReturnCache.push({
id: data[i].id,
read: false
})
if (i + 1 === data.length) {
if (feedMounted) {
setFeedCache(data)
setFeedReturnCache(toBeStatefulFeedReturnCache)
setFeedLoaded(true)
}
}
}
} else {
throw error
}
})
return () => {
feedMounted = false
}
}, [])
Try to fix duplication in array with set

Looking for a more elegant way to write multiple components using similar functions

I have this component that displays data but also needs to save and manipulate state. I have 5 other components that are exactly the same, except for little nuances inside of their functions. I need help finding a more elegant way to write this code, rather than having 6 files that are more or less the same.
I have tried to understand and use a HOC for this instance, but each component has its own submit call that requires data specific to that component. So, I can't seem to find a way to make a HOC work.
These are my functions:
componentDidMount () {
setTimeout(() => {
this.setState({ baseForm: this.props.baseData })
this.getDisable()
this.setState({ loading: false })
}, 2000)
}
handleSwitch = item => event => {
this.setState({ [item]: event.target.checked })
}
handlePRESubmit () {
const array = this.state.baseForm
array.forEach(item => {
item.isTemplate = false
item.mark = this.state.mark
item.isVisible = this.state.visible
item.genesisId = item._id
})
}
handleSubmit = () => {
this.handlePRESubmit()
let formData = this.state.baseForm[0]
fetch(APIURL, {
method: 'POST',
body: JSON.stringify(formData),
}).then(response => {
response.json().then(data => {
let orgId = localStorage.getItem('orgId')
let sku = { skuId: data.data._id, type: 'verification' }
fetch(APIURL, {})
.then(response => response.json())
.then(data => {})
})
})
}
toggleDisabled () {
if (this.state.assigned !== undefined) {
this.setState({ disabled: !this.state.disabled })
}
}
getDisable () {
setTimeout(() => {
const result = this.props.assignedSku.find(e => e.name === 'Base')
this.setState({ assigned: result })
if (this.state.assigned !== undefined) {
this.setState({ mark: true })
this.setState({ visible: true })
}
}, 1000)
}
handleMenu = event => {
this.setState({ anchorEl: event.currentTarget })
}
handleClose = () => {
this.setState({ anchorEl: null })
}
And this is my Card
<Card id='partner-sku-card'>
<CardHeader
title={base.name}
subheader={'$' + ' ' + base.price}
action={
<div>
<IconButton onClick={this.handleMenu}/>
</div>
}
/>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={this.handleClose}
>
<MenuItem disabled={this.state.disabled ? 'disabled' : ''}>
Edit
</MenuItem>
</Menu>
<CardActions>
<FormControlLabel
control={
<Switch
checked={this.state.mark}
onChange={this.handleSwitch('mark')}
value='mark'
/>
}
/>
<FormControlLabel
control={
<Switch
checked={this.state.visible}
onChange={this.handleSwitch('visible')}
value='visible'
/>
}
/>
<Button onClick={this.handleSubmit}>
Submit
</Button>
</CardActions>
</Card>
Again, all of this code is being written again in 5 other files. I need an elegant way to replace the word "Base" / "base" in every aspect of this. Lets say I have Base, Mid, Top. I would need all of these functions to work for all 3, and still produce the same card at the end.
Thanks for the help!
Create a component called CardWrapper or something. Then in each other file call CardWrapper like this:
class First extends Component {
handleSwitch = () => {
//its own implementation
}
handlePreSubmit = () => {
//its own implementation
}
handleSubmit = () => {
//its own implementation
}
//other methods that you need for each component to be different
render(){
return (
<CardWrapper handlePreSubmit={this.handlePreSubmit} handleSubmit={this.handleSubmit} handleSwitch={this.handleSwitch} />
)
}
}
Remember that you should add all of this to all the files that share the CardWrapper component.
And then in CardWrapper you can access it by this.props. Ex. in the end when you have Submit Button that would change like:
<Button onClick={this.props.handleSubmit}>
Submit
</Button>

Passing handler results in undefined

I can't seem to pass this handler correctly. TabItem ends up with undefined for onClick.
SearchTabs
export default class SearchTabs extends Component {
constructor(props) {
super(props)
const breakpoints = {
[SITE_PLATFORM_WEB]: {
displayGrid: true,
autoFocus: true,
},
[SITE_PLATFORM_MOBILE]: {
displayGrid: false,
autoFocus: false,
},
};
this.state = {
breakpoints,
filters: null,
filter: null,
isDropdownOpen: false,
selectedFilter: null,
tabs: null,
};
this.tabChanged = this.tabChanged.bind(this);
this.closeDropdown = this.closeDropdown.bind(this);
}
... more code
createTabs(panels) {
if(!panels) return;
const tabs = panels.member.map((panel, idx) => {
const { selectedTab } = this.props;
const { id: panelId, headline } = panel;
const url = getHeaderLogo(panel, 50);
const item = url ? <img src={url} alt={headline} /> : headline;
const classname = classNames([
searchResultsTheme.tabItem,
(idx === selectedTab) ? searchResultsTheme.active : null,
]);
this.renderFilters(panel, idx, selectedTab);
return (
<TabItem
key={panelId}
classname={classname}
idx={idx}
content={item}
onClick={this.tabChanged(idx, headline)}
/>
);
});
return tabs;
}
tabChanged(idx, headline) {
const { selectedTab } = this.props;
const { selectedFilter } = this.state;
const selectedFilterIdx = _.get(selectedFilter, 'idx', null);
if (selectedTab !== idx) {
this.props.resetNextPage();
this.props.setTab(idx, selectedFilterIdx, headline);
this.closeDropdown();
}
}
render() {
// const { panels, selectedTab } = this.props;
// if (!panels || panels.length === 0) return null;
//
//
// const { tabs, selectedTab } = this.props;
return (
<div>
<ul>{this.state.tabs}</ul>
</div>
);
}
}
export const TabItem = ({ classname, content, onClick, key }) => (
<li key={key} className={`${classname} tab-item`} onClick={onClick} >{content}</li>
);
so in TabItem onClick={onClick} ends up with undefined for onClick.
More info
here's how this used to work, when this was a function in the parent Container:
// renderDefaultTabs() {
// const { panels, selectedTab } = this.props;
//
// if (!panels || panels.length === 0) return;
//
// let filter = null;
//
// const tabs = panels.member.map((panel, idx) => {
// const { id: panelId, headline } = panel;
// const url = getHeaderLogo(panel, 50);
// const item = url ?
// <img src={url} alt={headline} /> : headline;
// const classname = classNames([
// searchResultsTheme.tabItem,
// (idx === selectedTab) ? searchResultsTheme.active : null,
// ]);
//
// filter = (idx === selectedTab) ? this.renderFilters(panel) : filter;
//
// return (
// <li
// key={panelId}
// className={classname}
// onClick={() => {
// this.tabChanged(idx, headline);
// }}
// >
// {item}
// </li>
// );
// });
So I extracted that out to that SearchTabs including moving the tabChange d method to my new SearchTabs component. And now in the container the above now does this:
renderDefaultTabs() {
const {
onFilterClick,
panels,
resetNextPage,
selectedTab,
selectedFilter,
isDropdownOpen,
} = this.props;
return (<SearchTabs
panels={panels}
...
/>);
}
Note: renderDefaultTabs() is sent as a prop to in the render() of the container and the Search calls it back thus rendering it in the Search's render():
Container
render() {
return (
<Search
request={{
headers: searchHeaders,
route: searchRoute,
}}
renderTabs={this.renderDefaultTabs}
renderSearchResults={this.renderSearchResults}
handleInputChange={({ input }) => {
this.setState({ searchInput: input });
}}
renderAltResults={true}
/>
);
}
Search is a shared component our apps use.
Update
So I mentioned that the Container's render() passes the renderDefaultTabs function as a prop to <Search />. Inside <Search /> it ultimately does this: render() { <div>{renderTabs({searchResults})}</div>} which calls the container's renderDefaultTabs function which as you can see above, ultimately renders
So it is passing it as a function. It's just strange when I click a TabItem, it doesn't hit my tabChanged function whatsoever
Update
Christ, it's hitting my tabChanged. Errr..I think I'm good. Thanks all!
onClick={this.tabChanged(idx, headline)}
This is not a proper way to pass a function to child component's props. Do it like (though it is not recommended)
onClick={() => this.tabChanged(idx, headline)}
UPDATE
I want to add more explanation. By onClick={this.tabChanged(idx, headline)}, you are executing tabChanged and pass its returned value to onClick.
With your previous implementation: onClick={() => { this.tabChanged(idx, headline); }}, now onClick will be a function similar to:
onClick = {(function() {
this.tabChanged(idx, headline);
})}
So it works with your previous implementation.
With your new implementation, onClick={() => this.tabChanged(idx, headline)} should work

Categories