Dynamically add all components in a folder - javascript

So I'm currently building a tutorial, where the number of pages will continuously expand as more features are added, currently I am manually adding each file to the displaying file, i.e.
const Page0 = () => import("../../components/tutorial/Page0/index.vue");
const Page1 = () => import("../../components/tutorial/Page1/index.vue");
but obviously if this isn't very well handled once it gets really big i.e.
const Page0 = () => import("../../components/tutorial/Page0/index.vue");
...
const Page100 = () => import("../../components/tutorial/Page100/index.vue");
So I was wondering if there was a way to know let vue.js know that it should be fetching all files/folders in a certain folder and render each of them as a component with 'Page' + number name.
Ordering matters.
full code sandbox here https://codesandbox.io/s/serene-curie-it7xo?file=/pages/tutorial/_page.vue:102-247

use dynamic loading then.
in your _page.vue
function mapComponents() {
let components = {};
for (let i = 0; i < 2; i++) { // 2 should be your pages amount
components["Page" + i] = () =>
import(`../../components/tutorial/Page${i}/index.vue`);
}
return components;
}
export default {
components: mapComponents(),
computed: {
current() {
//.... other code
in your tutorial.vue
data() {
return {
pages: [...Array(2).keys()] // same here, 2 should be your pages amount
};
},
maybe just use another function to get the amount of the page, but you got the idea :)
working sample : https://codesandbox.io/s/clever-feynman-vgm93?file=/pages/tutorial.vue:280-347

Related

NextJS Router doesn't reload data until page refocus

Currently working with NextJS, but struggling to make an indexing page of sorts. With the router, I'm trying to get the page number by doing this:
let router = useRouter()
let page = isNaN(router.query.page) ? 1 : parseInt(router.query.page);
So that when I go to page=1, page=2 etc, I get new sets of data.
The functionality for this is called in the same main component, and is a React Query function:
const {data, status} = useQuery(["keycaps", {manu: manuId}, {prof: profileId}, {col: colorId}, {stat: statusId}], getKeycaps)
And said function looks like this:
const getKeycaps = async(key) => {
const manuId = key.queryKey[1].manu
const profileIds = key.queryKey[2].prof.map(id => `profile.id=${id}`)
const colorIds = key.queryKey[3].col.map(id => `filter_colors.id=${id}`)
const statId = key.queryKey[4].stat
const profileQueryString = profileIds.join("&")
const colorQueryString = colorIds.join("&")
let urlParams = new URLSearchParams(document.location.search);
let page = urlParams.get("page") == null ? 1 : parseInt(urlParams.get("page"));
let start = (page * 10) - 10
const data = await axios(`
${process.env.REACT_APP_STRAPI_API}/keycaps?${manuId ? 'manufacturer.id=' + manuId + '&' : ''}${profileQueryString ? profileQueryString + '&' : ''}${colorQueryString ? colorQueryString + '&' : ''}_start=${start}&_limit=10`)
return data.data
}
When initially loading pages, like directly pasting the url of the index in (i.e. keycaps?page=2), it will get all the results all fine. However, if I start using navigational buttons like this:
<Link href={`/keycaps?page=${page - 1}`}> // next/link
<div className="w-32 rounded-lg cursor-pointer">
Prev
</div>
</Link>
<Link href={`/keycaps?page=${page + 1}`}>
<div className="w-32 rounded-lg cursor-pointer">
Next
</div>
</Link>
The whole thing starts to break down. Essentially, the page doesn't actually reload any data or results until the page is unfocused. So, if I press the Next button to go to the next page, it won't load the data until I do something like tab to a new window or check a different internet tab, and then when I come back, the data will all magically load within a second.
I've tried this with next build && next start too, and this produces the same results. I just want to get the page results when the next and prev page buttons are pressed, and in a way that doesn't require the user to switch tabs to get content.
I will note that I do have a getStaticProps on this as well. It does the following:
export async function getStaticProps() {
const allKeycaps = (await getAllKeycaps() || 'Error')
return {
props: { allKeycaps }
}
}
Which will call an api script, and said script does this:
async function fetchAPI(query, {variables} = {}) {
const res = await fetch(`${process.env.REACT_APP_STRAPI_API}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
})
const json = await res.json()
if (json.errors) {
console.error(json.errors)
throw new Error('Failed to fetch API')
}
console.log('json', json.data, variables)
return json.data
}
/* Keycap related grabs */
export async function getAllKeycaps() {
const data = await fetchAPI(
`
{
keycaps {
id
slug
name
published_at
updatedAt
profile {
id
name
}
manufacturer {
id
name
lead
}
status {
id
name
}
colors
filter_colors {
color
}
kits
designer
thumb {
formats
}
}
}
`
)
return data
}
Anyone know how to get this to work? To navigate between indexes like this? I've been trying to look for Next tutorials that use navigations like page 1, page 2 etc but all I can find is examples of blog articles with slugs, no index searches of any kind.
Thanks a lot in advance.
Answer found:
When setting data and status using useQuery
const curPage = router.query.page == null ? 1 : router.query.page
const {data, status} = useQuery(["keycaps", {manu: manuId}, {prof: profileId}, {col: colorId}, {stat: statusId}, {page: curPage}], getKeycaps)
And then, in getKeycaps
const page = key.queryKey[5].page
I guess the "urlParams" approach wasn't a good one? Or at least, not one that was updating quick enough. So passing through the router page number seems to work better.

Array is sometimes recreated, and in other times modified

I am trying to implement a multiple image file selector in my React app.
This is my input element:
<input
type="file"
multiple
onChange={handleImageChange}
/>
{renderPhotos(venueImages)}
These are the functions that are called when files are chosen:
const [venueImages, setVenueImages] = useState([]);`
const renderPhotos = source => {
console.log(source); ////////log 1
return source.map(photo => {
return <img src={photo} key={photo} />;
});
};
const handleImageChange = e => {
if (e.target.files) {
const filesArray = Array.from(e.target.files);
console.log(filesArray); ///////// log 2
filesArray.forEach(file => {
const tempUrl = URL.createObjectURL(file);
console.log(tempUrl); ////// log 3
setVenueImages([...venueImages, tempUrl]);
});
}
};
I call renderPhotos to show a preview off all the selected photos before uploading.
The issue I'm facing is as follow:
If I choose, for example, 5 photos, only 1 would end up being rendered on screen.
I've inserted console logs in handleImageChange, and what I get logged is confusing me even more.
The second log (I've numbered them in my code) prints an array of 5 files.
After from log 3 that I'll get 5 logs of the newly generated temporary URLs for each of the files.
But log 1, would only get printed once.
Now - if I'll click the input element to choose more files, I'll end up with another rendered image.
So basically everytime I choose images, no matter how many I've chosen, I'll only get one more image rendered.
The problem is that you are referencing the venueImages array in your setVenueImages call. Because the state update performed by setVenueImages is asynchronous, then you cannot be certain that venueImages contains all of the previous state updates.
The set state function can be passed a function that takes the old value, instead of just passing it the new value. This will ensure that your state updates are sequential. Replace your setVenueImages call with this:
setVenueImages(prevImages => [...prevImages, tempUrl]);
An additional change that I will suggest is to perform a concatenation of all images, instead of adding them one by one. This should be faster.
const handleImageChange = e => {
if (e.target.files) {
const filesArray = Array.from(e.target.files).map(file => URL.createObjectURL(file));
console.log(filesArray); ///////// log 2
setVenueImages(prevImages => prevImages.concat(filesArray));
}
};
That is happening because when you are saving the tempUrl, only one url is getting saved. Also do not set the state by adding images one by one.
Updated version of your handleImageChange function can be
const handleImageChange = e => {
if (e.target.files) {
const filesArray = Array.from(e.target.files);
const tempUrls = filesArray.map(file => URL.createObjectURL(file)))
setVenueImages([...venueImages, ...tempUrls])
}
};

Wix guide for creating Related Product list doesn't reload itself onclick

I've followed Wix's guide to create a Related Products area at the bottom of their dynamic product page.
It mostly works as you'd expect. The problem comes when you click on one of the related products and see that the related products list doesn't change to reflect the currently loaded product.
The only way to get the related products list to change is by refreshing the page.
Is there possibly a simple fix for this? Below is their code:
import wixData from 'wix-data';
import wixLocation from 'wix-location';
$w.onReady(function () {
loadRelatedProducts();
});
async function loadRelatedProducts() {
let product = await $w('#productPage1').getProduct();
let relatedProductResults = await Promise.all([
relatedProductsByTable(product),
relatedProductsByPrice(product)
]);
if (relatedProductResults[0].length > 0)
showRelatedProducts(relatedProductResults[0]);
else
showRelatedProducts(relatedProductResults[1]);
}
async function relatedProductsByTable(product) {
let productId = product._id;
// find related products by relation table
let relatedByTable = await Promise.all([
wixData.query('RelatedProducts')
.eq('productA', productId)
.include('productB')
.find(),
wixData.query('RelatedProducts')
.eq('productB', productId)
.include('productA')
.find()
]);
let relatedProducts = [
...relatedByTable[0].items.map(_ => _.productB),
...relatedByTable[1].items.map(_ => _.productA)
];
return relatedProducts;
}
async function relatedProductsByPrice(product) {
let productId = product._id;
// find related products by price
let relatedByPrice = await wixData.query('Stores/Products')
.between('price', product.price * 0.8, product.price * 1.2)
.ne('_id', productId)
.find();
return relatedByPrice.items;
}
function showRelatedProducts(relatedProducts){
if(relatedProducts.length > 0){
relatedProducts.splice(4, relatedProducts.length);
$w('#relatedItemsRepeater').onItemReady(relatedItemReady);
$w("#relatedItemsRepeater").data = relatedProducts;
$w("#relatedItems").expand();
}
else {
$w("#relatedItems").collapse();
}
}
function relatedItemReady($w, product){
$w("#productImage").src = product.mainMedia;
$w("#productName").text = product.name;
$w("#productPrice").text = product.formattedPrice;
$w('#productImage').onClick(() => {
wixLocation.to(product.productPageUrl);
});
}
I suspect the issue is in this all being triggered by the .onReady() event. Unfortunately, I'm not sure how to also make this re-run on another trigger like when the related item itself is clicked.
The page itself does not reload when you click one of the related products. Instead, I believe they are simply rewriting the URL and then updating then re-fetching data from the database.
Indeed, you are correct. The page itself doesn't reload when a related item is selected so a new list of related items is not generated. Actually, at the time this example was published there was no simple way to get around this.
Since then, Wix has exposed the wix-location.onChange() function to take care of this very problem. All you need to do is add the following line:
wixLocation.onChange( () => loadRelatedProducts() );
It probably makes the most sense to add it right before the onReady() or even inside the onReady().

Updating id elements in react Native

I recently started with react (alongside react-native) and a lot of things are alien to me. I was creating an app in react native which shows cryptocurrency value in real time.
Coin cap provides web-socket connection so that the new values are automatically updated instead of fetching the entire stuff using axios.
Now, In simple HTML and javascript world, I assigned an ID to the elements and used for loop whenever the new data came from web-socket to find the value I need to update(or change).
Something like this
var socket = io.connect('https://coincap.io');
socket.on('trades', function (tradeMsg) {
var crypto = tradeMsg.coin;
for (let i=0; i<500; i++) {
var compare = cryptoSName[i].innerHTML;
if (compare == crypto) {
var oldPrice = document.getElementById('price' + i).innerHTML;
document.getElementById('price' + i).innerHTML= (tradeMsg.message.msg.price.toFixed(4));;
document.getElementById('perc' + i).innerHTML= tradeMsg.message.msg.perc + "%";
document.getElementById('pricer1' + i).innerHTML= (tradeMsg.message.msg.price.toFixed(4));;
document.getElementById('percr1' + i).innerHTML= tradeMsg.message.msg.perc + "%";
var newPrice = tradeMsg.message.msg.price;
[Question] In react I can still assign an ID to the elements but how can we do something like that (using web-socket to update data)? Also, keeping in mind how react components render (or re-render) things.
[Update] I am using Redux which stores the data in a state. Consider this
Data/state I receive from redux (through axios api call in some action)
class cryptoTicker extends Component {
componentWillMount() {
this.props.fetchCoin();
}
render() {
var CryptoData = this.props.cryptoLoaded;
let displayCrypto = CryptoData.map(el => {
return (
<CoinCard
key={el["long"]}
coinPrice = {el["price"].toFixed(2)}
/>
);
});
return (
<ScrollView>{displayCrypto}</ScrollView>
);
}
}
const mapStateToProps = state => {
return {
cryptoLoaded: state.posts.itemsSucess
}
};
export default connect(mapStateToProps, {fetchCoin})(cryptoTicker);
Note: Initially all the data is fetched through axios (ajayx) and websocket only sends changes in the fetched data
Just in case: Here is the link to coincap api's documentation
https://github.com/CoinCapDev/CoinCap.io

Code Splitting / Preloading Content while user is browsing?

Using tools like Webpack we can enable code splitting and only
load our application code asynchronously when required.
Example in the context of a react application with react-router.
Load initial page.
-> go to new route
---> webpack loads in the component file required asynchronous.
Webpack waits until the code is required in order to initiate the request.
My question is, once the base application code load, can we start loading the rest of the code, even before the user initiates the transition to the new route?
My view is that will prevent the user from waiting for the webpack chunk to download.
-> Load initial page
--> user sitting idle or browsing on home page
----> Start loading application code for rest of the application
---> user goes to new route (faster UX because code has already download in the background)
I hope this makes sense
Yes you can achieve this. I will show one of the possible solutions.
Firstly let's create backgroundLoader for queuing required chunks:
const queue = [];
const delay = 1000;
let isWaiting = false;
function requestLoad() {
if (isWaiting) {
return;
}
if (!queue.length) {
return;
}
const loader = queue.pop();
isWaiting = true;
loader(() => {
setTimeout(() => {
isWaiting = false;
requestLoad();
}, delay)
});
}
export default (loader) => {
queue.push(loader);
requestLoad();
}
This function will load your chunks in background with 1 second delay (you can tweak it - for example start popping queue after for example 5 second or shuffle array of chunks).
Next you must register your require.ensure in queuing function backgroundLoader:
import render from './render'; // not relevant in this example
import backgroundLoader from './backgroundLoader';
let lightTheme = (cb) => {
require.ensure([], () => {
cb(require('./themeA.css'));
}, 'light');
}
let darkTheme = (cb) => {
require.ensure([], () => {
cb(require('./themeB.css'));
}, 'dark');
}
let pinkTheme = (cb) => {
require.ensure([], () => {
cb(require('./themeC.css'));
}, 'pink');
}
backgroundLoader(lightTheme);
backgroundLoader(darkTheme);
backgroundLoader(pinkTheme);
export default (themeName) => { // router simulation
switch(themeName) {
case 'light':
lightTheme(render);
break;
case 'dark':
darkTheme(render);
break;
case 'pink':
pinkTheme(render);
break;
}
};
Once you require your chunk in switch statement you pass render function containing resolve function. In backgroundLoader this function will be empty resulting only loading chunk to head of your app.
Full code for this example you can see on WebpackBin (you can check network to see how chunks are loaded in background)

Categories