Mapping array value in API response for conditional check - reactjs

I have an api which loads a response with an array value I need to map, in order to both check if available, and if so, use the id value of the offers to display later. I'm currently able to simply get the available option, but need to be able to map down to the offers and grab the id value within the FETCH OFFER DETAILS fetchContent.
API RESPONSE
{
"available": true,
"offers": [
{
"days_free": 30,
"id": 1,
}
]
}
React Component
class CancelOffer extends React.Component {
constructor (props) {
super(props)
this.state = {
user: []
}
this.processData = this.processData.bind(this)
this.cancelAccount = this.cancelAccount.bind(this)
this.acceptPromo = this.acceptPromo.bind(this)
}
componentDidMount () {
this.fetchContent(this.processData)
}
/**
* Fetch offer details
*/
fetchContent (cb) {
superagent
.get('/api/user/offers')
.then(function(res) {
/**
* If res.body.available === true, get offers.id and use in
acceptPromo.
* Else if not true, redirect to
.then(this.props.setStep(ACCEPTANCE))
*/
if(res.body.available){
const offerCode = res.body.offers[0].id
alert(offerCode)
} else {
this.props.setStep(ACCEPTANCE)
}
})
}
/**
* Set state after user have been fetched
* #param data
*/
processData (data) {
this.setState({
user: data.body.offers
})
}
/**
* Send accept promo offer code
*/
acceptPromo (e) {
e.preventDefault()
superagent
.post('/api/user/offers')
.send({
offerId: offerCode <==THIS NEEDS TO COME FROM THE API RESPONSE
})
.then(this.props.setStep(ACCEPTANCE))
}
render () {
const content = this.props.config.contentStrings
const daysFree = this.state.user.map((daysFree, i) => {
return (
<span>{daysFree.days_free}</span>
)
})
return (
<div className='offer'>
<h2 className='offer-heading md'>Heading</h2>
<p className='offer-subpara'>sub paragraph {daysFree}</p>
<div className='footer-links'>
<a onClick={this.acceptPromo} className='btn btn--primary btn--lg'>accept promo</a>
<a onClick={this.cancelAccount} className='cancel-link'>cancel</a>
</div>
</div>
)
}
}
export default CancelOffer

EDIT - ANSWER:
I needed to set the promo code in state
fetchContent (cb) {
superagent
.get('/api/user/offers')
.then((res) => {
if(res.body.available){
this.setState({
offerId: res.body.offers[0].id
})
} else {
this.props.setStep(OFFER)
}
})
}
and pass it to the accept promo
acceptPromo (e) {
e.preventDefault()
const { offerId } = this.state
superagent
.post('/api/user/offers')
.send({offerId})
.then(this.props.setStep(ACCEPTANCE))
}

Related

setState not returned from render when using Axios

I'm using axios to get data from an endpoint. I'm trying to store this data inside the state of my React component, but I keep getting this error:
Error: Results(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I've struggled with many approaches: arrow functions etc., but without luck.
export default class Map extends Component {
constructor() {
super();
this.state = {
fillColor: {},
selectedCounty: "",
dbResponse: null,
};
}
getCounty(e) {
axios.get("/getWeatherData?county=" + e.target.id)
.then((response) => {
this.setState(prevState => {
let fillColor = {...prevState.fillColor};
fillColor[prevState.selectedCounty] = '#81AC8B';
fillColor[e.target.id] = '#425957';
const selectedCounty = e.target.id;
const dbResponse = response.data;
return { dbResponse, selectedCounty, fillColor };
})
}).catch((error) => {
console.log('Could not connect to the backend');
console.log(error)
});
}
render() {
return (
<div id="map">
<svg>big svg file</svg>
{this.state.selectedCounty ? <Results/> : null}
</div>
)
}
I need to set the state using prevState in order to update the fillColor dictionary.
Should this be expected? Is there a workaround?

can't append to sourceBuffer because mediaSource Element (Parent) was removed

I'm having problem with video Streaming.
I'm using socket io for video Streaming and react for front end.
Front End code looks like this:-
class VideoElement extends React.Component {
constructor(props) {
/* props is having Id, projectDesignSocket */
super();
this.state = { hasError: false };
this.videoRef = React.createRef();
this.mediaSource = null;
this.sourceBuffer = null;
}
componentDidMount() {
this.mediaSource = new MediaSource();
this.videoRef.current.src = URL.createObjectURL(this.mediaSource);
this.mediaSource.onsourceopen = (e) => {
URL.revokeObjectURL(this.videoRef.current.src);
this.sourceBuffer = this.mediaSource.addSourceBuffer("video/webm; codecs=\"vp8, opus\"");
this.sourceBuffer.mode = 'sequence';
this.props.projectDesignSocket.on(`${this.props.Id}VideoData`, ({ blob }) => {
try {
this.sourceBuffer.appendBuffer(blob);
}
catch (err) {
console.log(err);
}
finally {
console.log(this.sourceBuffer);
}
});
}
}
render() {
if (this.state.hasError) {
return <h1>error</h1>;
}
else {
return <video autoPlay ref={this.videoRef} id={this.props.Id}></video>;
}
}
componentWillUnmount() {
this.mediaSource.onsourceopen = null;
this.props.projectDesignSocket.off(`${this.props.Id}VideoData`);
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
console.log(error, info);
}
}
class VideoConfrence extends React.Component {
/* prop is having projectDesignSocket, isAdmin, userList, currentRoom, userName, userId */
constructor(props) {
super();
this.state = {};
this.userVideoRef = React.createRef();
this.mediaRecorder = null;
this.videoStream = null;
}
static getDerivedStateFromProps(nextProp, prevState) {
if (nextProp.userList !== prevState.userList) {
return { userList: nextProp.userList }
}
else {
return {};
}
}
componentDidMount() {
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
}).then(stream => {
this.videoStream = stream;
this.userVideoRef.current.srcObject = stream;
this.mediaRecorder = new MediaRecorder(stream);
this.mediaRecorder.start(100);
this.mediaRecorder.ondataavailable = (e) => {
if (e.data.size) {
this.props.projectDesignSocket.emit('video', {
room: this.props.currentRoom,
data: { userId: this.props.userId, blob: e.data }
});
}
}
}).catch(err => {
console.log(err);
});
}
render() {
return (
<div className='videoConfrencing'>
<video autoPlay ref={this.userVideoRef} />
{
this.state.userList.map((item, index) => {
if (item !== this.props.userId) {
return <VideoElement key={index} Id={item} projectDesignSocket={this.props.projectDesignSocket} />
}
else {
return null;
}
})
}
</div>
);
}
componentWillUnmount() {
this.videoStream.getTracks().forEach(function (track) {
track.stop();
});
this.mediaRecorder.ondataavailable = null;
this.props.projectDesignSocket.off('newMemberAdded');
}
}
And Backend Like This :-
socket.on('video', function ({ room, data }) {
// data has userid and blob as it's properties
if (room && data ) {
socket.broadcast.to(room).emit(`${data.userId}VideoData`, {blob:data.blob});
}
})
error is occuring at try/catch block of componentDidMount of VideoElement
err:-Media resource blob:http://localhost:3000/493042c5-431b-40a1-aff6-5fafe218a677 could not be decoded, error: Error Code: NS_ERROR_FAILURE (0x80004005)
basicaly i'm designing an app for video confrencing and,
i've used mediaRecorder to record stream obtained by getUserMedia into Blobs and send those blobs to server throgh socket ('video event')
and the server receives roomName from which event was emmited, userId and blob of video and broadcast an event ${userId}VideoData to the specific room
when a user connects the server start receiving blobs and start broadcasting event ${userId}VideoData with {userId, blob} as values to be passed to client
when another user connects his webcam feeds are also sent to server...the user connected before him receives his video blobs and is able to play them , this user also receives video blobs of previously connected user but is unable to play them , on first append to sourceBuffer above warning is logged after which if appended again error is thrown!!
the error is that user which connects cant see the video of other users which were connected before him but is able to see the video of users connected after him!!
but users connected before him are able to see his video without any error

Lifecycle hooks - Where to set state?

I am trying to add sorting to my movie app, I had a code that was working fine but there was too much code repetition, I would like to take a different approach and keep my code DRY. Anyways, I am confused as on which method should I set the state when I make my AJAX call and update it with a click event.
This is a module to get the data that I need for my app.
export const moviesData = {
popular_movies: [],
top_movies: [],
theaters_movies: []
};
export const queries = {
popular:
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=",
top_rated:
"https://api.themoviedb.org/3/movie/top_rated?api_key=###&page=",
theaters:
"https://api.themoviedb.org/3/movie/now_playing?api_key=###&page="
};
export const key = "68f7e49d39fd0c0a1dd9bd094d9a8c75";
export function getData(arr, str) {
for (let i = 1; i < 11; i++) {
moviesData[arr].push(str + i);
}
}
The stateful component:
class App extends Component {
state = {
movies = [],
sortMovies: "popular_movies",
query: queries.popular,
sortValue: "Popularity"
}
}
// Here I am making the http request, documentation says
// this is a good place to load data from an end point
async componentDidMount() {
const { sortMovies, query } = this.state;
getData(sortMovies, query);
const data = await Promise.all(
moviesData[sortMovies].map(async movie => await axios.get(movie))
);
const movies = [].concat.apply([], data.map(movie => movie.data.results));
this.setState({ movies });
}
In my app I have a dropdown menu where you can sort movies by popularity, rating, etc. I have a method that when I select one of the options from the dropwdown, I update some of the states properties:
handleSortValue = value => {
let { sortMovies, query } = this.state;
if (value === "Top Rated") {
sortMovies = "top_movies";
query = queries.top_rated;
} else if (value === "Now Playing") {
sortMovies = "theaters_movies";
query = queries.theaters;
} else {
sortMovies = "popular_movies";
query = queries.popular;
}
this.setState({ sortMovies, query, sortValue: value });
};
Now, this method works and it is changing the properties in the state, but my components are not re-rendering. I still see the movies sorted by popularity since that is the original setup in the state (sortMovies), nothing is updating.
I know this is happening because I set the state of movies in the componentDidMount method, but I need data to be Initialized by default, so I don't know where else I should do this if not in this method.
I hope that I made myself clear of what I am trying to do here, if not please ask, I'm stuck here and any help is greatly appreciated. Thanks in advance.
The best lifecycle method for fetching data is componentDidMount(). According to React docs:
Where in the component lifecycle should I make an AJAX call?
You should populate data with AJAX calls in the componentDidMount() lifecycle method. This is so you can use setState() to update your component when the data is retrieved.
Example code from the docs:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
Bonus: setState() inside componentDidMount() is considered an anti-pattern. Only use this pattern when fetching data/measuring DOM nodes.
Further reading:
HashNode discussion
StackOverflow question

Cannot see firebase data in application - react native

I have set up a call to fetch data from my firebase database using react native.
Database structure
Code inside FirebaseList.js
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentWillMount() {
firebase.database().ref('/signposts/items').on('value', snapshot => {
const dataArray = [];
const result = snapshot.val();
for (const data in result) {
dataArray.push(data);
}
this.setState({ data: dataArray });
console.log(this.state.data);
});
}
render() {
return (
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<Text>{item}</Text>
)}
keyExtractor={item => item}
/>
);
}
I believe the connection to firebase is successful as I can build and run the application. However, when the component renders, I do not see my two rows of data 'row1' and 'row2'.
You said your code is right then also check that this rules are set
{
"rules": {
"foo": {
".read": true,
".write": false
}
}
}
Note : - When you use the above rule your database is open for all Read more here. Make you use update the rules once you push to production.
If console.log(dataArray) shows an empty array (assuming that console.log() works...), try checking your connection:
componentDidMount() {
const ref = firebase.database().ref('/signposts');
const checkConnection = firebase.database().ref(`.info/connected`);
checkConnection.on('value', function(snapshot) {
if (snapshot.val() === true) { /* we're connected! */
firebase.database().ref('/signposts').on('value', snapshot => {
const dataArray = [];
const result = snapshot.val();
for (const data in result) {
dataArray.push(data);
}
if (dataArray.length === 0)
console.log("No data.")
else
this.setState({ listViewData: dataArray });
});
} else { /* we're disconnected! */
console.error("Check your internet connection.")
}
}
}

Redux mutation detected between dispatches

I'm having some trouble with a react redux I'm currently working on. I'm relatively new to Redux so maybe I'm missing a simple concept here but what I'm trying to do is build a deck building app for a card game and I want to be able to save the deck anytime a user adds or removes a card from their deck.
However, anytime I click add or remove I'm receiving the following error message while trying to dispatch an update action.
The error message reads as follows:
Uncaught Error: A state mutation was detected between dispatches, in the path `decks.0.cards.mainboard.0.quantity`. This may cause incorrect behavior.
My container component
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import DeckMobileDisplay from './DeckMobileDisplay';
import * as deckActions from '../../actions/deckActions';
export class DeckEditorContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
deck: Object.assign({}, this.props.deck)
}
this.addCard = this.addCard.bind(this);
this.removeCard = this.removeCard.bind(this);
}
addCard(board, cardName) {
let deck = this.state.deck;
let cards = this.state.deck.cards;
cards[board].forEach(i => {
if(i.name === cardName)
i.quantity += 1;
});
const update = Object.assign(deck, cards);
this.props.deckActions.updateDeck(update).then(deck => {
console.log(deck);
})
.catch(err => {
console.log(err);
});
}
removeCard(board, cardName) {
let deck = this.state.deck;
let cards = this.state.deck.cards;
cards[board].forEach(i => {
if(i.name === cardName) {
if (i.quantity === 1) {
cards[board].splice(cards[board].indexOf(i), 1);
}
else {
i.quantity -= 1;
}
}
});
const update = Object.assign(deck, cards);
this.props.deckActions.updateDeck(update).then(deck => {
console.log(deck);
})
.catch(err => {
console.log(err);
});
}
render() {
const deck = Object.assign({}, this.props.deck);
return (
<div className="editor-container">
<DeckMobileDisplay
deck={deck}
addCard={this.addCard}
removeCard={this.removeCard}
/>
</div>
);
}
}
DeckEditorContainer.PropTypes = {
deck: PropTypes.object
};
function getDeckById(decks, id) {
const deck = decks.filter(deck => deck.id == id);
if (deck.length) return deck[0];
return null;
}
function mapStateToProps(state, ownProps) {
const deckId = ownProps.params.id;
let deck = {
id: '',
userId: '',
cards: []
}
if (state.decks.length > 0) {
deck = getDeckById(state.decks, deckId);
}
return {
deck: deck
};
}
function mapDispatchToProps(dispatch) {
return {
deckActions: bindActionCreators(deckActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(DeckEditorContainer);
Component for DeckMobileDisplay
import React, {PropTypes} from 'react';
import TabContainer from '../common/Tabs/TabContainer';
import Tab from '../common/Tabs/Tab';
import CardSearchContainer from '../CardSearch/CardSearchContainer';
import DeckList from './DeckList.js';
class DeckMobileDisplay extends React.Component {
render() {
return (
<TabContainer>
<Tab title="DeckList">
<DeckList
deck={this.props.deck}
addCard={this.props.addCard}
removeCard={this.props.removeCard}
/>
</Tab>
<Tab title="Search">
<CardSearchContainer
addCard={this.props.addCard}
removeCard={this.props.removeCard}
/>
</Tab>
<Tab title="Stats">
<p>stats coming soon...</p>
</Tab>
</TabContainer>
);
}
}
DeckMobileDisplay.propTypes = {
deck: PropTypes.object.isRequired,
addCard: PropTypes.func.isRequired,
removeCard: PropTypes.func.isRequired
}
export default DeckMobileDisplay;
Related Actions
export function createDeck(deck) {
return dispatch => {
dispatch(beginAjaxCall());
const config = {
method: 'POST',
headers: { 'Content-Type' : 'application/json' },
body : JSON.stringify({deck: deck})
};
return fetch(`http://localhost:3000/users/${deck.userId}/decks`, config)
.then(res => res.json().then(deck => ({deck, res})))
.then(({res, deck}) => {
if (res.status >= 200 && res.status < 300) {
dispatch(createDeckSuccess(deck.deck));
}
else
dispatch(createDeckFailure(deck));
})
.catch(err => {
console.log(err);
dispatch(ajaxCallError(err));
});
};
}
export function updateDeck(deck) {
return dispatch => {
dispatch(beginAjaxCall());
const body = JSON.stringify({deck: deck});
const config = {
method: 'PUT',
headers : { 'Content-Type' : 'application/json' },
body: body
};
return fetch(`http://localhost:3000/decks/${deck.id}`, config)
.then(res => res.json().then(deck => ({deck, res})))
.then(({res, deck}) => {
if (res.status >= 200 && res.status < 300) {
dispatch(updateDeckSuccess(deck.deck));
}
dispatch(ajaxCallError(err));
})
.catch(err => {
console.log(err);
dispatch(ajaxCallError(err));
});
};
}
export function updateDeckSuccess(deck) {
return {
type: types.UPDATE_DECK_SUCCESS,
deck
};
}
And my deck Reducer
import * as types from '../actions/actionTypes';
import initialState from './initialState';
export default function deckReducer(state = initialState.decks, action) {
switch (action.type) {
case types.LOAD_USERS_DECKS_SUCCESS:
return action.decks;
case types.CREATE_DECK_SUCCESS:
return [
...state,
Object.assign({}, action.deck)
]
case types.UPDATE_DECK_SUCCESS:
return [
...state.filter(deck => deck.id !== action.deck.id),
Object.assign({}, action.deck)
]
default:
return state;
}
}
If you need to see more of the app the repo is here:
https://github.com/dgravelle/magic-redux
Any kind of help would be appreciated, thanks!
Your problem is caused because you are modifying component's state manually.
One Redux's principle is:
State is read-only
The only way to change the state is to emit an action, an object
describing what happened.
This ensures that neither the views nor the network callbacks will
ever write directly to the state. Instead, they express an intent to
transform the state. Because all changes are centralized and happen
one by one in a strict order, there are no subtle race conditions to
watch out for. As actions are just plain objects, they can be logged,
serialized, stored, and later replayed for debugging or testing
purposes.
In the method removeCard you are modifying the state:
removeCard(board, cardName) {
let deck = this.state.deck;
//This is just a reference, not a clone
let cards = this.state.deck.cards;
cards[board].forEach(i => {
if(i.name === cardName) {
if (i.quantity === 1) {
//Here you are modifying cards, which is a pointer to this.state.deck.cards
cards[board].splice(cards[board].indexOf(i), 1);
}
else {
//Here you are modifying cards, which is a pointer to this.state.deck.cards
i.quantity -= 1;
}
}
});
//... more stuff
}
One concept you might be missing is that this.state.deck.cards is a reference/pointer to the Array's memory position. You need to clone it if you want to mutate it.
One solution could be to clone the original array instead:
removeCard(board, cardName) {
let deck = this.state.deck;
//Here you are cloning the original array, so cards references to a totally different memory position
let cards = Object.assign({}, this.state.deck.cards);
cards[board].forEach(i => {
if(i.name === cardName) {
if (i.quantity === 1) {
cards[board].splice(cards[board].indexOf(i), 1);
}
else {
i.quantity -= 1;
}
}
});
//... more stuff
}
Hope it helps you.

Resources