I am using peerjs library to build a chat. I'm trying to do an observable to trigger an event every time I get a new message.
this.data = new Observable(observer => {
this.peer.on('connection', (data) => {
alert("TRIGGER");
observer.next(data);
});
})
this.peerMessages = this.data.subscribe(message => {
alert(message);
});
I can see the first alert but the second one never triggers. I don't get any error from the browser console. What am I doing wrong?
Im trying to use an observer because instead of alert(message) I want to change a variable from the angular component.
The code looks good, but make sure you import everything you need. For example, if using rxjs:
import { Observable, ... } from 'rxjs/Observable';
Related
When Promise.all resolves and the new activity is saved, the user should be routed to /activities to view their newly created activity. Everything works as expected, however I currently need to refresh /activities page (once) after being routed in order to view the new activity in the table.
const handleSaveActivity = e => {
e.preventDefault();
Promise.all([
addActivity(),
saveActivity()
]).then(() => {
props.history.push('/activities');
})
};
I'm not sure how to re-render the page automatically after pushing a new history state, so the user does not need to manually refresh the page to see the new state. Happy to provide more code snippets if I left out something critical.
Hi i must be a little late to answer this, but this issue can be due to the wrong use of useEffect, if you have lets say a todo list and you wanna fetch data with axios for example, it would look like this:
useEffect(()=>{
axios.get(`${YOUR_URL}/todos`)
.then((res)=>{
setTodos(todos=res.data)
})
},[])
now as you can see we have initial value of an empty array, so this is acting as a ComponentDidMount, what you might want is to re render the component after it gets a new value, so you want to have a ComponentDidUpdate effect, so you would just not initialize the value as an empty array, therefore it would look like this:
useEffect(()=>{
axios.get(`${YOUR_URL}/todos`)
.then((res)=>{
setTodos(todos=res.data)
})
})
Hope this helps someone, couse i landed here due to the same issue and came to solve it this way.
just to run this.setState({whateverKey:whateverValue})?
In your activities page (call it Activities component) you should call API to get the updated data every time browser hit this component URL.
With class based style, you should do it in componentDidMount life cycle hook
class Activities extends Component {
// ...
componentDidMount() { loadActivities() }
// ...
}
With function based style, you should do it in useEffect hook
import React, { useEffect } from 'react'
const Activities = () => {
useEffect(() => { loadActivities() });
}
https://github.com/supasate/connected-react-router Please use this package, it solves the problem.
This issue I've faced a few minutes ago...however I finally found the solution by manually using the vanilla javascript. => for refreshing the page you can use
=> window.location.reload(false); after using the push property.
I've added an interceptor for my HTTP requests where I have to use the access token of my user instance. In my app component I initialise my user:
app.component.ts
private async restoreUser(): Promise<UserModel | any> {
// ... some view stuff
return this.userService.restore()
// login instance could be found
.then(async () => {
// ... some view stuff
})
// local storage is empty -> login is necessary
.catch(async () => {
// ... some view stuff
this.subscription = this.networkSrv.getNetworkStatus()
.subscribe((status: ConnectionStatus) => {
if (status === ConnectionStatus.Online) {
// ... some view stuff
} else {
// ... some view stuff
}
});
});
}
http.interceptor.ts
return this.userSrv.user.pipe(
map((user: UserModel) => request.clone(
{setParams: {'access-token': user.accessToken}}
)),
mergeMap(request => next.handle(request))
);
Now I would like to do a request by initialising my app. The problem is, that the user instance is empty and the application throws an error. Is there a way to do something like await -> so that the user instance is set?
Example:
this.transmissionSrv.restoreQueue().then((projects: ProjectModel[]) => {
this.transmissionSrv.transmitProjects(projects, true).subscribe(console.log);
});
Currently, I use the setTimeout-method, but that isn't the way I should do it, right? In addition, sorry for not being consistent by using Observer; Ionic often uses Promises(?)
You should try adding a filter before your map. Using the filter, your map wont get call until the user is set.
return this.userSrv.user.pipe(
filter(Boolean),
map((user: UserModel) => request.clone(
{setParams: {'access-token': user.accessToken}}
)),
mergeMap(request => next.handle(request))
);
There are a couple of ways you could solve this.
Synchronously: Use an Angular APP_INITIALIZER (see here) to make the backend call and ensure the user object is present when the app bootstraps.
Asynchronously: Modify your existing application to store the user instance in an RxJs BehaviorSubject in a service somewhere and have components that depend on it subscribe to that BehaviorSubject wherever the user instance is needed. When the service constructs, have it make the backend call and stick the completed user instance inside the BehaviorSubject (userSubject.next(user)) when it's complete.
I'm facing a random behavior with the flatMap operator and I can't find the reason why. Sometimes it triggers, sometimes it doesn't...
Here is the situation:
The user can change the language in my app, so I have a BehaviorSubject on the language (which is triggered by a select list) returned as an observable by its provider. When there's a change I call (via flatMap) a http request to fetch the data in the language selected.
It looks like this:
this.languageProvider.getLang$().flatMap(langCode => {
return this.http.get(`https://SERVER_URL.net/datas?lang=${langCode}`)
.map(data => data.json())
})
.subscribe(
data => {
// do smth
},
err => {
// do smth
}
);
The thing is, when I change the language the http call is most often not triggered.
If I add a simple subscribe it always work...
this.languageProvider.getLang$().subscribe(langCode => {
console.log(langCode);
});
Any idea why I have this issue ?
Here is the languageProvider:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class LanguageProvider {
private lang$: BehaviorSubject<string>;
constructor() {
this.lang$ = new BehaviorSubject('en');
}
setLang(langCode: string) {
this.lang$.next(langCode);
}
getLang$(): Observable<string> {
return this.lang$.asObservable();
}
}
Thanks a lot
OK, fixed it.
It wasn't random actually...
In my (simplified) snippet I didn't write how I handled the server response. Actually I could receive a 304 STATUS CODE if I tried to fetch datas that have not changed on the server (I'm doing this to avoid downloading server datas each time a user starts the app)
THE THING IS, I had to handle the 304 in the error callback since angular takes everything above 299 as an error.
WHAT I DIDN'T KNOW was that the error callback was killing my observable. So the pseudo "random" behavior was actually:
- If I tried to reload the app without changing the language, then I got a 304, so the observable was killed
- If I tried reload the app after changing the language, then the app would fetch the data for this language and get a 200. So the obserbavle would keep on working
Maybe this will help someone.
Cheers
I have this aurelia component for displaying a feed to the user which depends on a custom API service class called Api for fetching the feed. The Api class has a get() function which in turn uses HttpClient to fetch the data.
Trying to test the component I want to mock the service class, specifically the get function, to return suitable test data and have this mock injected into the component via aurelia's DI container. The DI part I am having trouble with.
Here is the relevant part of component's js file
import {bindable, inject} from 'aurelia-framework';
import {Api} from 'services/api';
#inject(Api)
export class Feed {
events = null;
constructor(api) {
console.info('feed.js constructor, api:', api)
this.api = api;
}
And the relevant code from my test
beforeEach(done => {
...
let mockApi = new Api();
spyOn(mockApi, 'get').and.returnValue(mockGetResponse);
const customConfig = (aurelia) => {
let conf = aurelia.use.standardConfiguration().instance("Api", mockApi);
console.info('Registering Api:', conf.container.get("Api"));
return conf;
}
const ct = new ComponentTester();
ct.configure = customConfig;
sut = ct.withResources('activityfeed/feed');
sut.inView('<feed username.bind="username"></feed>')
.boundTo({username: TEST_USER});
sut.create(bootstrap).then(() => {
done();
});
});
This code is actually working the way I intended as far as I can tell. On creation of the component my customConfig function is called and the mockApi instance is logged to the console.
However later in the bootstrapping process the component constructor still receives an instance of the actual Api service class instead of my mock instance which was registered to the container.
Spent the last couple of hours trying to dig up any documentation or examples for doing things like this without success so if anyone can assist I would greatly appreciate it.
Or if there is / are alternative ways to accomplish this that would work just as well.
When testing a standard component that consists of both a view and a view model, using the aurelia-testing package, I find that a cleaner approach might be to let Aurelia create both the view and view-model, and use mocked classes for all view model dependencies.
export class MockApi {
response = undefined;
get() { return Promise.resolve(this.response) }
}
describe("the feed component", () => {
let component;
let api = new MockApi();
beforeEach(() => {
api.response = null;
component = StageComponent
.withResources("feed/feed")
.inView("<feed></feed>");
component.bootstrap(aurelia => {
aurelia.use
.standardConfiguration();
aurelia.container.registerInstance(Api, api);
});
});
it("should work", done => {
api.response = "My response";
component.create(bootstrap).then(() => {
const element = document.querySelector("#selector");
expect(element.innerHTML).toBe("My response, or something");
done();
});
});
});
This approach lets you verify the rendered HTML using the normal view model class, mocking the dependencies to control the test data.
Just to answer my own question, at least partially, if it can be useful to someone.
By using the actual Api class constructor as the key instead of the string "Api" the mock seems to be correctly injected.
I.e.
import {Api} from 'services/api';
...
let conf = aurelia.use.standardConfiguration().instance(Api, mockApi);
I have an App component that is responsible for rendering child input components, it is also responsible for handling fetch requests to the Twitch API via a method called channelSearch. I have tried to adhere to suggested best practices outlined here for working with ajax/fetch with React.
The method is passed down through props and called via a callback.
Note the fetch method is actually isomorphic-fetch.
channelSearch (searchReq, baseUrl="https://api.twitch.tv/kraken/channels/") {
fetch(baseUrl + searchReq)
.then(response => {
return response.json();
})
.then(json => {
this.setState({newChannel:json});
})
.then( () => {
if (!("error" in this.state.newChannel) && this.channelChecker(this.state.newChannel._id, this.state.channelList) ) {
this.setState(
{channelList: this.state.channelList.concat([this.state.newChannel])}
);
}
})
.catch(error => {
return error;
});
}
I am currently trying to write a test for the channelSearch method. I am currently using enzyme and jsdom to mount the entire <App> component in a DOM. Find the child node with the callback, simulate a click (which should fire the callback) and check to see if the state of the component has been changed. However, this does not seem to work.
I have also tried calling the method directly, however, I run into problems with this.state being undefined.
test('channel search method should change newChannel state', t => {
const wrapper = mount(React.createElement(App));
wrapper.find('input').get(0).value = "test";
console.log(wrapper.find('input').get(0).value);
wrapper.find('input').simulate("change");
wrapper.find('button').simulate("click");
console.log(wrapper.state(["newChannel"]));
});
I am really lost, I am not sure if the method itself is poorly written or I am not using the correct tools for the job. Any guidance will be greatly appreciated.
Update #1:
I included nock as recommended in comments, test now looks like this:
test('channel search method should change newChannel state', t => {
// Test object setup
var twitch = nock('https://api.twitch.tv')
.log(console.log)
.get('/kraken/channels/test')
.reply(200, {
_id: '001',
name: 'test',
game: 'testGame'
});
function checker() {
if(twitch.isDone()) {
console.log("Done!");
console.log(wrapper.state(["newChannel"]));
}
else {
checker();
}
}
const wrapper = mount(React.createElement(App));
wrapper.find('input').get(0).value = "test";
wrapper.find('input').simulate("change");
wrapper.find('button').simulate("click");
checker();
});
This still does not seem to change the state of the component.
fetch is asynchronous but you're testing synchronously, you need to either mock fetch with a synchronous mock or make the test asynchronous.
nock may work for you here.
I suggest you create a sample of your test using plnkr.
I agree with Tom that you're testing synchronously. It would of course be helpful to show off your actual component code (all of the relevant portions, like what calls channelSearch, or at the least describe it by saying e.g. "channelSearch is called by componentDidMount()". You said:
I run into problems with this.state being undefined.
This is because this.setState() is asynchronous. This is for performance reasons, so that React can batch changes.
I suspect you'll need to change your code that is currently:
.then(json => {
this.setState({newChannel:json});
})
to:
.then(json => {
return new Promise(function(resolve, reject) {
this.setState({newChannel:json}, resolve);
})
})
Note that your checker() method won't work. It's looping, but twitch.isDone() will never be true because it never has a chance to run. Javascript is single threaded, so your checker code will run continuously, not allowing anything else in between.
If you set up the plnkr, I'll take a look.
Refactor out the fetch code from the component then pass it it to the component as a callback function in the properties.
export class Channel extends React.Component {
componentDidMount() {
this.props.searchFunction().then(data => this.setState(data));
}
render() {
return <div>{this.state}</div>;
}
}
Uage:
function channelSearch(name) {
return fetch(`https://api.twitch.tv/kraken/search/channels?query=${name}`);
}
<Channel searchFunction={channelSearch} />
Now you can test the API functionality independently of the component.