In Vue 2/3, and in an ES6 environment when I want to access a Vuex store in an external JS file (outside of the component), I would normally use something like this:
// example.js
import { store } from '../store/index';
console.log(store.state.currentUser);
This works great, however, in my current environment (Rails 5 without webpack), we can't use import statements at all.
Question: Is there any way, in regular ES5 JavaScript, to access Vuex stores outside of components?
It's worth noting that I've got a successful setup of Vuex going on our frontend, we just can't access it outside of our defined Vue components.
In my Rails setup, I have this:
// app/assets/javascripts/lib/vuex/store.js
const store = new Vuex.Store({
modules: {
activity: activityStore,
}
});
// application.js
//= require vue/dist/vue.min
//= require vuex/dist/vuex.min.js
$(document).ready(function () {
Vue.use(Vuex);
});
In this instance, store is just a global javascript object. Use it the same as you would any other JS object.
As long as you're inside a JS file that is properly compiled, and your base Vue is installed properly, you can just do store.state.activity.activities, or if you have mutations, store.commit('myMutation', 'Hello test').
My specific example was a huge function calling a webhook. The webhook can take a while, and I want to send messages to the user as I get new messages.
Example:
// webhook.js
async webhookFunction() {
// new message recieved
store.commit('newActivity', 'Your object has been updated');
}
Related
In the getServerSideProps function of my index page, I'd like to use a function foo, imported from another local file, which is dependent on a certain Node library.
Said library can't be run in the browser, as it depends on "server-only" modules such as fs or request.
I've been using the following pattern, but would like to optimize it. Defining foo as mutable in order to have it be in scope is clunky and seems avoidable.
let foo;
if (typeof window === "undefined") {
foo = require("../clients/foo");
}
export default function Index({data}) {
...
}
export async function getServerSideProps() {
return {
props: {data: await foo()},
}
}
What would be the best practice here? Is it somehow possible to leverage ES6's dynamic import function? What about dynamically importing within getServerSideProps?
I'm using Next.js version 9.3.6.
Thanks.
UPDATE:
It seems as if Next.js's own dynamic import solution is the answer to this. I'm still testing it and will update this post accordingly, when done. The docs seem quite confusing to me as they mentionn disabling imports for SSR, but not vice versa.
https://nextjs.org/docs/advanced-features/dynamic-import
When using getServerSideProps/getStaticProps, Next.js will automatically delete any code inside those functions, and imports used exclusively by them from the client bundle. There's no risk of running server code on the browser.
However, there are a couple of considerations to take in order to ensure the code elimination works as intended.
Don't use imports meant for the server-side inside client-side code (like React components).
Ensure you don't have unused imports in those files. Next.js won't be able to tell if an import is only meant for the server, and will include it in both the server and client bundles.
You can use the Next.js Code Elimination tool to verify what gets bundled for the client-side. You'll notice that getServerSideProps/getStaticProps gets removed as do the imports used by it.
Outside of getServerSideProps/getStaticProps, I found 2 fairly similar solutions.
Rely on dead code elimination
In next.config.js:
config.plugins.push(
new webpack.DefinePlugin({
'process.env.RUNTIME_ENV': JSON.stringify(isServer ? 'server' : 'browser'),
}),
);
export const addBreadcrumb = (...params: AddBreadcrumbParams) => {
if (process.env.RUNTIME_ENV === 'server') {
return import('./sentryServer').then(({ addBreadcrumb }) => addBreadcrumb(...params));
}
return SentryBrowser.addBreadcrumb(...params);
};
Note that some for reason I don't understand, dead code elimination does not work well if you use async await, or if you use a variable to store the result of process.env.RUNTIME_ENV === 'server'. I created a discussion in nextjs github.
Tell webpack to ignore it
In next.config.js
if (!isServer) {
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp: /sentryServer$/,
}),
);
}
In that case you need to make sure you will never import this file in the client otherwise you would get an error at runtime.
You can import the third party library or a serverside file inside getServerSideProps or getInitialProps since these functions run on server.
In my case I am using winston logger which runs on server only so importing the config file only on server like this
export async function getServerSideProps (){
const logger = await import('../logger');
logger.info(`Info Log ->> ${JSON.stringify(err)}`);
}
You can also import library/file which has default export like this
export async function getServerSideProps(context) {
const moment = (await import('moment')).default(); //default method is to access default export
return {
date: moment.format('dddd D MMMM YYYY'),
}
}
I have created a logging utility function that I plan to use on 99% of components in my site. I am wondering if it is possible to access this file without having to write "import { logger } from 'utils/logging';" for every React component? Sort of like an auto import?
I am using create-react-app.
If I understand your requirement properly, you want the similar usage of console.log (without importing console), then below is something you can try.
In your index file, set it as a global object(for server side js) or window object (for client side js). So that , it can be accessed anywhere.
We had something like this with a mmiddleware(using redux-logger package):
const logger = require('redux-logger').createLogger
return middleware.concat(logger({
collapsed: true,
duration: true
}))
Hope this helps!
What you're trying to do sounds like a bad way to do it. I think the best solution if you need custom data logged, would be to create/add a middleware. Or in React's case maybe a wrapper component. I'm not sure.
Otherwise look into React / Redux Dev Tools Extension.
https://github.com/facebook/react-devtools
https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
Edit:
If you want to ignore good practices then you can do this:
// in index.js or app.js or wherever
import { logger } from 'utils/logging'
// if you have an env for development use it here
process.NODE_ENV = 'development' && window.logger = logger
// or just
window.logger = logger
// SomeComponent.js
window.logger()
I am working on a Vue application that's living in a Laravel project. I bind my vue instance to an id that's placed in a blade file.
What I would like to do is to pass the logged user to my Vue instance from Laravel/blade. Is there a way to do this? I know you can pass data through props but this here is just a regular div with an id of #root that's binding the Vue instance. I know how to get the logged user, but I am specific looking for an way to directly pass the data from blade to my vue instance.
app.js
// Require the deps from laravel (jQuery, axios, Bootstrap)
require('./bootstrap');
// Import vue deps
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter)
// Import the router & routes
import router from './routes'
// Init a new vue instance
const root = new Vue({
el: '#root',
data: {
User: name here..
},
router
});
Blade
#extends('layouts.app')
#section('content')
<!-- pass user below -->
<div id="root"></div>
#endsection
In your blade file, pass the logged user information to a javascript global variable. Let's say you just want the user id and name:
<script>
window.auth_user = {!! json_encode([
'id' => auth()->user()->id,
'name' => auth()->user()->name
]) !!};
</script>
Then in your javascript, you can access the auth_user global variable.
For example, to log the user name:
console.log(auth_user.name)
or
console.log(window.auth_user.name)
You have few options (I think I not list all) e.g:
You can pass data by converting them to json and write as HTML element or attribute and then read it from vue using e.g. document.querySelector(...) - more info here: Best way to store JSON in an HTML attribute?
You can change a littlebit architecture and create separate (Restful) API which your vue components will be use via ajax to read data (e.g. you can create GET api/v1/currentUser do read current logged user)
Completly change your architecture - to "microservices" - so in laravel only create Restful API, and creatte SEPEARATE project with vue (and NO laravel) user interface which use that API (it is modern approach to separation backend from frontend). You will face CORS problem in this approach but its no so hard (only at first time).
You might want to take a look at the PHP-Vars-To-Js-Transformer
package. You can use it either in your controller or in a #php directive.
Probably not a good practice with VueJS though.
In my angular app I use angular-redux for application state management. In my main module I defined my redux store. Like this:
export class MainModule {
constructor(private ngRedux: NgRedux<MainAppState>,
private devTools: DevToolsExtension) {
let enhancers = [];
if (environment.production === false && devTools.isEnabled()) {
enhancers = [...enhancers, devTools.enhancer()];
}
this.ngRedux.configureStore(
reducer,
{} as MainAppState,
[],
enhancers);
}
}
I created new child module, which contains some components. These components should access to application state. In one of these components I access via #select to store, but this doesn't work. Here is how I access to store:
export function getLanguage(state: LanguageState) { return state.userLanguage; }
And this code I have in my ChildComponent class:
export class ChildComponent implements OnInit {
#select(getLanguage) savedUserLanguage$: Observable<LanguageState>;
// more code
}
How can I access to application state store from child modules? What should I import in child module? Will It be better to create own module only for redux store handling? Maybe I forgot something?
I use Angular v4 and #angular-redux/store v6.
I'd recommend creating a separate module that just contains your store, e.g. StoreModule. You can then import your StoreModule into all your child modules and access your store from there.
This is the way they go in the official example app:
StoreModule: https://github.com/angular-redux/example-app/blob/master/src/app/store/module.ts
Child Module: https://github.com/angular-redux/example-app/blob/master/src/app/elephants/module.ts
Component in child module: https://github.com/angular-redux/example-app/blob/master/src/app/elephants/page.ts
I was thinking about refactoring some ugly old JavaScript code that uses prototypal inheritance into an Angular 7+ project. I was asking myself pretty much the same question. Inspired by my udemy Angular course, I tried an experiment with a ngrx store and lazy loaded modules.
(Keep in mind that ngrx is SIMILAR to #angular-redux, but it's NOT the same thing. See https://ngrx.io/docs for details.)
Here it is.
I create the store in the main module with StoreModule.forRoot and in each lazy loaded module, I create a reference to the store with StoreModule.forFeature.
(See https://ngrx.io/api/store/StoreModule for details.)
When I dispatch actions on the store with the lazy loaded components, those actions (and corresponding reducers) seem to change the value to which the main app component subscribes.
Also, when I dispatch actions on the store with the main app component, those actions (and corresponding reducers) seem to change the value to which the lazy loaded components subscribe.
Also, it's hard to explain what I did in a simple 200-500 character block so I had to use a github project.
Background:
So I'm creating an admin package for meteor 1.4.2 with react so I can learn how to do that sort of thing. The admin package will just be able to update user defined collections (insert, delete, modify).
I have this file in my application under imports/api/posts.js:
// imports/api/posts.js
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
export const Posts = new Mongo.Collection('posts');
if (Meteor.isServer) {
Meteor.publish('posts', function postsPublication() {
return Posts.find();
});
}
I can easily access this file within my application using for example import { Posts } from '../imports/api/posts.js';.
Problem:
How can I access the same Posts collection from within the admin package so I can insert a new item, remove one, etc?
Also, I saw this post earlier about a similar thing, but does that mean packages such as yogiben:admin don't work with the module system either?
The key to understanding this is realising that some meteor packages are libraries, and some are extensions of the (Meteor) framework as defined here.
yogiben:admin is an extension of the Meteor framework, in that it needs to be able to find code that you have written (your collections) in order to work correctly.
How you enable this is up to you. Previously collections were globally defined, so that they would be (automatically/eagerly) imported, and generally outside the /client or /server directories so they would be accessible on both the client and server.
Now you have the choice - define your collections outside the /imports directory, and they will still be eagerly imported, or import them where your admin framework requires them. As a third way you could require they be attached to the (server side) global object e.g. as a dict (i.e. global.myCollections = {'posts': Posts}), and (in browser) the window object (with window.myCollections = {'posts': Posts}).
yogiben:admin's example starter repo keeps everything outside /imports, however I suspect this would still work fine if you just kept the collection definitions outside /imports, moving the rest of the code to the currently recommended project structure.
I do not think that Meteor has a way to get all collections defined in an app internally.
To achieve that you could override the Mongo.Collection method to keep a reference to every created collections.
const _oldCollection = Mongo.Collection;
Mongo.Collection = function(name, options) {
const c = new _oldCollection(name, options);
Meteor.myCollections[name] = c;
return c;
};
Then use it as:
// define new collection
new Mongo.Collection('questions');
// retrieve collection
const c = Meteor.myCollections['questions'];
Make sure the code used to override Mongo.Collection loaded before you use it to define new collections.