Default layout doesn't work in Laravel + Vite + Svelte - laravel

I started my first project with Laravel + Vite (I already used Inertia with Laravel + Webpack) and the problem I have is the default layout.
When using Webpack I could define the layout with the following code:
createInertiaApp({
resolve: name => {
const page = require(`../svelte/Pages/${name}.svelte`);
if (guestPages.indexOf(name) !== -1) {
page.layout = LayoutGuest
} else {
page.layout = Layout
}
return page
},
setup({ el, App, props }) {
new App({ target: el, props })
},
})
But now, with the new Vite way, I can't get it to work.
Here's the code I have:
async function resolve(name)
{
const page = resolvePageComponent(`../svelte/Pages/${name}.svelte`, import.meta.glob('../svelte/Pages/**/*.svelte'));
let component;
await page
.then(module => {
module.default.layout = Layout;
component = module;
});
return component;
I don't know if the problem is the dynamic import.

With the help of an Inertia server member on Discord (Robert Boes) and writing a few lines of code, I found the solution:
// import './bootstrap';
import { createInertiaApp } from '#inertiajs/inertia-svelte'
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
import "../less/app.less";
import Layout from "../svelte/Base/Layout.svelte";
async function resolve(name)
{
let component;
const pagesWithoutLayout = [
'Session/Index',
];
const page = resolvePageComponent(`../svelte/Pages/${name}.svelte`, import.meta.glob('../svelte/Pages/**/*.svelte'));
await page
.then(module => {
component = pagesWithoutLayout.includes(name) ?
module :
Object.assign({ layout: Layout }, module);
});
return component;
}
createInertiaApp({
resolve,
setup({ el, App, props }) {
new App({ target: el, props })
},
});

I made it work without Svelte with the following code snipped.
Hope it helps and maybe you can adapt it to your needs.
Im using it in a TypeScript environment, which is why I added "as any;".
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: async (name) => {
const page = resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob("./Pages/**/*.vue")
) as any;
await page.then((module) => {
//if name starts with auth, then use login layout
if (name.startsWith("Auth/")) {
module.default.layout = module.default.layout || LoginLayout;
} else if (name.startsWith("Public/")) {
module.default.layout = module.default.layout;
} else {
module.default.layout = module.default.layout || AppLayout;
}
});
return page;
},
setup({ el, app, props, plugin }) {
createApp({ render: () => h(app, props) })
.use(plugin)
.use(createPinia())
.mount(el);
},
});

Related

Cannot see data in view page source even though Cache of Apollo Client have data

I don't know why in another page, I use this way just different query and I can see data in view page source, but in this page , it not work. I wondering it cause I use localStorage value as params, i don't think problem come from query.
interface Props {
__typename?: 'ProductOfBill';
amount: number,
name: string,
totalPrice: number,
type: string,
unitPrice: number,
}
const Cart = () => {
const [products,setProducts] = useState<Props[]>([])
const { data } = useGetSomeProductQuery({
variables: { productList: productListForBill()},
notifyOnNetworkStatusChange: true
});
useEffect(() =>{
if(data?.getSomeProduct){
setProducts(data.getSomeProduct)
}
},[data])
return (
<>
...
</>
);
};
export const getStaticProps: GetStaticProps = async () => {
const apolloClient = initializeApollo();
await apolloClient.query<GetSomeProductQuery>({
query: GetSomeProductDocument,
variables: { productList: productListForBill() },
});
return addApolloState(apolloClient, {
props: {},
});
};
export default Cart;
I get localStorage value from this method.
export const productListForBill = () : GetProductForBill[] =>{
const returnEmtpyArray : GetProductForBill[] = []
if(typeof window !== "undefined"){
if(localStorage.getItem("products"))
{
const tempProduct = JSON.parse(localStorage.getItem("products") || "")
if(Array.isArray(tempProduct)){
return tempProduct
}
}
}
return returnEmtpyArray
}
and I custom Apollo Client like doc of Nextjs in github
import { useMemo } from 'react'
import { ApolloClient, HttpLink, InMemoryCache, NormalizedCacheObject } from '#apollo/client'
import merge from 'deepmerge'
import isEqual from 'lodash/isEqual'
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'
interface IApolloStateProps {
[APOLLO_STATE_PROP_NAME]?: NormalizedCacheObject
}
let apolloClient : ApolloClient<NormalizedCacheObject>
function createApolloClient() {
return new ApolloClient({
//type of "window"=== undifined
ssrMode: true,
link: new HttpLink({
uri: "http://localhost:4000/graphql",
credentials: "include",
}),
cache: new InMemoryCache()
)}
}
export function initializeApollo(initialState : NormalizedCacheObject | null = null) {
const _apolloClient = apolloClient ?? createApolloClient()
if (initialState) {
const existingCache = _apolloClient.extract()
cache
const data = merge(existingCache, initialState, {
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
...destinationArray.filter((d) =>
sourceArray.every((s) => !isEqual(d, s))
),
],
})
_apolloClient.cache.restore(data)
}
if (typeof window === 'undefined') return _apolloClient
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function addApolloState(client : ApolloClient<NormalizedCacheObject>, pageProps: { props: IApolloStateProps }) {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract()
}
return pageProps
}
export function useApollo(pageProps : IApolloStateProps) {
const state = pageProps[APOLLO_STATE_PROP_NAME]
const store = useMemo(() => initializeApollo(state), [state])
return store
}
Answering
Cannot see data in view page source even though Cache of Apollo Client have data
These are client side methods, value will not be visible in view source but in evaluated source, look in the elements panel in chrome.

How to listen emit event in parent component in vue3

I want to pass event from child comment to parent.
I did same thing in vue2 but i don't know how to that in vue3.
This one is child component setup method.
setup(props, { emit }) {
const router = useRouter();
const form = ref(
{
email: "ajay#gmail.com",
password: "123456789",
isLoading: false,
},
);
const user = ref("");
const error = ref("");
function login() {
User.login(this.form).then(() => {
emit('login', true);
// this.$root.$emit("login", true); -- vue2
localStorage.setItem("auth", "true");
router.push('/dashboard');
})
.catch(error => {});
}
return { form, login, user, error};
}
from here emit login method and i want to listen in parent comment.
this is parent component, emit.on method not working here
setup(props, { emit }) {
const router = useRouter();
const state = reactive({
isLoggedIn: false,
});
onMounted(async () => {
emit.on("login", () => { // `vue2` this.$root.$on("login"`
this.isLoggedIn = true;
});
});
In parent component you should add a handler for that emitted event :
<child #login="onLogin"></child>
setup(props, { emit }) {
const router = useRouter();
const state = reactive({
isLoggedIn: false,
});
function onLogin(){
state.isLoggedIn=true,
}
return{state,onLogin}
}
Or make a composable function named useAuth in separate file :
import {reactive} from 'vue'
const state = reactive({
isLoggedIn: false,
});
const useAuth=()=>{
function onLogin(){
state.isLogged=true;
}
return {state,onLogin}
}
export default useAuth();
then import the function inside the two components :
child :
import useAuth from './useAuth'
....
setup(props, { emit }) {
const router = useRouter();
const {useAuth} =useAuth();
....
function login() {
User.login(this.form).then(() => {
onLogin() //will call the nested function that set loggedIn to true
localStorage.setItem("auth", "true");
router.push('/dashboard');
})
.catch(error => {});
}
in parent :
import useAuth from './useAuth'
....
setup(props, { emit }) {
const router = useRouter();
const {state} =useAuth();
//it replaces your local state

Laravel + Vue.js - how to have a global variable?

I have a project using Laravel and Vue.js. I guess it wasn't the best idea not to separate them, but we learn from our mistakes ;)
Here is how it works:
I have been struggling trying to put global variables, such as "current user". Now, I am calling /currentuser through axios each time I need it, or I put it in props, but it drives my crazy... How can I make it global?
I am wondering if Vuex could work in my project, as everything is called from Laravel, the routes as well...
I have tried several things in app.js (here are 2 of them, mixed):
var curruser=null;
axios.get('/currmember').then(
response => {
curruser=response.data;
}
);
Vue.mixin({
methods: {
},
data: function() {
return {
myvaiable: '', //this doesn't work eather
get currentUser() {
if(curruser==null){
axios.get('/currmember').then(
response => {
curruser=response.data;
return curruser;
}
);
}
return curruser;
}
}
}
});}
in TestComponent.vue
<template>
<div>
{{currentUser}}
{{myvariable}} <!-- none of them display anything -->
</div>
</template>
Here is how things are working (simplify them very much):
app.js
import Vue from 'vue';
window.Vue = require('vue');
var App = Vue.component('app', require('./App.vue').default, {
name: 'app'
});
var shol = Vue.component('test', require('./components/TestComponent.vue').default);
let lang=localStorage.Lang!=null?localStorage.Lang:'fr';// = document.documentElement.lang.substr(0, 2);
init();
function init(){
const app = new Vue({
el: '#app',
i18n,
components:{test
}
});
var curruser=null;
axios.get('/currmember').then(
response => {
curruser=response.data;
}
);
Vue.mixin({
methods: {
},
data: function() {
return {
currentUser: 'blabla',
get currentUser2() {
if(curruser==null){
axios.get('/currmember').then(
response => {
curruser=response.data;
console.log(curruser);
return curruser;
}
);
}
return curruser;
}
}
}
});}
test.blade.php
#extends('template')
#section('pageTitle', 'test' )
#section('contenu')
<div >
<test></test>
</div>
#endsection
web.php
Route::get('/test', function () {
return view('test');
});
You may use vuex to access current authenticated user:
On app.js:
import Vue from 'vue';
import store from './store';
const app = new Vue({
el: '#app',
store,
i18n,
components:{ test },
created() {
store.dispatch('getUser');
}
});
The store.js:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
user: {},
},
getters: {
user: state => state.user,
},
mutations: {
setUser(state, user) {
state.user = user;
},
},
actions: {
getUser({ commit }) {
return new Promise((resolve, reject) => {
axios.get('/currmember')
.then(result => {
commit('setUser', result.data);
resolve();
})
.catch(error => {
reject(error.response && error.response.data.message || 'Error.');
});
});
},
}
})
The test component:
<template>
<div>
{{ currentUser }}
</div>
</template>
<script>
export default {
computed: {
currentUser() {
return this.$store.state.user;
}
}
};
</script>

"V8Js::compileString():1812: ReferenceError: document is not defined" Laravel VueJS & V8Js TypeScript

I am attempting to implement SSR with VueJS, VueRouter in Laravel using V8Js, and I keep coming up with this error:
V8JsScriptException
V8Js::compileString():1812: ReferenceError: document is not defined
import App from './app'
import Vue from "vue";
import router from "./router";
let app = new Vue(App);
Vue.config.productionTip = false;
// #ts-ignore
export default new Promise((resolve, reject) => {
// #ts-ignore
router.push(url);
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
return reject({
code: 404
});
}
resolve(app);
}, reject);
}).then(app => {
// #ts-ignore
renderVueComponentToString(app, (err, res) => {
// #ts-ignore
print(res);
});
}).catch((err) => {
// #ts-ignore
print(err);
});
...
$js =
<<<EOT
var process = { env: { VUE_ENV: "server", NODE_ENV: "production" } };
this.global = { process: process };
var url = "$path";
EOT;
$v8 = new \V8Js();
$v8->executeString($js);
$v8->executeString($renderer_source);
$v8->executeString($app_source);
$markup = ob_get_clean();
return $markup;
...
In SSR there are no window nor document, I know, but I cannot tell where's my error.
If there's anymore details needed I can provide, thank you.

How to transition routes after an ajax request in redux

I'm wondering at a high level what the correct pattern is for the following...
I have a HomeComponent, with some links to other components.
When I click on one of the links, I want to make an ajax request to get the initial state for that component.
Do I dispatch in the HomeComponent in the onClick? Or dispatch an action in the other components if there's no initialState from the server? (I'm doing a universal app, so if I was to hit one of the other components directly, the initial state would already be there, but coming from my HomeComponent, the data WON'T be there)
This is what I had so far...
class HomeComponent extends React.Component {
navigate(e) {
e.preventDefault();
// Fetch data here
actions.fetch(1234);
// When do I call this?
browserHistory.push(e.target.href);
}
render() {
const links = [
<a href="/foo/1247462" onClick={this.navigate}>Link 1</a>,
Link 2,
];
return (
<ul>
{links.map((link) => (
<li>{link}</li>
))}
</ul>
);
}
}
Sorry i can add a comment, is there a reason you're not using react-redux && redux-thunk ?
what you ask can be easily done with those : you fetch what you need in mapDispatchToProps & dispatch an action with the fetched initial state
Your reducer will catch the said dispatched action and update its state which will update the props of the react component with the help of mapStateToProps
I am writing from memory, it might not be accurate 100% :
redux file
componentNameReducer = (
state = {
history: ''
},
type = {}
) => {
switch(action.type) {
case 'HISTORY_FETCHED_SUCCESSFULLY':
return Object.assign({}, state, {
history: action.payload.history
});
default:
return state;
}
};
mapStateToProps = (state) => {
history: state.PathToWhereYouMountedThecomponentNameReducerInTheStore.history
};
mapDispatchToProps = (dispatch) => ({
fetchHistory : () => {
fetch('url/history')
.then((response) => {
if (response.status > 400) {
disptach({
type: 'HISTORY_FETCH_FAILED',
payload: {
error: response._bodyText
}
});
}
return response;
})
.then((response) => response.json())
.then((response) => {
//do checkups & validation here if you want before dispatching
dispatch({
type: 'HISTORY_FETCHED_SUCCESSFULLY',
payload: {
history: response
}
});
})
.catch((error) => console.error(error));
}
});
module.exports = {
mapStateToProps,
mapDispatchToProps,
componentNameReducer
}
On your react component you will need :
import React, {
Component
} from 'somewhere';
import { mapStateToProps, mapDispatachToProps } from 'reduxFile';
import { connect } from 'react-redux';
class HistoryComponent extends Component {
constructor(props){
super(props);
this.props.fetchHistory(); //this is provided by { connect } from react-redux
}
componentWillReceiveProps(nextProps){
browserHistory.push(nextProps.history);
}
render() {
return (
);
}
}
//proptypes here to make sure the component has the needed props
module.exports = connect(
mapStateToProps,
mapDispatachToProps
)(HistoryComponent);

Resources