Is there react/redux like mapStateToProps way in Vue/Vuex - reactjs

Usual way to map state and actions in React/Redux looks something like this, so mapping functions are placed separately from component code:
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import myAction from 'actions/request';
class MyComponent extends Component {
/* BODY */
}
function mapStateToProps(state) {
return {
myComponentProp: state.myReducer.myReducerProp
};
}
function mapDispatchToProps(dispatch) {
return {
myComponentPropAction: bindActionCreators(myAction, dispatch),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
The only described way to map state and actions I have found in Vue looks like this
import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState('myReducer', {
myComponentProp: (state) => state.myReducerProp,
}),
...{
/* aditional manipulations with this.myComponentProp */
}
},
methods: {
...mapActions('myReducer', [
'myReducerAction'
]),
...{
myEventHandler: function() {
/* checke conditions before action call */
this.myReducerAction();
},
}
}
}
Because of the number of spreading, the code looks fuzzy, so the question is:
Is there a way to move mapState and mapActions outside component like in react/redux usual approach.
Thanks for help!

Okay so along with typescript support they also added in a vue-class-component decorator which could be used to achieve what your after. The link to the repository for this can be found here but I would suggest instead creating a new project via the CLI and going from there as it was added in v3 Vue Class Component Github Repository.
<script>
function Getter (getterType) {
return createDecorator((options, key) => {
if (!options.computed) options.computed = {}
options.computed[key] = function () {
return this.$store.getters[getterType]
}
})
}
import Vue from 'vue'
import Component from 'vue-class-component'
#Component({
props: {
propMessage: String
}
})
export default class App extends Vue {
#Getter('foo') bar
#Setter('psu') psi
// computed
get computedMsg () {
return 'computed ' + this.msg
}
// method
greet () {
alert('greeting: ' + this.msg)
}
}
</script>
As you can see were calling in our getters and setters using a function here which is less than optimal but is closer to a succulent answer. Now in comes the vuex-class-binding package which abstracts all of those murky functions: vuex class
import Vue from 'vue'
import Component from 'vue-class-component'
import {
State,
Getter,
Action,
Mutation,
namespace
} from 'vuex-class'
const someModule = namespace('path/to/module')
#Component
export class MyComp extends Vue {
#State('foo') stateFoo
#State(state => state.bar) stateBar
#Getter('foo') getterFoo
#Action('foo') actionFoo
#Mutation('foo') mutationFoo
#someModule.Getter('foo') moduleGetterFoo
// If the argument is omitted, use the property name
// for each state/getter/action/mutation type
#State foo
#Getter bar
#Action baz
#Mutation qux
created () {
this.stateFoo // -> store.state.foo
this.stateBar // -> store.state.bar
this.getterFoo // -> store.getters.foo
this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
this.moduleGetterFoo // -> store.getters['path/to/module/foo']
}
}
This is there example and it's really nice because were able to take a namespaced module and call all of it's getters and setters without any nasty custom functions and we can import all of that ready to use into a const like above. Now you'd have access to all of your modules functionality using just decorators. This is as close as it really gets to being able to assign your functionality into the component sadly, but it looks pretty nice once you've got it all setup. You can do this with or without TS I think but I've always done it in TS as it has the first class support for the vue class components which are still relatively new.

Related

Is there a way to require in multiple modules into a file, which is then exported again as a single module?

Currently working with Redux and was wondering if there was a way to require in multiple modules into a single file, which is then exported again as a single module?
For example, in my actions/bookmark.js I group all actions related to bookmarks accordingly:
module.exports = {
fetchBookmarkList: () => {
return {
type: 'FETCH_LIST'
}
},
fetchBookmark: () => {
return {
type: 'FETCH_BOOKMARK'
}
}
}
Then in my actions/index.js file, I require in all groups of actions (which will include bookmark actions as well as others). Then I would like to export the entire file as a single module.
Schematically I had something like this in mind (obviously this code does not work):
actions/index.js:
module.exports = {
require('./bookmark');
require('./tags');
}
The reason that I want to do this is so that I only have to import a single action file that contains all my actions (i.e. the actions/index.js file):
Example component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
class BookmarkList extends Component {
constructor(props) {
super(props);
this.props.fetchBookmarkList();
}
render() {
return (
<div></div>
);
}
}
export default connect(null, actions)(BookmarkList);
I see that you es6 modules syntax in components then, why not in your redux files?
export const fetchBookmarkList = () => {
return {
type: 'FETCH_LIST'
}
};
export const fetchBookmark = () => {
return {
type: 'FETCH_BOOKMARK'
}
}
Based on this reference I think it could be possible to re-export everything like this:
export * from './bookmark'
export * from './tags;
But haven't tried it and I could be wrong.
I would NOT recommend you to pass all the actions to mapDispatchToProps, only the actions needed by your component
But you could just use in actions/index.js:
Using the spread operator:
module.exports = {
...require('./bookmark');
...require('./tags');
}
And in ES6 syntax:
actions/index.js:
export * from './bookmark'
export * from './tags'

How can I extend a "connected" component?

My situation is, I have the Navigation component, which is the base, and is listening to the Navigations state(Redux). It should be extended to HorizontalNavigation and VerticalNavigation, for easy reusable code in the future.
My problem is, right now I already have the "final" version of Navigation.jsx and I can extend it, as a class, but can't override it's methods. It triggers the super(Navigation) method and not the final one. I need to override the methods in Horizontal or Vertical components.
There is no code erros on console, so it isn't something breaking, but that I don't know how to handle how to extend it.
Navigation.jsx
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import { itemAction, stageAction } from 'Store/Actions/Actions';
class Navigation extends Component {
// ACTIONS
leftAction () {
this.onLeftAction();
}
onLeftAction () {}
rightAction () {
this.onRightAction();
}
onRightAction () {}
downAction () {
this.onDownAction();
}
onDownAction () {}
upAction () {
this.onUpAction();
}
onUpAction () {}
// STAGES
nextStage (slug) {
this.goToStage(slug);
}
previousStage (slug) {
this.goToStage(slug);
}
goToStage (slug) {
// Just for illustration purpose
// let { dispatch } = this.props;
// dispatch(stageAction(slug));
}
// ITEMS
nextItem (index) {
this.goToItem(index);
}
previousItem (index) {
this.goToItem(index);
}
goToItem (index) {
// Just for illustration purpose
// let { dispatch } = this.props;
// dispatch(itemAction(index));
}
render () {
return ();
}
}
function mapStateToProps (state, props) {
navigation: state.Navigations[props.slug]
}
export default connect(mapStateToProps)(Navigation);
Horizontal.jsx
import React from 'react';
import Navigation from 'Components/Navigation/Navigation';
class HorizontalNavigation extends Navigation {
onLeftAction (previousItemIndex) {
this.previousItem(previousItemIndex);
}
onRightAction (nextItemIndex) {
this.nextItem(nextItemIndex);
}
onTopAction (slug) {
this.previousStage(slug);
}
onDownAction (slug) {
this.nextStage(slug);
}
}
export default HorizontalNavigation;
The VerticalNavigation would be the opposite. Left and right for stage; up and down for items.
I don't want to reuse the Navigation component each time I could use Horizontal or Vertical, and rewrite the same exact logic over and over again.
I'm using the Higher-Order Component pattern, exporting a function to connect the extended component, eg:
import { connect as reduxConnect } from 'react-redux'
...
export class Navigation extends Component{
...
export function connect(Component){
return reduxConnect(
(state, props)=>({...})
)(Component);
}
export default connect(Navigation)
And in the Horizontal.jsx you could do
import { Navigation, connect } from './Navigation';
class Horizontal extends Navigation{
...
export default connect(Horizontal);
This way, you keep the connect(mapStateToProps) in one place.
This is a fun one. At the bottom of Navigation, you're exporting the connecting component, which in essence is exporting the class created in connect, which is not the same class as Navigation. So, when you extend the default exported class, you're actually extending the connect class. That's a mouthful.
To get this to work, you could also export your class (in addition to export default connect(mapStateToProps)(Navigation); at the bottom:
export class Navigation extends Component {
Then to extend it, you can do:
import { Navigation } from './Navigation';
class Horizontal extends Navigation {
// ...
However, you would also need connect the Horizontal component as well in order to get the right props from redux.
If you don't want to use connect, you could take in props to your Navigation component that changes how those up/down/left/right actions work, then you could create a Horizontal/Vertical component that passes in the right props. Something like:
class Horizontal extends React.Component {
constructor(props, context) {
super(props, context);
this.onUp = this.onUp.bind(this);
this.onDown = this.onDown.bind(this);
this.onLeft = this.onLeft.bind(this);
this.onRight = this.onRight.bind(this);
}
onUp() {}
onDown() {}
onLeft() {}
onRight() {}
render() {
return (
<Navigation onUp={this.onUp} onDown={this.onDown} onLeft={this.onLeft} onRight={this.onRight} />
);
}
);

Get redux state on window.scroll

I want to implement pagination. So when a user scrolls down to the bottom I want to make an api call. I see through window.scroll I can find position of scroll and can achieve that. However I want to access redux state to get certian data. Since this event is not bind by any component I won't be able to pass down data. What would be the correct approach in this scenario?
If I want to access redux store through a simple function How can I do that? And on scroll how do I make sure that only request goes through?
You can connect your component that does the scroll. or you can pass props to the component that have the store information. Those are the two recommended ways to reach your store. That being said you can also look at the context
class MyComponent extends React.Component {
someMethod() {
doSomethingWith(this.context.store);
}
render() {
...
}
}
MyComponent.contextTypes = {
store: React.PropTypes.object.isRequired
};
Note: Context is opt-in; you have to specify contextTypes on the component to get it.
Read up on React's Context doc It may not be a complete solution since it could be deprecated in a future version
Edit:
Per the comments with the clarity you provided you can just do this.
import React, { Component } from 'react';
import ReactDOM = from 'react-dom';
import _ from 'lodash';
const defaultOffset = 300;
var topOfElement = function(element) {
if (!element) {
return 0;
}
return element.offsetTop + topOfElement(element.offsetParent);
};
class InfiniteScroll extends Component {
constructor(props) {
super(props);
this.listener = _.throttle(this.scrollListener, 200).bind(this);
}
componentDidMount() {
this.attachScrollListener();
}
componentWillUnmount() {
this.detachScrollListener();
}
scrollListener () {
var el = ReactDOM.findDOMNode(this);
var offset = this.props.offset || defaultOffset;
var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
if (topOfElement(el) + el.offsetHeight - scrollTop - window.innerHeight < offset) {
this.props.somethingHere;
}
}
attachScrollListener() {
window.addEventListener('scroll', this.listener);
window.addEventListener('resize', this.listener);
this.listener();
}
detachScrollListener() {
window.removeEventListener('scroll', this.listener);
window.removeEventListener('resize', this.listener);
}
render() {
return (...)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(InfiniteScroll);
I added lodash to the import here so you can throttle the scroll listener function. you only want to call the handler function so many times a second or it can start lagging the page (depending on how heavy the listener function is)
The correct way to access your application state in components is the usage of react-redux and selectors functions.
react-redux provides a function which is called connect. You should use this function to define which values from our state you want to map to the props of the component so these will be available.
The function you need for this mapping is called mapStateToPropswhich returns an object with the values which should be passed to the component.
Also you can be define redux actions which should be made available in the component (e.g. for trigger the load of the next page). The function is called mapDispatchToProps.
Here an example:
import React from 'react';
import { connect } from 'react-redux';
import { getUsersPage } from './selectors';
import { loadUsersPage } from './actions';
class MyComponent extends React.Component {
handleScroll () {
this.props.loadUsersPage({ page: lastPage + 1 });
}
render () {
const users = this.props.users;
// ...
}
}
const mapStateToThis = (state) => {
return {
users: getUsers(state)
};
}
const mapDispatchToProps = (dispatch) => {
return {
loadUsersPage: (payload) => {
dispatch (loadUsersPage(payload));
}
}
};
export default connect()(MyComponent);

Which way should I use for Connector in Redux?

I seen 2 ways of doing the same thing but I am not sure what is the proper way.
Component
import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {selectUser} from '../actions/index'
class UserList extends Component {
renderList() {
return this.props.users.map((user) => {
return (
<li
key={user.id}
onClick={() => this.props.selectUser(user)}
>
{user.first} {user.last}
</li>
);
});
}
render() {
return (
<ul>
{this.renderList()}
</ul>
);
}
}
// Get apps state and pass it as props to UserList
// > whenever state changes, the UserList will automatically re-render
function mapStateToProps(state) {
return {
users: state.users
};
}
// Get actions and pass them as props to to UserList
// > now UserList has this.props.selectUser
function matchDispatchToProps(dispatch){
return bindActionCreators({selectUser: selectUser}, dispatch);
}
// We don't want to return the plain UserList (component) anymore, we want to return the smart Container
// > UserList is now aware of state and actions
export default connect(mapStateToProps, matchDispatchToProps)(UserList);
https://github.com/buckyroberts/React-Redux-Boilerplate
Or
import React from "react"
import { connect } from "react-redux"
import { fetchUser } from "../actions/userActions"
import { fetchTweets } from "../actions/tweetsActions"
#connect((store) => {
return {
user: store.user.user,
userFetched: store.user.fetched,
tweets: store.tweets.tweets,
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
}
fetchTweets() {
this.props.dispatch(fetchTweets())
}
render() {
const { user, tweets } = this.props;
if (!tweets.length) {
return <button onClick={this.fetchTweets.bind(this)}>load tweets</button>
}
const mappedTweets = tweets.map(tweet => <li>{tweet.text}</li>)
return <div>
<h1>{user.name}</h1>
<ul>{mappedTweets}</ul>
</div>
}
}
https://github.com/learncodeacademy/react-js-tutorials/tree/master/5-redux-react
The first way uses 2 different functions mapStateToProps() and matchDispatchToProps() while the other way uses #connect(....).
When I use the #connect I get a whole bunch of warnings saying that it has not been finalized and might change.
The # symbol is a decorator which is still considered experimental. So I would use that at your own risk. Your first code block is the safer way to do it as described in the official docs. Both blocks essentially do the same thing but decorators are more sugar than anything.
References:
https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
What's the '#' (at symbol) in the Redux #connect decorator?
I think the first method will give you less problems in the end. Someone else can chime in though too.
The answer by Jackson is right in every sense however he is missing out the importance of using the first version for the usage of unit testing. If you want to be able to unit test a component (which usually means testing with the unconnected version) you need to be able to export the connected and unconnected component.
Using your example and assuming you are using jest/enzyme you could do something like this:
// notice importing the disconnected component
import { UserList } from '../relative/file/path/UserList'
import { mount } from 'enzyme'
describe('UserList', () => {
it('displays the Username', () => {
const users = [{fist: 'Person', last: 'Thing'}, ... ]
const UserList = mount(<UserList users={users} />)
export(UserList.find('li')[0].text()).toEqual('Person Thing')
});
});
Once you build larger projects being able to unit test will provide sanity to your coding life. Hope this helps

Angular2: Injecting a provider into a component using a custom decorator or annotation?

I'm hiding tabs in Ionic 2 for certain #Pages (an Ionic 2 decorator) using a simple TabsProvider:
tabs.ts
import { Injectable } from 'angular2/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class TabsProvider {
currentState = new BehaviorSubject<boolean>(true);
public showTabs(){
this.currentState.next(true);
}
public hideTabs(){
this.currentState.next(false);
}
}
The tabs component subscribes to currentState, and TabsProvider is injected into various pages as below:
sample-page.ts:
import {Page} from 'ionic-angular';
import { TabsProvider } from './tabs';
#Page({
...
})
export class SamplePage {
tabsProvider: TabsProvider;
constructor(tabsProvider: TabsProvider) {
this.tabsProvider = tabsProvider;
}
onPageWillEnter(){
this.tabsProvider.hideTabs();
}
onPageWillLeave(){
this.tabsProvider.showTabs();
}
}
This code is practically all boilerplate, and would be much cleaner if I could define this functionality in a decorator (or annotation), e.g.:
import { Page } from 'ionic-angular';
import { hideTabs } from './tabs';
#hideTabs()
#Page({
...
})
export class BuyPage {
}
But I'm having trouble determining how to inject TabsProvider and add the onPageWillEnter and onPageWillLeave methods to SamplePage.
Can a decorator (or annotation) somehow inject additional Angular providers?
The farthest I've gotten so far:
in tabs.ts:
export function hideTabs() {
return function(cls: any) {
cls.prototype.onPageWillEnter = function() {
this.tabsProvider.hideTabs();
};
cls.prototype.onPageWillLeave = function() {
this.tabsProvider.showTabs();
};
return cls;
}
}
This gets us part of what we're looking for, but it's still necessary to import and inject TabsProvider as a specific instance member:
sample-page.ts
import {Page, Events} from 'ionic-angular';
import { hideTabs, TabsProvider } from './tabs';
#hideTabs()
#Page({
...
})
export class SamplePage {
constructor(public tabsProvider: TabsProvider) {
}
}
Is it possible to fully abstract this into #hideTabs()?
Edit:
Relevant parts of the tabs component (for anyone interested in implementing) pages/tabs/tabs.ts:
import { Page } from 'ionic-angular';
import { TabsProvider } from './tabs';
#Page({
...
})
export class TabsPage {
...
currentState: boolean;
constructor(TabsProvider: TabsProvider) {
TabsProvider.currentState.subscribe((state: boolean) => {
this.currentState = state;
});
}
}
pages/tabs/tabs.html:
<div [ngClass]="{'hide-tabs': !currentState}">
<ion-tabs>
...
</ion-tabs>
</div>
pages/tabs/tabs.scss:
.hide-tabs ion-tabbar-section {
display: none;
}
I have just had the same problem, and this is the solution I came up with. The general approach is to:
Use Reflect.getOwnMetadata to read the existing dependencies from the class.
Add to the list of dependencies
Create a new constructor which wraps the old constructor and deals with these dependencies.
tabs.ts:
import TabsProvider from "./tabs";
export function hideTabs() {
return function(cls: Function) : any {
// Save existing onPageWillEnter and onPageWillLeave inplementations
let enter = cls.prototype.onPageWillEnter || function () {};
let leave = cls.prototype.onPageWillLeave || function () {};
// Create new constructor for class
const newCls = function (tabsProvider, ...args) {
this.__hideTabs__tabsProvider = tabsProvider;
return cls.apply(this, args);
}
// Copy prototype to new constructor
newCls.prototype = constructor.prototype;
// Copy metadata to new constructor
let metadatakeys = Reflect.getMetadataKeys(cls);
metadatakeys.forEach(function (key) {
Reflect.defineMetadata(key, Reflect.getOwnMetadata(key, cls), newCls)
});
// Read dependencies list from 'cls', add our own dependency, and write list to 'newCls'
let dependencies = Reflect.getOwnMetadata('design:paramtypes', cls);
dependencies = [TabsProvider].concat(dependencies)
Reflect.defineMetadata('design:paramtypes', params, newCls);
// Define new onPageWillEnter and onPageWillLeave implementation which implement tab show/hide functionality
// and also call the old implementation saved above (if they exist)
newCls.prototype.onPageWillEnter = function() {
this.tabsProvider.hideTabs();
enter.apply(this, arguments);
};
newCls.prototype.onPageWillLeave = function() {
this.tabsProvider.showTabs();
leave.apply(this, arguments);
};
return newCls;
}
}
I would also recommend moving the #hideTabs decorator below the #Page decorator

Resources