Alpine JS global function not defined - alpine.js

I am using Alpine JS store() to make a global function available to toggle a modal status.
Module:
export default () => ({
/**
* #property isOpen - controls the state of the Contact Form Modal
*/
isOpen: false,
open() {
this.isOpen = true
},
close() {
this.isOpen = false
},
});
Then I use it like:
import modalContactForm from './alpine/modal-contact-form';
// Alpine.store sets data for GLOBAL usage
Alpine.store('modalContactForm', modalContactForm);
Alpine.start();
And with this HTML:
<button x-data #click="$store.modalContactForm.open()"></button>
But I get:
Alpine Expression Error: $store.modalContactForm.open is not a function
And I don't know how to debug this situation.
Interestingly, if I pass the object directly into Alpine.store(), it works.
Alpine.store('modalContactForm', {
isOpen: false,
open() {
this.isOpen = true
},
close() {
this.isOpen = false
},
});
Alpine.start();

In the external module file export the store as a simple object, not as an anonymous function:
const modalContactForm = {
/**
* #property isOpen - controls the state of the Contact Form Modal
*/
isOpen: false,
open() {
this.isOpen = true
},
close() {
this.isOpen = false
},
}
export default modalContactForm

Related

TypeScriptError: Type 'Data' is not assignable to type 'string'

I am using React-typescript for my app. for state management I am using Redux-toolkit. I am fetching one open api and store it my redux store. I created dispatch function. From the component when I click the dispatch function then it will display random dog image. But the problem is after mapping the when I am using this img src. I am getting typescript error: Type 'Data' is not assignable to type 'string'. I don't know what I am doing wrong. i uploaded my code in codesandbox, although it works in codesandbox but does not work in my app.
Ps. I did not upload my store setup code because it works find ☺️.
This is my reducer
/* eslint-disable #typescript-eslint/indent */
import { createSlice, PayloadAction } from '#reduxjs/toolkit';
import { AppThunk } from "store/store";
interface IMeta {
loading: boolean;
error: boolean;
message: string;
}
interface Data {
src: string;
}
interface IDogs {
meta: IMeta;
dogs: Data[];
}
const initialState: IDogs = {
"meta": {
"loading": false,
"error": false,
"message": ``
},
"dogs": []
};
const dogSlice = createSlice({
"name": `random-dogs`,
initialState,
"reducers": {
loadState(state) {
state.meta = {
"loading": true,
"error": false,
"message": ``
};
state.dogs = [];
},
fetchData(state, action: PayloadAction<Data[]>) {
state.meta.loading = false;
state.dogs = action.payload;
console.log(`dogs`, action.payload);
},
loadFailed(state, action: PayloadAction<string>) {
state.meta = {
"loading": false,
"error": true,
"message": action.payload
};
state.dogs = [];
}
}
});
export const { loadState, fetchData, loadFailed } = dogSlice.actions;
export default dogSlice.reducer;
export const fetchDogs = (): AppThunk => async (dispatch) => {
const url = `https://dog.ceo/api/breeds/image/random/5`;
try {
dispatch(loadState);
const response = await fetch(url);
const data = await response.json();
console.log(data);
console.log(data.message);
const singleData = data.message.map((i) => i);
dispatch(fetchData(singleData));
} catch (error) {
dispatch(loadFailed(`dogs are unavailable`));
console.log({ error });
}
};
This is the component I am using the redux store
import React, { memo } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchDogs } from 'store/dogs';
import { RootState } from 'store/combineReducer';
export default memo(() => {
const state = useSelector((rootState: RootState) => ({
"dogs": rootState.fetchDogs.dogs,
"meta": rootState.fetchDogs.meta
}));
const dispatch = useDispatch();
console.log(`Dog component`, state.dogs[0]);
return (
<div>
{state.meta.loading ? <p>loading....</p> :
state.dogs.map((i, index) =>
<div key={index}>
<ul>
<li>{i}</li> // I can see the strings
</ul>
<img style={{ "width": 50, "height": 50 }} src={i} /> //getting error in here
</div>)}
<br></br>
<button onClick={() => dispatch(fetchDogs())}> display random dogs</button>
</div>
);
});
The situation is as follows:
Interface IDog is has a property "dogs" of type Data[].
Data has a property "src" of type String.
Src attribute of an img needs to be a string.
You are now passing IDogs.dogs. You need to go deeper to IDogs.dogs.src to get the source string you want.
So line 25 of App.tsx should look like this and all seems to work fine:
<img style={{ width: 50, height: 50 }} src={i.src} alt="dog" />
PS: The codesandbox example still works as it apparently does some kind of assumption that you want the src property, but as you see you still get the error.
EDIT: After some fiddling the answer is as below. It is however connected to what was written above.
I downloaded you project and tried to run in npm on my PC. I did 2 things to make it work:
I updated line 25 to use the cast: src={String(i)}
I updated react-scripts. See this thread for reference: TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app

Get only distinct values from an ngrx store selector

I have a function which checks whether a grid has loaded or not and if not it triggers the loading. But currently this ends up firing several times for the same Loaded value so it will call the relevant action multiple times. I was under the impression that store selectors emit by default only distinct (changed) values?
My function
private gridLoaded(filters: FilteredListingInput): Observable<boolean> {
return this.settings.states.Loaded.pipe(
tap(loaded => {
this.logService.debug(
`Grid ID=<${
this.settings.id
}> this.settings.states.Loaded state = ${loaded}`
);
// Now we get duplicate firings of this action.
if (!loaded) {
this.logService.debug(
`Grid Id=<${
this.settings.id
}> Dispatching action this.settings.stateActions.Read`
);
this.store.dispatch(
new this.settings.stateActions.Read(filters)
);
}
}),
filter(loaded => loaded),
take(1)
);
}
this.settings.states.Loaded is selector from NgRx store.
The logging output I get looks like this:
Grid ID=<grid-reviewItem> this.settings.states.Loaded state = false {ignoreIntercept: true}
Grid Id=<grid-reviewItem> Dispatching action this.settings.stateActions.Read {ignoreIntercept: true}
Grid ID=<grid-reviewItem> this.settings.states.Loaded state = true {ignoreIntercept: true}
Grid ID=<grid-reviewItem> Calling FilterClientSide action. Loaded=true {ignoreIntercept: true}
Grid ID=<grid-reviewItem> this.settings.states.Loaded state = true {ignoreIntercept: true}
Grid ID=<grid-reviewItem> Calling FilterClientSide action. Loaded=true {ignoreIntercept: true}
Grid ID=<grid-reviewItem> this.settings.states.Loaded state = true {ignoreIntercept: true}
Grid ID=<grid-reviewItem> Calling FilterClientSide action. Loaded=true {ignoreIntercept: true}
How can I make sure that the relevant actions are triggered only once?
Edit - updates
Selector code:
export const getReviewItemsLoaded = createSelector(
getReviewItemState,
fromReviewItems.getReviewItemsLoaded
);
export const getReviewItemState = createSelector(
fromFeature.getForecastState,
(state: fromFeature.ForecastState) => {
return state.reviewItems;
}
);
export const getReviewItemsLoaded = (state: GridNgrxState<ReviewItemListDto>) =>
state.loaded;
export interface GridNgrxState<TItemListDto> {
allItems: TItemListDto[];
filteredItems: TItemListDto[];
totalCount: number;
filters: FilteredListingInput;
loaded: boolean;
loading: boolean;
selectedItems: TItemListDto[];
}
As you can see we are just getting the state.loaded property, it's a trivial selector.
Reducers that change the loading property:
export function loadItemsSuccessReducer(state: any, action: GridAction) {
const data = action.payload;
return {
...state,
loading: false,
loaded: true,
totalCount: data.totalCount ? data.totalCount : data.items.length,
allItems: data.items
};
}
export function loadItemsReducer(state: any, action: GridAction) {
return {
...state,
loading: true,
filters: action.payload
};
}
export function loadItemsFailReducer(state: any, action: GridAction) {
return {
...state,
loading: false,
loaded: false
};
}
Actions
export class LoadReviewItemsAction implements Action {
readonly type = LOAD_REVIEWITEMS;
constructor(public payload?: FilteredListingInput) {}
}
export class LoadReviewItemsFailAction implements Action {
readonly type = LOAD_REVIEWITEMS_FAIL;
constructor(public payload: any) {}
}
export class LoadReviewItemsSuccessAction implements Action {
readonly type = LOAD_REVIEWITEMS_SUCCESS;
constructor(public payload: PagedResultDtoOfReviewItemListDto) {}
Effects
export class ReviewItemsEffects {
constructor(
private actions$: Actions,
private reviewItemApi: ReviewItemApi
) {}
#Effect()
loadReviewItems$ = this.actions$
.ofType(reviewItemActions.LOAD_REVIEWITEMS)
.pipe(
switchMap((action: reviewItemActions.LoadReviewItemsAction) => {
return this.getDataFromApi(action.payload);
})
);
/**
* Retrieves and filters data from API
*/
private getDataFromApi(filters: FilteredListingInput) {
return this.reviewItemApi.getReviewItems(filters || {}).pipe(
map(
reviewItems =>
new reviewItemActions.LoadReviewItemsSuccessAction(
reviewItems
)
),
catchError(error =>
of(new reviewItemActions.LoadReviewItemsFailAction(error))
)
);
}
}
I was able to work around the issue by refactoring the gridLoaded method into waitForGridLoaded and moving some of its logic outside of it. This works well but I couldn't solve the original issue of why the tap(loaded => ...) logic is triggered many times.
Now the relevant bits look like this (it doesn't feel like the nicest solution):
private initializeLoadingState() {
const loadingStateSubscription = this.settings.states.Loading.subscribe(
loading => {
this.loading = loading;
}
);
this.addSubscription(loadingStateSubscription);
}
private initializeLoadedState() {
const loadedStateSubscription = this.settings.states.Loaded.subscribe(
loaded => {
this.loaded = loaded;
}
);
this.addSubscription(loadedStateSubscription);
}
onLazyLoad(event: LazyLoadEvent) {
// Do nothing yet if we are expecting to set parent filters
// but we have not provided any parent filter yet
if (
this.settings.features.ParentFilters &&
(!this.parentFiltersOnClient ||
!this.parentFiltersOnClient.length) &&
(!this.parentFiltersOnServer || !this.parentFiltersOnServer.length)
) {
return;
}
this.loadAndFilterItems(event);
}
private loadAndFilterItems(event: LazyLoadEvent) {
if (this.settings.features.ClientSideCaching) {
if (this.loaded) {
// Load only once and filter client side
this.store.dispatch(
new this.settings.stateActions.FilterClientSide(
this.buildFilters(event, GridParentFilterTypes.Client)
)
);
} else if (!this.loading) {
// Start loading in from server side
this.store.dispatch(
new this.settings.stateActions.Read(
this.buildFilters(event, GridParentFilterTypes.Server)
)
);
// When we have finished loading, apply any client side filters
const gridLoadedSubscription = this.waitForGridLoaded().subscribe(
loaded => {
if (loaded) {
this.store.dispatch(
new this.settings.stateActions.FilterClientSide(
this.buildFilters(
event,
GridParentFilterTypes.Client
)
)
);
}
}
);
this.addSubscription(gridLoadedSubscription);
}
} else {
this.store.dispatch(
new this.settings.stateActions.Read(
this.buildFilters(event, GridParentFilterTypes.Server)
)
);
}
}
private waitForGridLoaded(): Observable<boolean> {
return this.settings.states.Loaded.pipe(
filter(loaded => loaded),
take(1)
);
}

ReactJS pass props to child via redux ajax

I have a reactjs component with redux which passes asynchronously props to child component.
In child component I try to catch the data in componentDidMount but somehow does not work either, however the child component is getting rendered.
This is my parent component
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as slidesActions from '../../actions/slidesActions';
import Slider from '../Partials/Slider'
import _ from 'underscore';
class HomePage extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.actions.getSlides()
}
componentWillMount() {
const {slides} = this.props;
}
render() {
const {slides} = this.props;
return (
<div className="homePage">
<Slider columns={1} slides={slides} />
</div>
);
}
}
function mapStateToProps(state) {
return {
slides: state.slides
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(slidesActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(HomePage);
here comes my child component where I try to get passed slides props but is empty
import React from 'react';
import _ from 'underscore';
import Hammer from 'hammerjs';
class Slider extends React.Component {
constructor(props) {
super(props)
this.updatePosition = this.updatePosition.bind(this);
this.next = this.next.bind(this);
this.prev = this.prev.bind(this);
this.state = {
images: [],
slidesLength: null,
currentPosition: 0,
slideTransform: 0,
interval: null
};
}
next() {
const currentPosition = this.updatePosition(this.state.currentPosition - 10);
this.setState({ currentPosition });
}
prev() {
//TODO: work on logic
if( this.state.currentPosition !== 0) {
const currentPosition = this.updatePosition(this.state.currentPosition + 10);
this.setState({currentPosition});
}
}
componentDidMount() {
//here I try set a state variable on slides
let {slides} = this.props
let slidesLength = slides.length
this.setState({slidesLength})
this.hammer = Hammer(this._slider)
this.hammer.on('swipeleft', this.next);
this.hammer.on('swiperight', this.prev);
}
componentWillUnmount() {
this.hammer.off('swipeleft', this.next)
this.hammer.off('swiperight', this.prev)
}
updatePosition(nextPosition) {
const { visibleItems, currentPosition } = this.state;
return nextPosition;
}
render() {
let {slides, columns} = this.props
let {currentPosition} = this.state
let sliderNavigation = null
//TODO: this should go to slides actions
let slider = _.map(slides, function (slide) {
let Background = slide.featured_image_url.full;
if(slide.status === 'publish')
return <div className="slide" id={slide.id} key={slide.id}><div className="Img" style={{ backgroundImage: `url(${Background})` }} data-src={slide.featured_image_url.full}></div></div>
});
if(slides.length > 1 ) {
sliderNavigation = <ul className="slider__navigation">
<li data-slide="prev" className="" onClick={this.prev}>previous</li>
<li data-slide="next" className="" onClick={this.next}>next</li>
</ul>
}
return <div ref={
(el) => this._slider = el
} className="slider-attached"
data-navigation="true"
data-columns={columns}
data-dimensions="auto"
data-slides={slides.length}>
<div className="slides" style={{ transform: `translate(${currentPosition}%, 0px)`, left : 0 }}> {slider} </div>
{sliderNavigation}
</div>
}
}
export default Slider;
and here I have my actions for slider
import * as types from './actionTypes';
import axios from 'axios';
import _ from 'underscore';
//TODO: this should be accessed from DataService
if (process.env.NODE_ENV === 'development') {
var slidesEndPoint = 'http://dev.server/wp-json/wp/v2/slides';
} else {
var slidesEndPoint = 'http://prod.server/wp-json/wp/v2/slides';
}
export function getSlides () {
return dispatch => {
dispatch(setLoadingState()); // Show a loading spinner
axios.get(slidesEndPoint)
.then(function (response) {
dispatch(setSlides(response.data))
dispatch(doneFetchingData(response.data))
})
/*.error((response) => {
dispatch(showError(response.data))
})*/
}
}
function setSlides(data) {
return {
type: types.SLIDES_SUCCESS,
slides: data
}
}
function setLoadingState() {
return {
type: types.SHOW_SPINNER,
loaded: false
}
}
function doneFetchingData(data) {
return {
type: types.HIDE_SPINNER,
loaded: true,
slides: data
}
}
function showError() {
return {
type: types.SHOW_ERROR,
loaded: false,
error: 'error'
}
}
Reason is, componentDidMount will get called only once, just after the initial rendering, since you are fetching the data asynchronously so before you get the data Slider component will get rendered.
So You need to use componentwillreceiveprops lifecycle method.
componentDidMount:
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. Setting state in this method will
trigger a re-rendering.
componentWillReceiveProps:
componentWillReceiveProps() is invoked before a mounted component
receives new props. If you need to update the state in response to
prop changes (for example, to reset it), you may compare this.props
and nextProps and perform state transitions using this.setState() in
this method.
Write it like this:
componentWillReceiveProps(nextProps){
if(nextProps.slides){
let {slides} = nextProps.props
let slidesLength = slides.length;
this.hammer = Hammer(this._slider)
this.hammer.on('swipeleft', this.next);
this.hammer.on('swiperight', this.prev);
this.setState({slidesLength})
}
}
As far as I understand, you are doing an axios call to fetch the data and then set it in the reducer which you are returning later. Also initially reducer data is empty . Now since componentDidMount is called only once, and initially no data may have been there you are not seeing any values. Use a componentWillReceiveProps function
componentWillReceiveProps(nextProps) {
//here I try set a state variable on slides
let {slides} = nextProps
let slidesLength = slides.length
this.setState({slidesLength})
this.hammer = Hammer(this._slider)
this.hammer.on('swipeleft', this.next);
this.hammer.on('swiperight', this.prev);
}

Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfront in the data option.

I am a bit confused using VueJS2. I added a few variables to the data container for sending it to my API. That works fine but Vue is throwing me a warning/error message which I don't know how to solve:
Avoid adding reactive properties to a Vue instance or its root $data
at runtime - declare it upfront in the data option.
var app = new Vue({
el: '#app',
data: {
incidentReference: '',
streetName: '',
latitude: '',
longitude: '',
featureTypeId: 1,
archived: 0
},
computed: {
href() {
return '#' + this.name.toLowerCase().replace(/ /g, '-');
}
},
mounted: function () {
this.getIncidents();
},
methods: {
onSubmit() {
axios.post('/api/v1/incidents', this.$data)
.then(response => alert('Success'))
.catch(error => {
console.log(error.response);
})
},
getIncidents: function() {
console.log('getIncidents');
var self = this;
axios.get('/api/v1/incidents').then(function(response) {
// set data on vm
console.log(response.data);
var incidentsReceived = response.data.data.map(function (incident) {
return incident;
});
Vue.set(self, 'incidents', incidentsReceived);
});
}
}
});
You're creating a new reactive property on the response of your API
Vue.set(self, 'incidents', incidentsReceived);
Not sure if you misspelled property's name or forget to create that property. Just use an existing property on you data section
Vue.set(self, 'incidentReference', incidentsReceived); //change property name
or
data: {
incidents: null, //or create this property
},
In my case during unit testing using Jest, I was setting selected but didn't have on component so got this error.
wrapper.setData({
selected: recipients,
});
So created the property on component and then it's working fine.
In the context of Jest & Vue Test Utils consider declaring data in component:
const Component = {
// ..
data() { return { abc: 'abc'; } }
};
const wrapper = mount(Component, { /*..*/ });
instead of
const Component = { /*..*/ };
const wrapper = mount(Component, { /*..*/ });
wrapper.setData({ abc: 'abc' });
await wrapper.vm.$nextTick();

After closing the modal dialog refresh the base view

suggestion and code sample
I am new to Backbone marionette, I have a view ("JoinCommunityNonmemberWidgetview.js") which opens a modal dialog ("JoinCommunityDetailWidgetview.js").On closing of the dialog ( I want the view JoinCommunityNonmemberWidgetview.js") to be refreshed again by calling a specific function "submitsuccess" of the view JoinCommunityNonmemberWidgetview.js.
How can I achieve it.
The code for the modal is as below:
define(
[
"grads",
"views/base/forms/BaseFormLayout",
"models/MembershipRequestModel",
"require.text!templates/communitypagewidget/JoinCommunityWidgetDetailTemplate.htm",
],
function (grads, BaseFormLayout, MembershipRequestModel, JoinCommunityWidgetDetailTemplate) {
// Create custom bindings for edit form
var MemberDetailbindings = {
'[name="firstname"]': 'FirstName',
'[name="lastname"]': 'LastName',
'[name="organization"]': 'InstitutionName',
'[name="email"]': 'Email'
};
var Detailview = BaseFormLayout.extend({
formViewOptions: {
template: JoinCommunityWidgetDetailTemplate,
bindings: MemberDetailbindings,
labels: {
'InstitutionName': "Organization"
},
validation: {
'Email': function (value) {
var emailconf = this.attributes.conf;
if (value != emailconf) {
return 'Email message and Confirm email meassage should match';
}
}
}
},
editViewOptions: {
viewEvents: {
"after:render": function () {
var self = this;
var btn = this.$el.find('#buttonSubmit');
$j(btn).button();
}
}
},
showToolbar: false,
editMode: true,
events: {
"click [data-name='buttonSubmit']": "handleSubmitButton"
},
beforeInitialize: function (options) {
this.model = new MembershipRequestModel({ CommunityId: this.options.communityId, MembershipRequestStatusTypeId: 1, RequestDate: new Date() });
},
onRender: function () {
BaseFormLayout.prototype.onRender.call(this)
},
handleSubmitButton: function (event) {
this.hideErrors();
// this.model.set({ conf: 'conf' });
this.model.set({ conf: this.$el.find('#confirmemail-textbox').val() });
//this.form.currentView.save();
//console.log(this.form);
this.model.save({}, {
success: this.saveSuccess.bind(this),
error: this.saveError.bind(this),
wait: true
});
},
saveSuccess: function (model, response) {
var mesg = 'You have submitted a request to join this community.';
$j('<div>').html(mesg).dialog({
title: 'Success',
buttons: {
OK: function () {
$j(this).dialog('close');
}
}
});
grads.modal.close();
},
saveError: function (model, response) {
var msg = 'There was a problem. The request could not be processed.Please try again.';
$j('<div>').html(msg).dialog({
title: 'Error',
buttons: {
OK: function () {
$j(this).dialog('close');
}
}
});
}
});
return Detailview;
}
);
I would use Marionette's event framework.
Take a look at: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.commands.md
Specifically, you need to:
1) Create a marionette application :
App = new Marionette.Application();
2) Use the application to set up event handlers
//Should be somewhere you can perform the logic you are after
App.commands.setHandler('refresh');
3) Fire a 'command' and let marionette route the event
App.execute('refresh');

Resources