I am doing my first Next.js project and I am struggling with making this component to work:
"use client"
import styles from './page.module.scss'
import ListFetcher from '../components/ListFetcher/ListFetcher';
import { useSearchParams }from 'next/navigation';
export default function Home() {
const searchParams = useSearchParams();
const category = searchParams.get('category');
const api_link = `/api/products/category/${category}`;
return (
<div className={styles.page_body}>
<article>
<ListFetcher api_url={api_link} />
</article>
</div>
)
}
When I run it, I get
Uncaught Error: Objects are not valid as a React child (found: [object
Promise]). If you meant to render a collection of children, use an
array instead.
Everything works if I make the api_link static and comment out
"use client"
const searchParams = useSearchParams();
const category = searchParams.get('category');
ListFetcher is the component where the API call happens.
What am I doing wrong? Is there an alternative how to get the routes without making this a Client Component?
Related
I know that you are unable to dynamically generate an arbitrary string in TailwindCSS like hue-rotate-[${randomHueColor}deg], because the arbitrary string has to exist at build time.
I see that it also seems impossible to generate the string in a different component and pass it through a prop to the component.
Eg.
<script setup>
import {ref, onBeforeMount} from 'vue'
import ImageComponent from './components/ImageComponent.vue'
const randomHue = ref('')
function generateRandomHue(){
let random = Math.floor(Math.random() * 361);
randomHue.value = String(`hue-rotate-[${random}deg]`) // or String(`filter: hue-rotate(${random}deg)`)
}
onBeforeMount(() => {
generateRandomHue()
})
</setup>
<template>
<ImageComponent :hueColor="randomHue" />
</template>
On the component side, I've tried both class: and style: (with filter:).
Is there another way to go about this, so I can have a truly dynamic random arbitrary hue-rotate?
I'm trying to create a Vue 3 component library using composition API:
https://github.com/hyperbotauthor/vue3complib
In one of the components I would like to import an other composition API component ( https://github.com/hyperbotauthor/vue3complib/blob/main/src/components/ChessboardExt.vue ):
import { Perscombo } from "../index"
const PerscomboE = (Perscombo as any).setup
const e = PerscomboE({id: "variant", options: variants}, context)()
const vertContainer = h(
"div",
{
},
[e, outerContainer]
);
This almost works, because the component's node is created with its setup function, and it is even rendered on the page correctly, however its onMounted function does not get called properly and I get the warning
onMounted is called when there is no active component instance to be associated with.
Lifecycle injection APIs can only be used during execution of setup().
If you are using async setup(), make sure to register lifecycle hooks before the first await statement.
Not only a warning, but unfortunately I need this for initializing the component, so it is not fully functional without its onMounted function as it should be persistent and its state cannot be initialized from localStorage.
How do I import an other composition API component into my composition API component's setup properly?
EDIT:
Managed to remove onMounted from the child component and I can pass a callback in props for the case when its state changes. So for this case I solved the issue. In general I still don't know the solution.
You can pass an imported custom component via the h function, like an HTML tag. Event handling works the same way as with HTML elements: You prefix the handler name with on, use camel case, and pass the handler as a property with this name:
const variantCombo = h(Perscombo, {id: props.id + "/variant", options: variants, onPerscombochanged: (event:any) => {
setVariant()
}})
const sizeCombo = h(Perscombo, {id: props.id + "/size", options: sizes, onPerscombochanged: (event:any) => {
resize(event.value)
}})
const upperControls = h(
"div",
{
},
[variantCombo, sizeCombo]
);
I use Vue 3 and I have a dynamic component. It takes a prop called componentName so I can send any component to it. It works, kind of.
Part of the template
<component :is="componentName" />
The problem is that I still need to import all the possible components. If I send About as a componentName I need to import About.vue.
Part of the script
I import all the possible components that can be added into componentName. With 30 possible components, it will be a long list.
import About "#/components/About.vue";
import Projects from "#/components/Projects.vue";
Question
It there a way to dynamically import the component used?
I already faced the same situation in my template when I tried to make a demo of my icons which are more than 1k icon components so I used something like this :
import {defineAsyncComponent,defineComponent} from "vue";
const requireContext = require.context(
"#/components", //path to components folder which are resolved automatically
true,
/\.vue$/i,
"sync"
);
let componentNames= requireContext
.keys()
.map((file) => file.replace(/(^.\/)|(\.vue$)/g, ""));
let components= {};
componentNames.forEach((component) => { //component represents the component name
components[component] = defineAsyncComponent(() => //import each component dynamically
import("#/components/components/" + component + ".vue")
);
});
export default defineComponent({
name: "App",
data() {
return {
componentNames,// you need this if you want to loop through the component names in template
};
},
components,//ES6 shorthand of components:components or components:{...components }
});
learn more about require.context
This is the code implementation in screenshot. I just created a simple application that contains one button and one input field where I'm saving the input text and printing in the log on console after button press but it shows in error with **const [enterGoal, setGoalState] = useState('')**. Can someone have a look on it?
The problem is you are using a class component and hooks are meant to be used in functional components. So thats the error. If you are using class try using setState method to update state and also define states in constructor. like
constructor(props){
this.state={
nameOfPerson:'robert'
}
}
and inside any function do this.setState({nameOfPerson:'wowo'}) to change it,,
you are using hooks in class components .hooks are working in functional componets
for example
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
for more check documentation
My Vue app has a dynamic tabs mechanism.
Users can create as many tabs as the want on the fly, each tab having its own state (eg "Pages").
I am using the <keep-alive> component to cache the different pages.
<keep-alive include="page">
<router-view :key="$route.params.id" />
</keep-alive>
But users can also "close" individual tab. As pages tend to store a lot of datas, I would like to delete the according page component from the cache, as the user close the tab.
How can I programmatically destroy a cached component inside keep-alive ?
You can call this.$destroy() before user close the tab and delete all of data and event binding in that one.
If you don't mind losing the state when a tab is added/removed, then you can try these:
Use v-if and turn off the keep-alive component and turn it back on in
nextTick
Use v-bind on the include list, and remove "page" and add it
back in nextTick
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
cachedViews is the array of the route component name
First when create a tab, cachedViews push the cached route name, when you switch the opened tab, the current route is cached.
Second when close the tab, cachedViews pop the cached route name, the route
component will destroyed.
There is no built-in function in keep-alive which allows you to clear a specific component from the cache.
However, you can clear the cache from the VNode directly inside the component you want to destroy by calling this function :
import Vue, { VNode } from 'vue'
interface KeepAlive extends Vue {
cache: { [key: string]: VNode }
keys: string[]
}
export default Vue.extend({
name: 'PageToDestroy',
...
methods: {
// Make sure you are not on this page anymore before calling it
clearPageFromKeepAlive() {
const myKey = this.$vnode.key as string
const keepAlive = this.$vnode.parent?.componentInstance as KeepAlive
delete keepAlive.cache[myKey]
keepAlive.keys = keepAlive.keys.filter((k) => k !== myKey)
this.$destroy()
}
},
})
For me, it doesn't cause any memory leaks and the component is not in the Vue.js devtools anymore.
based on the answer of #feasin, here is the setup I am using
template
<router-view v-slot="{ Component }">
<keep-alive :include="cachedViews">
<component :is="Component" :key="$route.fullPath" />
</keep-alive>
</router-view>
script
import { ref, inject, watch } from "vue";
export default {
components: { CustomRouterLink },
setup() {
const cachedViewsDefault = ["Page1", "Page1", "Page3"];
var cachedViews = ref([]);
const auth = inject("auth");
// check whether user is logged in (REACTIVE!)
const isSignedIn = auth.isSignedIn;
// set the initial cache state
if (isSignedIn.value) {
cachedViews.value = cachedViewsDefault;
}
// clear the cache state
watch(isSignedIn, () => {
if (!isSignedIn.value) {
cachedViews.value = [];
} else {
cachedViews.value = cachedViewsDefault;
}
});
return {
cachedViews,
};
},
};
First I set the initial cached views value based on whether the user is logged in or not.. After the user logs-out I simply set the array value to an empty array.
When the user logs back in - I push the default array keys back into the array.
This example of course does not provide the login/logout functionality, it is only meant as a POC to to the solution proposed by the #feasin (which seems like a good approach to me)
Edit 19.01.2022
I now understand the shortcomings of such approach. It does not allow to gradually destroy a certain component. Given that we have a component named Item and it's path is Item/{id} - there is currently no native way (in Vuejs3) to remove, let's say a cached item with Id = 2. Follow up on this issue on the Github: https://github.com/vuejs/rfcs/discussions/283
Edit 20-21.01.2022
Note that you have to use the computed function for inclusion list. Otherwise the component will not ever be unmounted.
Here is the fiddle with the problem: https://jsfiddle.net/7f2d4c0t/4/
Here's fiddle with the fix: https://jsfiddle.net/mvj2z3pL/
return {
cachedViews: computed(() => Array.from(cachedViews.value)),
}