I'm learning how to fetch asynchronous data in a web component using the fetch API and lit-element:
import {LitElement, html} from 'lit-element';
class WebIndex extends LitElement {
connectedCallback() {
super.connectedCallback();
this.fetchData();
}
fetchData() {
fetch('ajax_url')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
};
response.json();
})
.then(data => {
this.data = data;
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
}
render() {
if (!this.data) {
return html`
<h4>Loading...</h4>
`;
}
return html`
<h4>Done</h4>
`;
}
}
customElements.define('web-index', WebIndex);
However the html rendered never changes. What I'm doing wrong? Is this the best way to fetch asynchronous data in a web component?
You need to register data in component properties so that the render is called once value of data is changed
static get properties() {
return {
data: Object
}
}
https://lit-element.polymer-project.org/guide/properties
Related
EventEmitter in Nestjs is wrapper around EventEmitter2 module. I whant that Server-Sent Events return Observable with EE.
import { Controller, Post, Body, Sse } from '#nestjs/common';
import { fromEvent } from 'rxjs';
import { EventEmitter2 } from '#nestjs/event-emitter';
import { OrdersService } from './orders.service';
import { CreateOrderDto } from './dto/create-order.dto';
#Controller('orders')
export class OrdersController {
constructor(private ordersService: OrdersService,
private eventEmitter2: EventEmitter2) {}
#Post()
createOrder(#Body() createOrderDto: CreateOrderDto) {
// save `Order` in Mongo
const newOrder = this.ordersService.save(createOrderDto);
// emit event with new order
this.eventEmitter2.emit('order.created', newOrder);
return newOrder;
}
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
}
But after subscribtion to this source from browser i've getting only errors
this.eventSource = new EventSource('http://localhost:3000/api/v1/orders/newOrders');
this.eventSource.addEventListener('open', (o) => {
console.log("The connection has been established.");
});
this.eventSource.addEventListener('error', (e) => {
console.log("Some erorro has happened");
console.log(e);
});
this.eventSource.addEventListener('message', (m) => {
const newOder = JSON.parse(m.data);
console.log(newOder);
});
It's quite likely that you forgot to format the event in the right way.
For SSE to work internally, each chunk needs to be a string of such format: data: <your_message>\n\n - whitespaces do matter here. See MDN reference.
With Nest.js, you don't need to create such message manually - you just need to return a JSON in the right structure.
So in your example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
would have to be adjusted to, for example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created')
.pipe(map((_) => ({ data: { newOrder } })));
}
the structure { data: { newOrder } } is key here. This will be later translated by Nest.js to earlier mentioned data: ${newOrder}\n\n
app.js
const mapStateToProps = (state) => {
return {home:state}
}
const mapDispatchToProps = (dispatch) => {
return {
guestLogin: (data)=>{dispatch(guestLogin(data)).then(()=>{
dispatch(initiateTrans(stateProps.home))
})},
};
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
return Object.assign({}, ownProps, stateProps, dispatchProps,{
initiateTrans: () => dispatchProps.initiateTrans(stateProps.home),
})
}
Action.js
export const guestLogin= (state)=>{
var data={
'email':state.email,
'name':state.name,
'phone_number':state.ph_number,
'phone_code':state.country_code
}
return function(dispatch) {
return dataservice.guestSignup(data).then(res => {
dispatch(afterLoggedGuest(res))
}).catch(error => {
throw(error);
});
}
}
function afterLoggedGuest(result) {
return {type: guestLoginChange, result};
}
export const initiateTrans= (updatedState)=>{
return function(dispatch) {
return dataservice.initiateTransaction(updatedState).then(res => {
console.log("initiateTransaction",res)
}).catch(error => {
throw(error);
});
}
}
Reducer.js
if(action.type === guestLoginChange){
return {
...state,guestData: {
...state.guestData,
Authorization: action.result.authentication ,
auth_token: action.result.auth_token ,
platform: action.result.platform
} ,
}
}
I am having two api requests.. After first api request success i want to update state value then pass that updated state to another api request..
I tried to get the updted props
how to dispatch the initiateTrans with update props
I need to update value at api request success in call back i need to call one more request with updated state value
currently i am not able to get the update props value
I think this is a good use case for thunk (redux-thunk), which is a middleware that allows you to execute multiple dispatches in an action.
You will need to apply the middleware when you configure the initial store (see docs on link above). But then in your actions, you can wrap the code with a dispatch return statement, which gives you access to multiple calls. For example:
export const guestLogin= (state)=>{
return dispatch => {
var data={...} // some data in here
return dataservice.guestSignup(data).then(res => {
dispatch(afterLoggedGuest(res))
}).catch(error => {
throw(error);
// could dispatch here as well...
});
}
}
In the code below I want to get the form values from Edit form and sent using fetch on handleClick.
class GenerateButton extends Component {
handleClick = () => {
const { push, record, showNotification, values } = this.props;
const updatedRecord = { ...record, is_approved: true };
fetch(`api/reports/${record.id}`, { method: 'GET', body: updatedRecord })
.then((response) => {
return response.blob();
}).then(function(blob) {
console.log(blob);
})
.catch((e) => {
showNotification('Error: report generation failed.', 'warning')
});
}
render() {
return <RaisedButton label="Generate" onClick={this.handleClick} />;
}
}
I have the following middleware that I use to call similar async calls:
import { callApi } from '../utils/Api';
import generateUUID from '../utils/UUID';
import { assign } from 'lodash';
export const CALL_API = Symbol('Call API');
export default store => next => action => {
const callAsync = action[CALL_API];
if(typeof callAsync === 'undefined') {
return next(action);
}
const { endpoint, types, data, authentication, method, authenticated } = callAsync;
if (!types.REQUEST || !types.SUCCESS || !types.FAILURE) {
throw new Error('types must be an object with REQUEST, SUCCESS and FAILURE');
}
function actionWith(data) {
const finalAction = assign({}, action, data);
delete finalAction[CALL_API];
return finalAction;
}
next(actionWith({ type: types.REQUEST }));
return callApi(endpoint, method, data, authenticated).then(response => {
return next(actionWith({
type: types.SUCCESS,
payload: {
response
}
}))
}).catch(error => {
return next(actionWith({
type: types.FAILURE,
error: true,
payload: {
error: error,
id: generateUUID()
}
}))
});
};
I am then making the following calls in componentWillMount of a component:
componentWillMount() {
this.props.fetchResults();
this.props.fetchTeams();
}
fetchTeams for example will dispatch an action that is handled by the middleware, that looks like this:
export function fetchTeams() {
return (dispatch, getState) => {
return dispatch({
type: 'CALL_API',
[CALL_API]: {
types: TEAMS,
endpoint: '/admin/teams',
method: 'GET',
authenticated: true
}
});
};
}
Both the success actions are dispatched and the new state is returned from the reducer. Both reducers look the same and below is the Teams reducer:
export const initialState = Map({
isFetching: false,
teams: List()
});
export default createReducer(initialState, {
[ActionTypes.TEAMS.REQUEST]: (state, action) => {
return state.merge({isFetching: true});
},
[ActionTypes.TEAMS.SUCCESS]: (state, action) => {
return state.merge({
isFetching: false,
teams: action.payload.response
});
},
[ActionTypes.TEAMS.FAILURE]: (state, action) => {
return state.merge({isFetching: false});
}
});
The component then renders another component that dispatches another action:
render() {
<div>
<Autocomplete items={teams}/>
</div>
}
Autocomplete then dispatches an action in its componentWillMount:
class Autocomplete extends Component{
componentWillMount() {
this.props.dispatch(actions.init({ props: this.exportProps() }));
}
if an error happens in the autocomplete reducer that is invoked after the SUCCESS reducers have been invoked for fetchTeams and fetchResults from the original calls in componentWillMount of the parent component and the error will be handled in the Promise.catch of the callApi method that happens in the middleware.
return callApi(endpoint, method, data, authenticated).then(response => {
return next(actionWith({
type: types.SUCCESS,
payload: {
response
}
}))
}).catch(error => {
return next(actionWith({
type: types.FAILURE,
error: true,
payload: {
error: error,
id: generateUUID()
}
}))
});
};
This is because it is happening with in the same tick of the event loop. If I introduce some asynchronicity in the Autcomplete componentWIllMount function then the error is not handled in the Promise catch handler of the middleware
class Autocomplete extends Component{
componentWillMount() {
setTimeout(() => {
this.props.dispatch(actions.init({ props: this.exportProps() }));
});
}
Should I have the callApi function execute on a separate event loop tick?
I'm wondering at a high level what the correct pattern is for the following...
I have a HomeComponent, with some links to other components.
When I click on one of the links, I want to make an ajax request to get the initial state for that component.
Do I dispatch in the HomeComponent in the onClick? Or dispatch an action in the other components if there's no initialState from the server? (I'm doing a universal app, so if I was to hit one of the other components directly, the initial state would already be there, but coming from my HomeComponent, the data WON'T be there)
This is what I had so far...
class HomeComponent extends React.Component {
navigate(e) {
e.preventDefault();
// Fetch data here
actions.fetch(1234);
// When do I call this?
browserHistory.push(e.target.href);
}
render() {
const links = [
<a href="/foo/1247462" onClick={this.navigate}>Link 1</a>,
Link 2,
];
return (
<ul>
{links.map((link) => (
<li>{link}</li>
))}
</ul>
);
}
}
Sorry i can add a comment, is there a reason you're not using react-redux && redux-thunk ?
what you ask can be easily done with those : you fetch what you need in mapDispatchToProps & dispatch an action with the fetched initial state
Your reducer will catch the said dispatched action and update its state which will update the props of the react component with the help of mapStateToProps
I am writing from memory, it might not be accurate 100% :
redux file
componentNameReducer = (
state = {
history: ''
},
type = {}
) => {
switch(action.type) {
case 'HISTORY_FETCHED_SUCCESSFULLY':
return Object.assign({}, state, {
history: action.payload.history
});
default:
return state;
}
};
mapStateToProps = (state) => {
history: state.PathToWhereYouMountedThecomponentNameReducerInTheStore.history
};
mapDispatchToProps = (dispatch) => ({
fetchHistory : () => {
fetch('url/history')
.then((response) => {
if (response.status > 400) {
disptach({
type: 'HISTORY_FETCH_FAILED',
payload: {
error: response._bodyText
}
});
}
return response;
})
.then((response) => response.json())
.then((response) => {
//do checkups & validation here if you want before dispatching
dispatch({
type: 'HISTORY_FETCHED_SUCCESSFULLY',
payload: {
history: response
}
});
})
.catch((error) => console.error(error));
}
});
module.exports = {
mapStateToProps,
mapDispatchToProps,
componentNameReducer
}
On your react component you will need :
import React, {
Component
} from 'somewhere';
import { mapStateToProps, mapDispatachToProps } from 'reduxFile';
import { connect } from 'react-redux';
class HistoryComponent extends Component {
constructor(props){
super(props);
this.props.fetchHistory(); //this is provided by { connect } from react-redux
}
componentWillReceiveProps(nextProps){
browserHistory.push(nextProps.history);
}
render() {
return (
);
}
}
//proptypes here to make sure the component has the needed props
module.exports = connect(
mapStateToProps,
mapDispatachToProps
)(HistoryComponent);