aframe state component - pass data from state to component - three.js

I am making use of aframe-state-component to build a game and tracking the game level and play/pause status through the state variables.
I am looking to understand how to pass state data to a component with properties.
Here is my state -
AFRAME.registerState({
initialState: {
score: 0
},
handlers: {
gamePaused: function(state) {
state.gamePaused = true;
},
gameStarted: function(state, event) {
state.gamePaused = false;
state.level = event.source;
}
}
});
and I pass the level to gameStarted handler by emitting an event in my angular controller -
el.emit('gameStarted', {source: levelnumber}, true);
and here is my html
<a-entity bind__model-subset="target: #orca; gamelevel: level"></a-entity>
the component "model-subset" originally accepted only "target" property. I included gamelevel property in there so that I can pass the state variable level to this component. Thereafter I modified, the model-subset component to include the gamelevel property in the schema as shown below -
AFRAME.registerComponent('model-subset', {
schema: {
target: { default: '', type: 'selector' },
gamelevel: { type: 'number'}
},
init: function() {
var data = this.data;
var el = this.el;
console.log("model-subset level is ", data.gamelevel); //this component does not get executed
}
})
But, it errors out and the component code does not get executed. Could you please give me an example of how to pass state data to different components that already have a few properties in their schema?

good question.
In a bind__ component, you only pass state properties, not any real values. Real values go in the component defininition itself. So move target: #orca out into model-subset since that's just a value.
<a-entity bind__model-subset="gamelevel: level" model-subset="target: #orca"></a-entity>

Related

React component not receiving intermediate state when chaining actions in redux-saga

I have two actions TEST and TEST_DONE which both increment an id property in my redux state. I am using redux-saga to dispatch the second action TEST_DONE automatically whenever I dispatch the first action TEST from my component.
I expect the order of execution to go like this:
component renders with initial value of testState.id = 0
component dispatches TEST action
component re-renders with testState.id = 1
saga dispatches the TEST_DONE action
component re-renders with testState.id = 2
Instead my component only re-renders when testState.id is updated to 2. I can't see the 1 value in the getSnapshotBeforeUpdate function. It shows 0 as the previous prop.
Why does the prop jump from 0 to 2 without receiving 1 in between?
saga.js:
export function* TestSagaFunc() {
yield put({
type: actions.TEST_DONE
});
};
export default function* rootSaga() {
yield all([
yield takeEvery(actions.TEST, TestSagaFunc),
]);
};
action.js:
const actions = {
TEST: 'TEST',
TEST_DONE: 'TEST_DONE',
callTest: (id) => ({
type: actions.TEST,
payload: {
id
}
}),
};
export default actions;
reducer.js:
const initState = {
testState: {
id: 0
}
};
export default function TestReducers ( state=initState, { type, ...action}) {
switch(type) {
default:
return state;
case actions.TEST: {
const { id } = state.testState;
const nextId = id + 1;
return {
...state,
testState: {
...state.testState,
id: nextId
}
};
};
case actions.TEST_DONE: {
const { id } = state.testState;
const nextId = id + 1;
return {
...state,
testState: {
...state.testState,
id: nextId
}
};
}
};
};
console output from component getSnapshotBeforeUpdate
Summarizing my comments from the question:
The redux state is indeed being updated as you've seen, but a component is not guaranteed to render every intermediate state change based on the way react batches state changes. To test this you can try importing delay from redux-saga/effects and adding yield delay(1000); before calling yield put in TestSagaFunc so the two state updates don't get batched together.
This is just a trick to illustrate the effects of batching and almost certainly not what you want to do. If you need the intermediate state to be rendered you could dispatch TEST_DONE from the component being rendered with a useEffect (or componentDidUpdate) to ensure that the component went through one render cycle with the intermediate state. But there is no way to force your component to render intermediate reducer states that are batched together.

change local data object on change of global state

I am trying to edit and update using vuex. I have two components, one is form for entry/edit and another for viewing data. When I click on edit button of view component I want to populate the form component with the data from backend.
Note that I already have a data object on form component that is bind with form and used to store data. I want to change it on edit. I have no problem on getting data from backend.
I just can not change the local data object from state
This is my codes what I've tried
Form component:
import { mapState } from 'vuex'
export default {
data(){
return{
form:{
id:'',
name:'',
email:'',
phone:'',
address:''
}
}
},
computed:{
...mapState({
data (state) {
this.form=state.employee; //This is where I am stuck
}
}),
}
state:
export default{
state:{
employee:{}
},
getters:{}
},
mutations:{
setEmployee(state,employee){
state.employee=employee;
}
},
actions:{
fetchEmployee({commit},id){
axios.put('employee-edit/'+id)
.then(res=>res.data)
.then(employee=>{
commit('setEmployee',employee)
})
}
}
}
view component:
export default {
methods:{
editEmployee(id){
this.$store.dispatch('fetchEmployee',id);
}
}
}
Multiple issues/misunderstandings with your approach.
1) computed properties are "passive" and supposed to return a value "computed" from other values. Directly assigning to your local state is probably not what you want to do.
2) HTTP methods: In general the HTTP PUT method replaces the resource at the current URL with the resource contained within the request.
Please read up on http methods and how they are supposed to be used. You want GET
3) mapState is a helper method if you need multiple getters from vuex state store. I suggest you use this.$store.getters.myVariableInState for simple tasks like in your example.
What you probably want is more along these lines:
// store
getters:{
employee: state => state.employee
}
// component
computed: {
employee() {
return this.$store.getters.employee
}
}
If your actions was already dispatched earlier and the data is available in the store all you then need is
methods: {
editEmployee() {
this.form = this.employee
}
}
Since your question stated "change local data on state change", here is an approach for that:
watch your local computed property
watch: {
employee() {
// this.employee (the computed value) now refers to the new data
// and this method is triggered whenever state.employee changes
}
}

reselect across multiple comoponents work only with deepEqual check

I've tested in various ways... Still, It isn't working.
I don't seem to doing anything wrong
exactly same code as reselect doc
redux store is all normalized
reducers are all immutable
From parent component, I just pass down a prop with id and from child component, connected with redux and used selector to get that exact item by id(from parent component)
### This is what Parent components render looks like
render() {
return (
<div>
<h4>Parent component</h4>
{this.props.sessionWindow.tabs.map(tabId =>
<ChildComponentHere key={tabId} tabId={tabId} />
)}
</div>
);
}
### This is what Child component looks like
render() {
const { sessionTab } = this.props (this props is from connect() )
<div>
<Tab key={sessionTab.id} tab={sessionTab} />
</div>
))
}
### Selectors for across multiple components
const getTheTab = (state: any, ownProps: IOwnProps) => state.sessionWindows.sessionTab[ownProps.tabId];
const makeTheTabSelector = () =>
createSelector(
[getTheTab],
(tab: object) => tab
)
export const makeMapState = () => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props)
}
}
return mapStateToProps
}
Weirdly Working solution: just change to deep equality check.(from anywhere)
use selectors with deep equality works as expected.
at shouldComponentUpdate. use _.isEqual also worked.
.
1. const createDeepEqualSelector = createSelectorCreator(
defaultMemoize,
isEqual
)
2. if (!_isEqual(this.props, nextProps) || !_isEqual(this.state, nextState)){return true}
From my understanding, my redux is always immutable so when something changed It makes new reference(object or array) that's why react re-renders. But when there is 100 items and only 1 item changed, only component with that changed props get to re-render.
To make this happen, I pass down only id(just string. shallow equality(===) works right?)using this id, get exact item.(most of the components get same valued input but few component get different valued input) Use reselect to memoize the value. when something updated and each component get new referenced input compare with memoized value and re-render when something trully changed.
This is mostly what I can think of right now... If I have to use _isEqual anyway, why would use reselect?? I'm pretty sure I'm missing something here. can anyone help?
For more clarification.(hopefully..)
First,My redux data structure is like this
sessionWindow: {
byId: { // window datas byId
"windowId_111": {
id: "windowId_111",
incognito: false,
tabs: [1,7,3,8,45,468,35,124] // this is for the order of sessionTab datas that this window Item has
},
"windowId_222": {
id: "windowId_222",
incognito: true,
tabs: [2, 8, 333, 111]
},{
... keep same data structure as above
}
},
allIds: ["windowId_222", "windowId_111"] // this is for the order of sessionWindow datas
}
sessionTab: { // I put all tab datas here. each sessionTab doesn't know which sessionWindow they are belong to
"1": {
id: 1
title: "google",
url: "www.google.com",
active: false,
...more properties
},
"7": {
id: 7
title: "github",
url: "www.github.com",
active: true
},{
...keep same data structure as above
}
}
Problems.
1. when a small portion of data changed, It re-renders all other components.
Let's say sessionTab with id 7's url and title changed. At my sessionTab Reducer with 'SessionTabUpdated" action dispatched. This is the reducer logic
const updateSessionTab = (state, action) => {
return {
...state,
[action.tabId]: {
...state[action.tabId],
title: action.payload.title,
url: action.payload.url
}
}
}
Nothing is broken. just using basic reselect doesn't prevent from other components to be re-rendered. I have to use deep equality version to stop re-render the component with no data changed
After few days I've struggled, I started to think that the problem is maybe from my redux data structure? because even if I change one item from sessionTab, It will always make new reference like {...state, [changedTab'id]: {....}} In the end, I don't know...
Three aspects of your selector definition and usage look a little odd:
getTheTab is digging down through multiple levels at once
makeTheTabSelector has an "output selector" that just returns the value it was given, which means it's the same as getTheTab
In mapState, you're passing the entire props object to theTabSelector(state, props).
I'd suggest trying this, and see what happens:
const selectSessionWindows = state => state.sessionWindows;
const selectSessionTabs = createSelector(
[selectSessionWindows],
sessionWindows => sessionWindows.sessionTab
);
const makeTheTabSelector = () => {
const selectTabById = createSelector(
[selectSessionTabs, (state, tabId) => tabId],
(sessionTabs, tabId) => sessionTabs[tabId]
);
return selectTabById;
}
export const makeMapState() => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props.tabId)
}
}
return mapStateToProps
}
No guarantees that will fix things, but it's worth a shot.
You might also want to try using some devtool utilities that will tell you why a component is re-rendering. I have links to several such tools in the Devtools#Component Update Monitoring section of my Redux addons catalog.
Hopefully that will let you figure things out. Either way, leave a comment and let me know.

Polymer paper-input custom validator issue

I am trying to implement a custom validator for a paper-input. In this particular case, the control should accept positive numbers. However, not only only will the control only accept positive numbers, it will also run some other custom validation logic to determine if the entry falls within a constantly changing (dynamic & calculated) upper and lower limit. Ideally, the paper-input control's error-message text will also change depending on what part of the custom validator check failed.
In the past, I was able to implement this sort of thing with the gold-email-input element. In that case, the control checks for an entry that matches a regular expression for email addresses (i.e. implements a type-check). It also calls a backend api to see if the email address entered (as it is being typed), already exists in a database. If it exists in the database, the control fails validation and updates the control's validation error-message with a custom message. If it does not exist, it passes validation. As you might have imagined by this description, this was for a user registration UI element whereby the provided email should not already exist in the current list of user accounts. Here is an excerpt of that working code below for your reference:
<gold-email-input id="userEmail" label="Email" required auto-validate value="{{userEmail}}" error-message$="{{_getEmailErrorMsg(0)}}" invalid="{{_emailInvalid}}" validator="_validateEmail"></gold-email-input>
<iron-signals on-iron-signal-email-used="_accountFound" on-iron-signal-email-available="_accountNotFound"></iron-signals>
<script>
var emailErrors = ["Provide a valid email address", "Address already used"];
// Register the polymer element
Polymer({
properties: {
userEmail: {type: String, value: null},
validated: {type: Boolean, notify: true}, //overall validity state of entire element
_emailInvalid: {type: Boolean, value: true, observer: "_validityChanged"}, // validity state of email input itself
},
ready: function() {
// Called before attached
this.$.userEmail.validate = this._validateEmail.bind(this);
},
_accountFound: function() {
// Listener function intended to fire when the user email address/account was found
console.log(this.nodeName + " accountFound listener called\n");
this.$.userEmail.errorMessage = this._getEmailErrorMsg(1);
this._emailInvalid = true;
},
_accountNotFound: function() {
// Listener function intended to fire when the user email address/account was not found
console.log(this.nodeName + " accountNotFound listener called\n");
this.$.userEmail.errorMessage = this._getEmailErrorMsg(0);
this._emailInvalid = false;
},
_checkAccountExistance: function() {
if (this.userEmail !== undefined && this.userEmail != null) {
this.$.user.checkEmailAvailability(this.userEmail);
} else {
this._emailInvalid = true;
}
},
_getEmailErrorMsg: function(code) {
if (code !== undefined && code != null) {
return emailErrors[code];
} else {
return "";
}
},
_validateEmail: function() {
// Custom validator function for email input (also checks if email has already been associated to any user accounts)
console.log(this.nodeName + " validateEmail validator called\n");
// Check if proper email address format (W3C Spec Regex used)
var validEntry = /^[a-zA-Z0-9.!#$%&�*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(this.userEmail);
if (validEntry) {
this._emailInvalid = false;
this._checkAccountExistance();
} else {
this._emailInvalid = true;
}
}
_validityChanged: function(newVal, oldVal) {
// set the containing/parent element's overall validity state
this.validated = (!this._nameInvalid && !this._pwInvalid && !this._phoneInvalid && !this._emailInvalid && !this._countryInvalid && !this._regionInvalid && !this._cityInvalid);
},
});
</script>
Now, if I try to implement a similar approach with the paper-input component, it does not work. The custom validator function does not get called at any point. Is there something inherently different with paper-input compared to gold-email-input? Should it not treat validation the same way?
<paper-input id="xpos" label="Horizontal Position" required auto-validate value="{{XPos}}" error-message="Provide the x position" invalid="{{_xInvalid}}" validator="_validatePosition"></paper-input>
<script>
// Register the polymer element
Polymer({
properties: {
xPos: {type: Number},
validated: {type: Boolean, notify: true}, //overall validity state of entire element
_xInvalid: {type: Boolean, value: true, observer: "_validityChanged"}, // validity state of xpos input itself
},
ready: function() {
// Called before attached
this.$.xpos.validate = this._validatePosition.bind(this);
},
_validatePosition: function() {
console.log(this.nodeName + " validatePosition validator called\n");
// perform some validation code here like the gold-email-input example above
}
});
</script>

CKEditor widget receives data after it has been rendered

Looking at the docs you can pass startup data to a widget:
editor.execCommand( 'simplebox', {
startupData: {
align: 'left'
}
} );
However this data is pointless as there seems to be no way to affect the template output - it has already been generated before the widget's init, and also the data isn't even available at that point:
editor.widgets.add('uselesswidget', {
init: function() {
// `this.data` is empty..
// `this.dataReady` is false..
// Modifying `this.template` here does nothing..
// this.template = new CKEDITOR.template('<div>new content</div>');
// Just after init setData is called..
this.on('data', function(e) {
// `e.data` has the data but it's too late to affect the tpl..
});
},
template: '<div>there seems to be no good way of creating the widget based on the data..</div>'
});
Also adding a CKEditor tag throws a "Uncaught TypeError: Cannot read property 'align' of undefined" exception so it seems the data is also not passed to the original template:
template: '<div>Align: {align}</div>'
What is the point of having a CKEDITOR.template.output function which can accept a context, if there's no way of dynamically passing data?
The only horribly hacky solution I've found so far is to intercept the command in a beforeCommandExec and block it, then modify the template and manually execute the command again..
Any ideas to generate dynamic templates based on passed data? Thanks.
Here's how I did it.
Widget definition:
template:
'<div class="myEditable"></div>',
init: function () {
// Wait until widget fires data event
this.on('data', function(e) {
if (this.data.content) {
// Clear previous values and set initial content
this.element.setHtml('')
var newElement = CKEDITOR.dom.element.createFromHtml( this.data.content );
this.element.append(newElement,true);
this.element.setAttribute('id', this.data.id);
}
// Create nestedEditable
this.initEditable('myEditable', {
selector: '.myEditable',
allowedContent: this.data.allowedContent
})
});
}
Dynamic widget creation:
editor.execCommand('myEditable', {startupData: {
id: "1",
content: "some <em>text</em>",
allowedContent: {
'em ul li': true,
}
}});

Resources