I've been tasked with implementing selectors in our redux application. Everything I'm reading online about redux selectors talks about React and how you can replace what is in mapStateToProps with a selector. What is the equivalent/where would i do this in a mithril app?
What is the equivalent/where would i do this in a mithril app?
Firstly, you don't need an equivalent, you can just use the exact same selectors that you would in a React application.
Where to call selectors?
You can call the selectors wherever you want, but I recommend calling them as close to where the data is used as possible. Don't call selectors in a component high up in the component hierarchy only to pass the data down via several components before they end up in a component that actually uses the data – unless you have a good reason to do so.
For most cases you can call the selectors inside a view-function, although you might come across cases where you need to call selectors in other lifecycle methods as well. In some applications you might also want to use selectors in m.render as well.
A couple of examples off the top of my head:
Inside the view function when creating DOM-elements
var LoggedinUserDetails = {
view: function () {
return m('', [
m('', getLoggedinUserName(store.getState())), // getLoggedinUserName is a selector
m('img', { src: getLoggedinUserImageUrl(store.getState()) }) // getLoggedinUserImageUrl is a selector
])
}
}
Inside the view function when creating Mithril components
var UserDetails = {
view: function (attrs) {
return m('', [
m('', attrs.user.name),
m('img', { src: attrs.user.imageUrl })
])
}
}
...
m(UserDetails, { user: getLoggedInUserDetails(store.getState()) }) // getLoggedInUserDetails is a selector
Inside m.render
In this example, we have a game that requires us to re-render the whole page after any change.
function onStateChange() {
var state = store.getState();
m.render(document.body, m(Game, {
map: getMap(state),
players: getPlayers(state),
visibleArea: getVisibleArea(state)
}));
}
// Receiving partial state updates from server via websockets
websocket.on('update-map', function (map) {
store.dispatch({ type: 'update-map', payload: map });
});
websocket.on('update-players', function (players) {
store.dispatch({ type: 'update-players', payload: players });
});
// Changing state based on user input
window.addEventListener('keydown', function (event) {
switch (event.key) {
case 'Enter':
store.dispatch({ type: 'move-visible-area-to-next-player' });
break;
}
});
I'm not familiar with Mithril, but Redux state selectors are independent from React. They are just functions that expect state and return a:
a slice of state
or data derived from state
For example, if I my Redux state has an entry named records containg a list of models:
{
records: [ ... ],
}
I could create a selector returning the length:
const numOfRecords = state => state.records.length
Or if my state also keeps track of a sortBy value:
const sortedRecords = state => state.records.sort(sortByFn(state.sortBy))
Selectors can be helpful to increase performance and reduce the need for updates. (reselect is a great module for this).
They are also great for developing modular pieces of code that depend on data stored in application state but don't really want to know where that data comes from.
Related
I am building an app that mounts hundreds or even thousands of instances of a vue component in the same view. Each component has an id that refers to an object in the vuex store.
<my-component :id="item1"></my-component>
<my-component :id="item2"></my-component>
<my-component :id="item3"></my-component>
...
The store:
state: {
myComponentData: [
{
id: 'item1',
name: 'Hawaiian Shirt',
selected: false,
// ...
},
// ...
]
}
In my-component, I look up its object in the store and store it in a computed property:
computed: {
myComponentData: function () {
return this.$store.state.myComponentData.find(el => el.id === this.id);
},
isSelected: function () {
return this.myComponentData.selected;
},
// ...
}
The object properties in the store can be manipulated by interacting with the component itself or by other components elsewhere in the app.
The problem is when I try running this with several hundred or 1000+ elements, it takes 30 seconds or longer to load.
If I remove the computed properties, or replace them with static values, then the page loads seamlessly. It's only when I try to load all of the computed properties from the store that I have lag issues.
Is there a way to improve performance when loading many (1500+) Vue components that use computed properties and Vuex? I have looked into dynamic/async components, vue-async-computed, and vue-rx as possible solutions, but I'm not sure which would help in this case.
The problem is within the logic. If you have 1000 items, you create 1000 components. Then each of them loops through the list of items in order to find the proper one based on id. So you are basically doing 1000 * 1000 loops (worst case of course). Which is not the worst thing - you are adding a reactivity through this computed property! Every time something changes, the computed method will fire again, leading to another 1000 loop (worst case scenario if your item is the last).
You have a few options:
Get the store's item while setting the data in your component. This will not create a computed property, so you'll be having a static data. Which will lead to making your own personal watchers for all the data you want changed.
(I would go with this) You're having the list of items in the parent component that shows the list. Don't pass just the id - pass the whole item. This way you won't need to use any computed properties, and you will have reactivity out of the box (props gets updated in children automatically).
Just don't add that many loops and computed properties - as the name states, they are for computing, not for filtering something you can filter elsewhere ;)
Good luck!
I think the Array.find() operation is most costly. Having it in the computed property means it runs every time the dependencies change.
You can get the array index in mounted() and use it in the computed,
export default {
name: "MyComponent",
props: ["id"],
data() {
return {
storeIndex: -2
};
},
mounted() {
this.storeIndex = this.$store.state.myComponentData.findIndex(
el => el.id == "item" + this.id
);
},
computed: {
myComponentData: function() {
// return this.$store.state.myComponentData.find(
// el => el.id === "item" + this.id
// );
return this.$store.state.myComponentData[this.storeIndex] || {};
},
isSelected: function() {
return this.myComponentData.selected;
}
}
};
Here is a CodeSandbox with a working example.
Ok, I believe I am VERY close to having my first working Vue JS application but I keep hitting little snag after little snag. I hope this is the last little snag.
I am using vue-async-computed and axios to fetch a customer object from my API.
I am then passing that property to a child component and rendering to screen like: {{customer.fName}}.
As far as I can see, the ajax call is being made and the response coming back is expected, the problem is there is nothing on the page, the customer object doesnt seem to update after the ajax call maybe.
Here is the profile page .vue file I'm working on
http://pastebin.com/DJH9pAtU
The component has a computed property called "customer" and as I said, I can see in the network tab, that request is being made and there are no errors. The response is being sent to the child component here:
<app-customerInfo :customer="customer"></app-customerInfo>
within that component I am rendering the data to the page:
{{customer.fName}}
But, the page shows no results. Is there a way to verify the value of the property "customer" in inspector? is there something obvious I am missing?
I've been using Vue for about a year and a half, and I realize the struggle that is dealing with async data loading and that good stuff. Here's how I would set up your component:
<script>
export default {
components: {
// your components were fine
},
data: () => ({ customer: {} }),
async mounted() {
const { data } = await this.axios.get(`/api/customer/get/${this.$route.params.id}`);
this.customer = data;
}
}
</script>
so what I did was initialize customer in the data function for your component, then when the component gets mounted, send an axios call to the server. When that call returns, set this.customer to the data. And like I said in my comment above, definitely check out Vue's devtools, they make tracking down variables and events super easy!
I believed your error is with naming. The vue-async-computed plugin needs a new property of the Vue object.
computed: {
customer: async function() {
this.axios.get('/api/customer/get/' + this.$route.params.id).then(function(response){
return(response.data);
});
}
}
should be:
asyncComputed: {
async customer() {
const res = await this.axios.get(`/api/customer/get/${this.$route.params.id}`);
return res.data;
}
}
I'm a novice to Meteor/React.js applications, and while I've been able to cobble together an app with multiple components by passing the App.jsx collections around as props, I can't seem to get individual components to handle their own collections. My rational is that some components need the collection fetched in one order, while others need the collection fetched in another. It seems silly to pass around a collection and then have to manipulate it internally to get the proper effect when a new sort is what's needed. My main App.jsx has:
App.propTypes = {
workdone:PropTypes.array.isRequired,
subjects:PropTypes.array.isRequired
};
export default createContainer(() => {
return {
workdone: WorkDoneCollection.find({},{sort:{createdAt:-1}}).fetch(),
subjects: SubjectCollection.find({},{sort:{subject:1}}).fetch()
};
}, App);
and if I pass the props.workdone on to another component everything works fine. On the other hand if I try a set up an independent collection in the other component, like this:
WorkSummary.propTypes = {
workdone:PropTypes.array
};
export default createContainer(() => {
return {
workdone: WorkDoneCollection.find({},{sort:{createdAt:1}}).fetch()
};
}, WorkSummary);
the workdone property is undefined.
The Flux documentation says that:
We often pass the entire state of the store down the chain of views in a single object, allowing different descendants to use what they need. In addition to keeping the controller-like behavior at the top of the hierarchy, and thus keeping our descendant views as functionally pure as possible, passing down the entire state of the store in a single object also has the effect of reducing the number of props we need to manage.
In this pseudo code (illustrating the passing of the whole store as suggested by the Flux docs), the interfaces of the components don't seem to be very well defined.
function header (state) {
profileLink(state);
navigation(state);
}
function postList (state) {
ul(state);
}
function footer (state) {
div(state.copyright);
}
function rootComponent () {
var state = getState();
header(state);
postsList(state);
footer(state)
}
Isn't the following example (with more specific interface of the components) more in accordance with the functional programming paradigm?
function header (userName, navigationItems) {
profileLink(username);
navigation(navigationItems);
}
function postList (posts) {
ul(posts);
}
function footer (copyright) {
div(copyright);
}
function rootComponent () {
var state = getState();
header(state.user.name, state.navigation.items);
postsList(state.posts);
footer(state.copyright);
}
The question is: Isn't the passing of the whole state down the component hierarchy an anti pattern?
I think what he means is sometimes is convenient to pass the entire state of one specific store down the children. It makes sense if all the children are related to the store. For example:
function rootComponent(userState, bookState){
bookTitle(bookState);
bookDescription(bookState);
bookPrice(bookState);
}
bookTitle(bookState) {
div(bookState.title)
div(bookState.author)
}
bookDescription(bookState) {
div(bookState.description)
}
bookPrice(bookState) {
div(bookState.price)
}
So, as you can see, I'm not passing the whole state of the app but all the state coming from the book store is being sent packed to all the children related with this store
I faced a problem, I solve it by cookies but I want to solve the problem without cookies. I have a component which called app-header and It has another component which called outmodal.
Now, My first Vue instance require component app-header.
var vue = new Vue({
el : "html",
data : {
title : "Site Title",
description : "description of page",
keywords : "my keywords",
view : "home",
login : "login"
},
components:{
"app-header" :require("../../components/header"),
"app-footer" :require("../../components/footer"),
"home" :require("../../views/home")
},
});
code of app-header
var Vue = require("vue");
Vue.partial("login",require("../../partials/login.html"));
Vue.partial("logged",require("../../partials/logged.html"));
module.exports = {
template : require("./template.html"),
replace : true,
components : {
outmodal : require("../outmodal")
},
props : ['login']
}
code of outmodal
var Vue = require("vue");
Vue.partial("loginModal",require("../../partials/loginModal.html"));
module.exports = {
template : require("./template.html"),
replace : true,
props : ['name'],
data : function () {
return {
userLogin : { mail : "", password : "", remember : ""}
}
},
methods : {
formSubmit : function(e){
e.preventDefault();
this.$http.post("http://example.com/auth/login",{ "email": this.userLogin.mail , "password": this.userLogin.password },function(data,status,request){
$.cookie("site_token",data.token,{expires : 1})
}).error(function(data,status,request){
});
}
}, ready : function(){
console.log("it works")
}
}
In outmodal component I connect the API and I check the login, If login will be succesfull, I want to change value of login variable in my Vue instance. I use web pack to build all requires. So I don't know how can I data binding between these files.
How can I solve It? I
The Best Solution which I found
For 0.12
http://012.vuejs.org/guide/components.html#Inheriting_Parent_Scope
for 1.0
http://v1.vuejs.org/guide/components.html#Parent-Child-Communication
for 2.0
https://v2.vuejs.org/v2/guide/components.html#Composing-Components (use props to one-way bind data from parent to child)
There are several ways of doing it, and some are mentioned in other answers:
Use props on components
Use v-model attribute
Use the sync modifier (for Vue 2.0)
Use v-model arguments (for Vue 3.0)
Use Pinia
Here are some details to the methods that are available:
1.) Use props on components
Props should ideally only be used to pass data down into a component and events should pass data back up. This is the way the system was intended. (Use either v-model or sync modifier as "shorthands")
Props and events are easy to use and are the ideal way to solve most common problems.
Using props for two-way binding is not usually advised but possible, by passing an object or array you can change a property of that object and it will be observed in both child and parent without Vue printing a warning in the console.
Because of how Vue observes changes all properties need to be available on an object or they will not be reactive.
If any properties are added after Vue has finished making them observable 'set' will have to be used.
//Normal usage
Vue.set(aVariable, 'aNewProp', 42);
//This is how to use it in Nuxt
this.$set(this.historyEntry, 'date', new Date());
The object will be reactive for both component and the parent:
I you pass an object/array as a prop, it's two-way syncing automatically - change data in the
child, it is changed in the parent.
If you pass simple values (strings, numbers)
via props, you have to explicitly use the .sync modifier
As quoted from --> https://stackoverflow.com/a/35723888/1087372
2.) Use v-model attribute
The v-model attribute is syntactic sugar that enables easy two-way binding between parent and child. It does the same thing as the sync modifier does only it uses a specific prop and a specific event for the binding
This:
<input v-model="searchText">
is the same as this:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
Where the prop must be value and the event must be input
3.) Use the sync modifier (for Vue 2.0)
The sync modifier is also syntactic sugar and does the same as v-model, just that the prop and event names are set by whatever is being used.
In the parent it can be used as follows:
<text-document v-bind:title.sync="doc.title"></text-document>
From the child an event can be emitted to notify the parent of any changes:
this.$emit('update:title', newTitle)
4.) Use v-model arguments (for Vue 3.0)
In Vue 3.x the sync modifier was removed.
Instead you can use v-model arguments which solve the same problem
<ChildComponent v-model:title="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" #update:title="pageTitle = $event" />
5.) Use Pinia (or Vuex)
As of now Pinia is the official recommended state manager/data store
Pinia is a store library for Vue, it allows you to share a state across components/pages.
By using the Pinia store it is easier to see the flow of data mutations and they are explicitly defined. By using the vue developer tools it is easy to debug and rollback changes that were made.
This approach needs a bit more boilerplate, but if used throughout a project it becomes a much cleaner way to define how changes are made and from where.
Take a look at their getting started section
**In case of legacy projects** :
If your project already uses Vuex, you can keep on using it.
Vuex 3 and 4 will still be maintained. However, it's unlikely to add new functionalities to it. Vuex and Pinia can be installed in the same project. If you're migrating existing Vuex app to Pinia, it might be a suitable option. However, if you're planning to start a new project, we highly recommend using Pinia instead.
i found this one to be more accurate.
https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
only in 2.3.0+ tho.
and honestly it's still not good enough. should simply be a easy option for 'two-way' data binding. so none of these options is good.
try using vuex instead. they have more options for such purpose.
https://vuex.vuejs.org/en/state.html
I would prefer event-driven updates as recommended in the documentation. However, I was limited by the existing ("third-party") component already using props and $emit. This component is my grandchild. The following is my solution (passing value through child using props, sync and computed value with $emit.
Comments are welcome.
Value can be modified in parent and grandchild without error:
Grandchild (simplified third-party component):
<template>
<div v-show="value">{{ value}}</div>
<button #click="closeBox">Close</button>
</template>
<script>
export default {
props: {
value: null
},
methods: {
closeBox() {
this.$emit('update:value', null);
}
}
}
</script>
Child:
<template>
<grandchild-component :value.sync="passedValue" />
</template>
<script>
export default {
props: {
value: null
},
computed: {
passedValue: {
get() {
return this.value;
},
set(newVal) {
this.$emit('update:value', newVal);
}
}
}
}
</script>
Parent:
<template>
<child-component :value.sync="value" />
</template>
<script>
export default {
data() {
return {
value: null,
}
},
// ... e.g. method setting/modifying the value
}
</script>