for (const localTrack of localTracks) {
if (localTrack.kind === 'video') {
localParticipant.publishTrack(localTrack, {
priority: 'low',
});
} else {
localParticipant.publishTrack(localTrack, {
priority: 'standard',
});
}
}
I am currently getting an error:
TwilioError: Track name is duplicated
This is because this method is called multiple times (each time a new permission is approved) with the list of all approved tracks.
How do I check if a particular track has been already published?
One would expect that we can inspect the localParticipant object, e.g.
console.log(
'>>>',
localParticipant.tracks.size,
localParticipant.audioTracks.size,
localParticipant.videoTracks.size
);
but the above produces >>> 0 0 0 and then is followed by "Track name is duplicated" error. So there is some race-condition error.
This was indeed a race condition, and to understand how we got there, we need the full code example:
useEffect(() => {
if (!localParticipant) {
return;
}
for (const localTrack of localTracks) {
if (localTrack.kind === 'video') {
localParticipant.publishTrack(localTrack, {
priority: 'low',
});
} else {
localParticipant.publishTrack(localTrack, {
priority: 'standard',
});
}
}
return () => {
localParticipant.audioTracks.forEach((publication) => {
publication.unpublish();
});
localParticipant.videoTracks.forEach((publication) => {
publication.unpublish();
});
};
}, [localParticipant, localTracks]);
What is happening here is that every time localParticipant or localTracks change, we do two things:
We clean-up by unsetting any existing audio/ video tracks
We bind new tracks
Somehow the clean up logic causes the localParticipant.publishTrack method to go into an error state ("Track name is duplicated") publishTrack is invoked just after unpublish.
The fix is to simply move unpublish logic into a separate hook that does not depend on localTracks.
useEffect(() => {
if (!localParticipant) {
return;
}
return () => {
localParticipant.audioTracks.forEach((publication) => {
publication.unpublish();
});
localParticipant.videoTracks.forEach((publication) => {
publication.unpublish();
});
};
}, [localParticipant]);
useEffect(() => {
if (!localParticipant) {
return;
}
for (const localTrack of localTracks) {
if (localTrack.kind === 'video') {
localParticipant.publishTrack(localTrack, {
priority: 'low',
});
} else {
localParticipant.publishTrack(localTrack, {
priority: 'standard',
});
}
}
}, [localParticipant, localTracks]);
Note that you need to do this in addition for handling events. The unmount clean-up strategy is used here primarily to enable react hot reloading.
Related
I'm playing around learning XState and wanted to include an action in a machine that would just log the current state to console.
Defining a simple example machine like so, how would I go about this? Also note the questions in the comments in the code.
import { createMachine, interpret } from "xstate"
const sm = createMachine({
initial: 'foo',
states: {
foo: {
entry: 'logState', // Can I only reference an action by string?
// Or can I add arguments here somehow?
on: {
TOGGLE: {target: 'bar'}
}
},
bar: {
entry: 'logState',
on: {
TOGGLE: {target: 'foo'}
}
}
}
},
{
actions: {
logState(/* What arguments can go here? */) => {
// What do I do here?
}
}
});
I know that actions are called with context and event as arguments but I don't see a way to get the current state from either of those. Am I missing something here?
For a simple use case like yours, you could try recording the state on transition.
let currentState;
const service = interpret(machine).onTransition(state => {
if (state.value != currentState) {
// TODO: terminate timer if any and start a new one
currentState = state.value;
}
});
Then use the value in your actions.
See more here: https://github.com/statelyai/xstate/discussions/1294
Actions receive three arguments - context, event and meta. meta have property state, which is current state.
import { createMachine } from "xstate";
let metaDemo = createMachine(
{
id: "meta-demo",
initial: "ping",
states: {
ping: {
entry: ["logStateValues"],
after: { TIMEOUT: "pong" },
},
pong: {
entry: ["logStateValues"],
after: { TIMEOUT: "ping" },
},
},
},
{
delays: {
TIMEOUT: 3000,
},
actions: {
logStateValues(ctx, event, meta) {
if (meta.state.matches("ping")) {
console.log("It's PING!");
} else if (meta.state.matches("pong")) {
console.log("And now it's PONG");
} else {
console.log(
`This is not supposed to happen. State is: ${meta.state
.toStrings()
.join(".")}`
);
}
},
},
}
);
I have a simple lesson creator where I allow teachers to make selections for various categories, those selections ids are collected and grouped together and I want to return them all at the end as a lesson plan.
However, I am having a strange problem that I can't figure out. My Vuex store shows the selections correctly, however my getter duplicates all of my arrays.
After selections are made, my Vuex store shows something like this through the Vue.js dev-tools plugin:
lesson_store:Object
lesson:Object
selected_event:1
selected_exploration:Array[1]
0:1
selected_extensions:Array[1]
0:1
selected_goals:Array[1]
0:54
selected_lexis:Array[1]
0:2
store.js state and getter:
const state = {
lesson: {
selected_event: '',
selected_exploration: [],
selected_extensions: [],
selected_goals: [],
selected_lexis: [],
}
};
getSelections(state) {
console.log('GETTER SELECTIONS', state.lesson);
return state.lesson
}
My call to getSelections from lesson.vue file:
<template><button #click="saveLesson">Save</button></template>
methods: {
saveLesson () {
console.log('GET RETURN OF SELECTIONS',this.$store.getters["getSelections"]);
},
}
Now my console output is:
lesson_store:Object
lesson:Object
selected_event:1
selected_exploration:Array[2]
0:1
0:1
selected_extensions:Array[2]
0:1
0:1
selected_goals:Array[2]
0:54
0:54
selected_lexis:Array[2]
0:2
0:2
The thing is, none of my other getters behave this way. This getter is super basic.
When I check out store and getSelections getter in the Vue.js dev-tools the values are correct and there are no duplicates.
Any advice or direction you can provide would be much appreciated.
UPDATE::::::
Actions and Mutations for Lesson_Store
// create mutations
const mutations = {
setSelectedEvent(state, payload) {
// state.lesson.selected_event = payload
if (state.lesson.selected_event === payload) {
state.lesson.selected_event = '';
} else {
state.lesson.selected_event = payload
}
},
setSelectedReading(state, payload) {
if (state.lesson.selected_reading === payload) {
state.lesson.selected_reading = '';
} else {
state.lesson.selected_reading = payload
}
},
setSelectedLexis(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_lexis.includes(payload)) {
state.lesson.selected_lexis = state.lesson.selected_lexis.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_lexis.push(payload);
}
// state.lesson.selected_lexis = payload
},
setSelectedExplorations(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_exploration.includes(payload)) {
state.lesson.selected_exploration = state.lesson.selected_exploration.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_exploration.push(payload);
}
// state.lesson.selected_lexis = payload
},
setSelectedQuestions(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_questions.includes(payload)) {
state.lesson.selected_questions = state.lesson.selected_questions.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_questions.push(payload);
}
// state.lesson.selected_lexis = payload
},
setSelectedPerformances(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_performances.includes(payload)) {
state.lesson.selected_performances = state.lesson.selected_performances.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_performances.push(payload);
}
},
setSelectedExtensions(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_extensions.includes(payload)) {
state.lesson.selected_extensions = state.lesson.selected_extensions.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_extensions.push(payload);
}
},
setSelectedGoals(state, payload) {
// if event is already in array, then remove it with filter
// otherwise push it to the array
if (state.lesson.selected_goals.includes(payload)) {
state.lesson.selected_goals = state.lesson.selected_goals.filter(function (item) {
return item !== payload;
});
} else {
state.lesson.selected_goals.push(payload);
}
},
};
// create actions
const actions = {
setSelectedEvent({commit}, payload) {
commit('setSelectedEvent', payload);
},
setSelectedReading({commit}, payload) {
commit('setSelectedReading', payload);
},
setSelectedLexis({commit}, payload) {
commit('setSelectedLexis', payload);
},
setSelectedExplorations({commit}, payload) {
commit('setSelectedExplorations', payload);
},
setSelectedQuestions({commit}, payload) {
commit('setSelectedQuestions', payload);
},
setSelectedPerformances({commit}, payload) {
commit('setSelectedPerformances', payload);
},
setSelectedExtensions({commit}, payload) {
commit('setSelectedExtensions', payload);
},
setSelectedGoals({commit}, payload) {
commit('setSelectedGoals', payload);
},
};
All of these appear to be working correctly because my vuejs dev tools display all of the selection id's properly.
To anyone having similar issues where your dev tools store does not match your actual store values output, it is probably due to your code not updating the store values formally through the actions and mutations approach.
if this store value is ever updated directly without actions and mutations the value in the store will change, however, those updated values will not be detected by vuejs dev tools and your actual store data and dev tools data values will not match.
I make a component, which show information from database in table. But this information with filters.
Filtering can be by event type and by participant (id: integer type).
When I click the button, I call handleShowClick(). In this function I check: if value of type event isn't null, I get from database events with this type. if value of type event is null, I get all events.
After this I check a participant value. If value isn't null, I call function, which search which events are include this participant. Data from this.state.event show in table in another component.
I haven't problems with event type. But I have problem with participant. When I choose one of participant, table shows correct data for a split second. After this return to prev state (without filter by participants).
How can I fix this issue? I set state to event only in this component
class TestPage extends Component {
constructor(props) {
super(props);
this.state = {
event: [],
searchByType: null,
searchByParticipant: null,
participantToEvent: []
};
this.handleShowClick = this.handleShowClick.bind(this);
this.onHandleEventByTypeFetch = this.onHandleEventByTypeFetch.bind(this);
this.handleParticipantSearch = this.handleParticipantSearch.bind(this);
this.onHandleEventFetch = this.onHandleEventFetch.bind(this);
}
handleShowClick() { // onClick
if (this.state.searchByType !== null) {
this.onHandleEventByTypeFetch(); // select * from ... where type=...
} else {
this.onHandleEventFetch(); // select * from ...
}
if (this.state.searchByParticipant !== null) {
this.handleParticipantSearch();
}
}
handleParticipantSearch() {
const list = [];
this.state.participantToEvent.map(itemP => { // participantToEvent is binding table
if (itemP.parid === this.state.searchByParticipant) {
this.state.event.map(itemEvent => {
if (itemEvent.id === itemP.eventid) {
list.push(itemEvent);
}
});
}
});
console.log(list); // here I see array with correct result
this.setState({ event: list });
}
onHandleEventFetch() {
fetch( ... , {
method: 'GET'
})
.then((response) => {
if (response.status >= 400) {
throw new Error('Bad response from server');
}
return response.json();
})
.then(data => {
if (data.length === 0) {
alert('nothing');
} else {
this.setState({
event: data
});
}
});
}
onHandleEventByTypeFetch() {
fetch( ... , {
method: 'GET'
})
.then((response) => {
if (response.status >= 400) {
throw new Error('Bad response from server');
}
return response.json();
})
.then(data => {
if (data.length === 0) {
alert('nothing');
} else {
this.setState({
event: data
});
}
});
...
}
}
Structure of this.state.event:
[{id: 1, name: 'New event', participant: 5, type: 10}, ...]
Structure of this.state.participantToEvent:
[{id: 1, idparticipant: 5, idevent: 1}, ...]
this.setState(...this.state,{ event: list });
I think this would solve your problem. Because you clear every item except for {event:list} by not copying the previous state.
Edit:
You should put
...this.state
to onHandleEventByeTypeFetch and onHandleEventFetch. Without them when you click handleShowClick one of those two functions always work and clears searchByParticipant data from the state by not copying the previous state.
The reason for you see the correct data for a short time is all about async nature of the state.
I'm having an issue where I'm using peekAll() to load a large list of records in my route model(). I've got some view rendering streamlining to do as the page render is currently taking 2500ms, but it's highlighting that the foo.loading state doesn't seem to be triggered, as the page template hangs on the existing link when the new link is clicked for the 2500ms without loading displaying. How do I manually control the foo.loading state in the route.js file so that the loading template renders?
I've got the following code in my route.js file currently trying to use schedule to set isRendering on the controller:
model(params, transition) {
const passedInOrgCode = transition.params.customers.org_code;
const orgCode = passedInOrgCode !== undefined ? passedInOrgCode : this.get('currentUser.org.orgCode');
if (orgCode !== this.get('currentUser.impersonatedOrg.orgCode')) {
this.store.queryRecord('organisation', { org: orgCode }).then(org => {
this.get('currentUser').impersonateOrg(org);
}
const peekData = this.store.peekAll('customer');
let filterByOrg = (customers) => {
return customers.filter((item) => {
if (item.get('parentOrgCode') === orgCode || parseInt(orgCode) === ENV.APP.KORDIA_ORG_CODE) {
return true;
}
});
};
if (peekData.get('content').length === 0) {
return new Ember.RSVP.Promise((resolve, reject) => {
this.store.query('customer', { orgCode: orgCode }).then((customers) => {
resolve(filterByOrg(customers));
}).catch(() => { reject(); });
});
} else {
Ember.run(() => {
this.controllerFor('customers.index').set('isRendering', true);
return filterByOrg(peekData);
});
Ember.run.schedule('afterRender', () => {
this.controllerFor('customers.index').set('isRendering', false);
});
}
},
And in my handlebars file:
{{#if isRendering}}
{{loading-component }}
{{else}}
{{customer-list customers=model updated=updated search=search orgCode=orgCode}}
{{/if}}
All of the states are triggering to the console with the right timing, but I can't get the view to render the loading state.
[SOLVED]
I had to wrap my peekAll in a RSVP promise object with an Ember.run.later function. The answer was in the Asynchronous Routing docs - https://guides.emberjs.com/v2.12.0/routing/asynchronous-routing/#toc_the-router-pauses-for-promises. The relevant piece of code is below:
if (peekData.get('content').length === 0) {
return new Ember.RSVP.Promise((resolve, reject) => {
this.store.query('customer', { orgCode: orgCode }).then((customers) => {
resolve(filterByOrg(customers));
}).catch(() => { reject(); });
});
} else {
return new Ember.RSVP.Promise(function(resolve) {
Ember.run.later(function() {
resolve(filterByOrg(peekData));
});
});
}
Hope this helps someone :)
basically, I am validating form fields by checking if they pass my regex, and if they do, I am setting state with either 'success' or 'error' (used by react-bootstrap).
so basically, I have about 6 functions that need to execute, however, the password field validation functions are giving me a lot of trouble.
My handleSubmit() at the moment looks something like this -
handleSubmit() {
this.validate1();
this.validate2();
// ...
this.validatePassword();
this.validateConfirmPassword();
}
However, the issue is that validatePassword() will setState either 'success' or 'error', and since the functions are not firing off in order, I usually get the wrong result for validateConfirmPassword().
I am reading the mozilla page on Promises, but I am really confused and not sure how to apply that in my code.
Could I do something like Promise.all([everything_except_validateConfirmPassword]).then(validateConfirmPassword()) but that doesn't seem right..
validatePassword(pass) {
if (pass.length >= 8) {
if (checkPass.test(pass)) {
this.setState({
passValidation: validation.success
});
} else {
this.setState({
passValidation: validation.error
});
}
} else {
this.setState({
passValidation: validation.error
});
}
}
validateConfirmPassword(pass, confirmPass) {
const matches = pass === confirmPass;
if (matches && this.state.passValidation === validation.success) {
this.setState({
confirmPassValidation: validation.success
});
} else {
this.setState({
confirmPassValidation: validation.error
});
}
}
You can solve this by using React's componentDidUpdate in this way:
componentDidUpdate() {
if (this.state.canCheckConfirmPwd) {
this.validateConfirmPassword();
}
}
validatePassword(pass) {
if (pass.length >= 8) {
if (checkPass.test(pass)) {
this.setState({
passValidation: validation.success,
canCheckConfirmPwd: true, // on next update we'll trigger validateConfirmPassword()
});
} else {
this.setState({
passValidation: validation.error
});
}
} else {
this.setState({
passValidation: validation.error
});
}
}
validateConfirmPassword(pass, confirmPass) {
const matches = pass === confirmPass;
if (matches && this.state.passValidation === validation.success) {
this.setState({
confirmPassValidation: validation.success,
canCheckConfirmPwd: false, // to avoid retriggering the function on next update
});
} else {
this.setState({
confirmPassValidation: validation.error,
canCheckConfirmPwd: false,
});
}
}