Broad overview of my app: Users can submit posts with images and text.
I am using MERN stack.
Currently, images are being saved into my localhost:3001 server at /public/uploads/<images> and the route is then saved into my database.
I am now trying to render the posts of screen.
// example value of what gets stored into my db
// post.img.url = "public\uploads\user-5cc37dda4142ff49a8c903d2-1557200552890.png"
posts.map( ( post, i ) => {
return <img src={ post.img.url } alt='post image'} />
} )
When I hover over the chrome console to view the route that it returns, this is the route that shows up:
http://localhost:3000/profile/admin/posts/public/uploads/user-5cc37dda4142ff49a8c903d2-1557174873119.png
To actually view the image, you would have to go to this route: http://localhost:3001/uploads/user-5cc37dda4142ff49a8c903d2-1557200552890.png
How can I get the correct route for my images?
Some addition details that might matter
// this line is in my server.js
app.use( express.static( path.join( __dirname, 'public' ) ) )
Just set some global or .env variable in your React app which contain a http route to your server like this: SERVER_HOST = 'http://localhost:3001'. Then you need to store the relative path to your images in database like /uploads/user-*.png. Now you can get access to images like this:
posts.map( ( post, i ) => {
return <img src={ SERVER_HOST + post.img.path } alt='post image'} />
})
Related
I had some alert components when each clicked; it will get redirected to a page
<div className="question11">
{data.map((itm) => (
<Link
key={itm._id}
href={{
pathname: "/[itm]",
query: { id: itm._id },
}}
as={`/${encodeURIComponent(
itm.Name.replace(/[^a-zA-Z0-9 - _ . ~]/g, "").replace(
/ /g,
"-"
)
)}`}
>
<Alert className="question13">{itm.Name}</Alert>
</Link>
))}
</div>
The redirected page has a URL in the following pattern
http://localhost:3000/itm.Name. Example: http://localhost:3000/spiderman-no-way-home-release-date-in-india. I am passing itm._id for accessing the corresponding data on the redirected page
export async function getServerSideProps(context) {
var id1 = context.query.id;
// console.log(context.query.id);
const queryRequest = fetch("https://askover.wixten.com/questone/" + id1).then(
async (res) => await res.json()
);
When I click on alert components, I can pass the itm._id, and the page is redirected properly. The issue occurs when I manually enter the URL in the browser.The issue here is not getting the itm._id from the alert component. The answer that I came up with here is to create an API to access the API by passing the itm.Name, but that will require deconstructing the itm.Name to its original form, and itm.Name might not be unique every time is there another method by which I can access itm._id itself also, if I can use the URL in http://localhost:3000/itm._id/itm.Name
this format also, I think it will be okay just as StackOverflow does it.
When you refresh the page you will lose the context, even if you use some store(local, session, etc) that will not work for the user visiting your app for the first time.
One thing always remains is URL, neither storage nor context.
To solve this kind of issue, what you can do is pass the id and slug parameters to the URL and read whenever requires.
Check more details here
Next.js Directory
pages
index.js
[id]
[slug].js
The URL will look something like this: https://localhost:3000/123/my-post-slug
, Slug is optional, It'll help for SEO purposes.
[slug].js
const Component = (props) => (
<div>
<h1>{props.title}</h1>
<p>{props.content}</p>
</div>
);
export async function getServerSideProps(context) {
const id = context.params.id;
const data = fetch(`https://askover.wixten.com/questone/${id}`).then((res) => await res.json());
return {
props: data,
}
}
I read the docs of dynamic routes but they didn't explain much about how dynamic routes will work with "catching all routes".
My folder structure for this route is:
└──pages
└──catalog
└──[[...slug]].js
Here's my code:
export default function Catalog(props) {
return (
<Product product={props.product} />
)
}
export async function getStaticProps({ params }) {
const productSlug = params.slug[params.slug.length-1];
const data = await getSingleProduct(productSlug)
return {
props: {
product: data.product,
},
revalidate: 30
}
}
My API is WP and I have product pages URI like this /catalog/category/sub-category/product/
So if I go to the URL /catalog/category/sub-category/product/ it works fine with the code I shared below because I have const productSlug = params.slug[params.slug.length-1]; which will get my slug which I can pass to the API and use the product data just fine.
But I want to work with categories too, so if I go to /catalog/category/sub-category/ it should load the category page, and if I go to /catalog/category/ it should load up that category page.
Even this will work with the code I have because I'm getting the last element of params array which is the product slug, but that's NOT always the case. Sometimes the product is without any sub-category so the URI would be /catalog/category/product which means I can't fix it to the third element of the array and use the other two as category slugs.
The params gives me an array without any key or anything and I can't seem to figure out how to achieve this in next.js
Any help is appreciated!
I current have a folder in my firebase storage that looks like this:
enter image description here
I am trying to map through a cloud firestore collection and rendering a screen with list items that have the corresponding info and the image. My document field include IDs that have 0 and 1 for example so I have an easier method of finding the images that I have on firebase storage folder. This is the code I'm using to render:
renderAccordion() {
return this.state.accordionarr.map((item,index) => {
const url = this.returnurl(this.state.displaydatestringversion, item.id);
return(
<View key={index}>
<Accordion
onChange={this.onChange}
activeSections={this.state.activeSections}
>
<Accordion.Panel header= {item.name}>
<List>
<List.Item>
<Image
source = {{ uri: url }}
style = {styles.sizer}
></Image>
</List.Item>
<List.Item>{item.protein}</List.Item>
<List.Item>{item.carbohydrate}</List.Item>
</List>
</Accordion.Panel>
</Accordion>
</View>
);
});
}
In the (item,index) you can assume the fields of item look like this:
enter image description here
Also, if you check the second line there is a method call returnurl. That looks like this:
returnurl = async(date, id) => {
var ref = firebase.storage().ref(date+'/'+id+".jpg");
const url = await ref.getDownloadURL();
return url;
}
I am currently getting an error message of "of type NSMutableDictionary cannot be converted to a valid URL.
What is wrong with my code? Or is there a more efficient way of mapping through a field and finding/downloading the image url to render the screen?
You can't call an async function in your render methods. If you need async data in rendering, you should start loading that data when the component mounts (or when any input for loading the data is available), and then put the data into the component's state once it's loaded.
See for example:
Firebase storage: download images to and put to img src in React map
Image from Cloud Storage is not redendring in .map list
How to update Firebase data to the React application in realtime
React Native how to read Real Time Database Firebase
Firebase storage: download images to and put to img src in React map
I'm am learning node.js and therefore try to build a simple web app that shows the current news. The API that I am using offers several categories for the news.
So I create a route that takes the category as a param. My routes/index.js:
const router = require('express').Router();
const renderHome = require('../controllers/newsController');
const quotesCookie = require('./../middleware/quotesCookie');
router.get('/', quotesCookie, renderHome);
router.get('/:category', quotesCookie, renderHome);
module.exports = router;
My controllers/newsController.js looks like this:
const newsService = require('./../services/newsService');
const renderHome = async ( req, res ) => {
const category = req.params.category;
console.log(req.params);
const quote = res.quoteOfTheDay;
const { status, msg } = await newsService.topHeadlines(category);
res.render('home', {
title: 'News2Go',
author: quote.author,
quote: quote.quote,
articles: msg.articles
});
};
module.exports = renderHome;
When I for instance call http://localhost:3000/entertainment the console.log in the controller prints this to the console:
{ category: 'entertainment' }
{ category: 'sw.js' }
I have absolute no clue where the sw.js comes from... It appears a few milliseconds after the real category and ensures that topHeadlines is called twice.
Did someone know what this is? Did I miss something?
Apparently your web page has a script in it named sw.js. Because of that, the browser will request that with the URL http://localhost:3000/sw.js and your :category route will handle that request and log a category of sw.js.
Remember, ALL resources used on your site will be requested by the browser and will be seen by your Express server as incoming requests. Not just the top level page, but all scripts, images, fonts, CSS files, etc... used by your pages.
It's generally not a good idea to define a wide-open top level route handler like this:
router.get('/:category', ...)
Because that will grab ALL top level URLs and leave none for the rest of your site to use. It would probably make more sense to use a structure like this:
router.get('/category/:category', ...)
With a URL of http://localhost:3000/category/entertainment. Then, you can more clearly separate out the actual category requests from all the other requests in your site. Either that or you will have to move ALL other URLs used on your site to routes that come before this and/or use sub-directories in their page such as:
http://localhost:3000/scripts/sw.js
http://localhost:3000/styles/main.css
I have this scenario.
A user types in /company/<company-id> in the address bar.
Since the app is totally separate from the backend, it needs to prefetch the companies.
Normal user flow is /login -> /company/. I handle this case pretty well and just navigate to /company/<whatever id is first in the prefetch> with no problems.
But what if you load WITH the id? I have solution but I think I have a feeling that I'm misunderstanding something in routing.
You may assume that my prefetching works and the code snippet below will only trigger if companyState.success is true. Like i said, it is working.
I handled this manually, by
// pretty sure i can handle this with regex better to capture other cases
// but that's beside the point for the scope of this question
const urlId = +location.pathname.replace("/company/", "")
const checkCompany = !!companyState.data.find(d => d.id === urlId)
if(checkCompany){
company.set(urlId)
}
else{
navigate("/404")
}
I have hooks in place where in if company.set(<company:id>) does update, it will pre-fetch everything else needed for the view. And, company is a custom context hook so that it's present everywhere in my application.
Is there a better way in handling this? It seems hack-y to manually check the path name.
You can assume that my gatsby_node.js has the right definitions to allow the client side routing.
Here's my routing definitions: (this is what i put in the pages folder)
const DashboardPage = () => (
<ProtectedRoute>
<Router>
<Company path="/company/*" />
</Router>
</ProtectedRoute>
)
Finally in the components folder,
const Company = ({location}) => (
<Router>
<Main path="/:companyId">
<Summary path="/" />
.... other dashboard routes
</Main>
</Router>
)
You have to assume that client-side code can always be changed by a malicious actor. Ultimately, you have to make sure on the backend that a user can only request ressources he is supposed to see.
Your solution for the client-side seems fine to me. I don't see another way than checking the URL path manually and then redirecting.
By logging in, your user needs to be assigned to a cryptographically safe cookie or token (such as JSON web tokens) so you can always be sure of their identity. Everytime a company id is routed to, your frontend needs to send the user identity to your backend. Only there you can be safe from code manipulations. Your backend needs to check if the user can look at this page.
If the user can: your backend sends the page data
If the user can't: your backend sends "not authorized" message and your frontend redirects
This way even if someone manipulates your client-side code and cancels the redirect, the hacker will stare at a useless blank page.
In summary:
Your approach on the client-side is fine. Make sure your backend checks the identity of your user before sending the company data.
So instead of me manually handling the routing, I solely used ReachRouter's navigate to simulate history.push() to avoid re-rendering of the whole page. (similarly just a state change with the benefit of keeping track of history ). Backend is fully protected with auth tokens so no need to worry about that.
My strategy below will handle these cases:
User types in or app navigates to /company ( app will pre fetch > get first company by default > navigate to /company/{id}
User types in or app navigates to /company/{id} ( app will pre-fetch > navigate to /company/{id} > component triggered by that route will check validity of id else navigate to 404 )
The strategy I created was,
Create a component that will load up a loading screen by default and prefetch the said companies.
Call the said component in the pages folder as the default, in my case, /company should be pages > company.js or pages > company > index.js
If the prefetch is successful, navigate to /company/{id} which is the child of the said component, which is a purely client route.
make sure gatsby-node.js have the necessary createPages definition to allow client routing for everything /company/*
manually check if the current location is /company to deny redirection to capture the second case when user types in /company/{id}
Better if I show the code, ignore my custom built in hooks.
useRequest just gives an axios class, with the true parameter telling it's an authenticated required request.
useApi just gives a handy (async-await) function that includes dispatch for the redux states and calling the api itself. I'm using thunk, so success and error are standard.
const CompanyDefault = ({location}) => {
const [loading, toggle] = useState(true)
const request = useRequest(true)
const companyApi = useApi(request, getCompanies)
const companyState = useSelector(({company}) => company)
const redirectToCompany = async () => await navigate(clientRoutes.COMPANY(companyState.data[0].id))
const fetchCompanies = async () => {
await companyApi()
toggle(false)
}
useEffect(() => {
if(companyState.data.length === 0){
fetchCompanies()
}
}, [])
if(loading){
return <Loading />
}
else if(companyState.error || companyState.data.length === 0){
return <Error callApi={companyApi} />
}
else if(companyState.success && (location.pathname === "/company" || location.pathname === "/company/")){
redirectToCompany()
}
return (
<Router>
<Company path="company/:companyId/*" />
</Router>
)
}
export default CompanyDefault
Thus, the company module will be just
const Company = ({companyId}) => (
// you now have companyId!
// do magic here, and check if that company id is valid.
<Router>
<Main path="/">
<Summary path="/" />
.... other dashboard routes
</Main>
</Router>
)
Hopefully this is cleaner. If there's a better way do let me know! :D