I did an example of multi async XMLHttpReqest based on examples i found:
var URL=["https://api.github.com/users/github","https://api.github.com/users/github/repos"];
var xhr = [null, null];
for (var i = 0; i < 2; i++) {
(function(i) {
xhr[i] = new XMLHttpRequest();
xhr[i].open("GET", URL[i], true);
xhr[i].onload = function (e) {
if (xhr[i].readyState === 4) {
if (xhr[i].status === 200) {
console.log(xhr[i].responseText);
} else {
console.error(xhr[i].statusText);
}
}
};
xhr[i].onerror = function (e) {
console.error(xhr[i].statusText);
};
xhr[i].send(null);
})(i);
}
Thing is that i am having a problem with implementing it in react as i can not assign value with this.setState({json_objs[i]:JSON.parse(xhr[i].responseText}) to object array.
This is my react code which is not working due problems with assigning value:
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
class ImageViewer extends React.Component {
constructor() {
super();
this.state = {
json_objs : [null,null],
links:["https://api.github.com/users/github/repos","https://api.github.com/users/github"]
}
}
componentDidMount() {
var xhr = [null, null]
for (var i = 0; i < 2; i++) {
(function(i) {
xhr[i] = new XMLHttpRequest();
xhr[i].open("GET", this.state.link, true);
xhr[i].onload = function (e) {
if (xhr[i].readyState === 4) {
if (xhr[i].status === 200) {
this.setState({ json_objs[i]:JSON.parse(xhr[i].responseText)});
} else {
console.error(xhr[i].statusText);
}
}
}.bind(this);
xhr[i].onerror = function (e) {
console.error(xhr[i].statusText);
};
xhr[i].send(null);
})(i);
}
render() {
if (!this.state.json_objs[0] || !this.state.json_objs[1]) {
return <div>Loading...</div>;
}
return(
<div>
<div><h1>{this.state.json_objs[1].email}</h1></div>
{this.state.json_objs[0].sort(function(a, b){var keyA = new Date(a.updated_at),keyB = new Date(b.updated_at);if(keyA > keyB) return -1;if(keyA < keyB) return 1;return 0;}).map((obj, index) => {
return (
<div key={index}>
<div>{obj.name}</div>
<div>{(new Date(obj.updated_at)).toString()}</td>
</div>
)
})}
</div>
)
}}
ReactDOM.render(<ImageViewer />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I have been reading about modyfing arrays in react but i am having problems with implementing it.
I think Promise's would be helpful here. Wrap each XHR request in a Promise, use Promise.all() to wait for both of them complete, then set your results into state (it will resolve with an array of response objects):
Promise.all([0, 1].map((index) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open("GET", this.state.links[index], true)
xhr.onload = function() {
if (xhr.readyState === 4) {
if(xhr.status === 200) {
resolve(JSON.parse(xhr.responseText))
} else {
reject(xhr.statusText)
}
}
})
})).then((json_objs) => {
this.setState({ json_objs }, () => {
console.log('json_objs => ', this.state.json_objs)
})
}).catch(err => console.error(err))
Related
I have a parent component which has timer component inside it. Timer starts at 15 minutes and count downs till 0. When my timer shows time as 0 I want to trigger a submit button event, submit button is inside Quiz Component (Quiz Component is also a child component of Parent Component). I found probably I can use MutationObserver when p tag changes. I am not sure whether it's the correct and only approach or there is better way to achieve this.
Parent Component:
import React, { Component } from 'react';
import '../css/App.css'
import Quiz from './Quiz';
import Timer from './Timer';
import { connect } from 'react-redux';
import { ActionTypes } from '../redux/constants/actionTypes';
import { saveQuizAll, getQuizIndex } from '../commonjs/common.js';
const mapStateToProps = state => { return { ...state.quiz, ...state.quizAll } };
const mapDispatchToProps = dispatch => ({
onQuizLoad: payload => dispatch({ type: ActionTypes.QuizLoad, payload }),
onQuizChange: payload => dispatch({ type: ActionTypes.QuizAnswerAll, payload }),
onPagerUpdate: payload => dispatch({ type: ActionTypes.PagerUpdate, payload })
});
class QuizContainer extends Component {
state = {
quizes: [
{ id: 'data/class1.json', name: 'Class 1' },
{ id: 'data/class2.json', name: 'Class 2' },
{ id: 'data/class3.json', name: 'Class 3' },
{ id: 'data/class4.json', name: 'Class 4' },
],
quizId: 'data/class1.json'
};
pager = {
index: 0,
size: 1,
count: 1
}
componentDidMount() {
console.log('componentDidMount');
this.load(this.state.quizId);
}
load(quizId, isValReload) {
console.log('In load');
let url = quizId || this.props.quizId;
if (isValReload) {
let quiz = this.props.quizAll.find(a => url.indexOf(`${a.id}.`) !== -1);
console.log('In load quiz : ', quiz);
this.pager.count = quiz.questions.length / this.pager.size;
this.props.onQuizLoad(quiz);
this.props.onPagerUpdate(this.pager);
}
else {
fetch(`../${url}`).then(res => res.json()).then(res => {
let quiz = res;
quiz.questions.forEach(q => {
q.options.forEach(o => o.selected = false);
});
quiz.config = Object.assign(this.props.quiz.config || {}, quiz.config);
this.pager.count = quiz.questions.length / this.pager.size;
this.props.onQuizLoad(quiz);
this.props.onPagerUpdate(this.pager);
});
}
}
//This event implements restriction to change class without finishing curretnly selectd class
onClassClick = (e) => {
let qus = this.props.quiz.questions;
// console.log(qus);
let isNotAllAns = qus.some((q, i) => {
var isNot = false;
if (q.answerType.id !== 3 && q.answerType.id !== 4) {
isNot = (q.options.find((o) => o.selected === true)) === undefined;
}
else {
// console.log('q', q);
isNot = ((q.answers === "" || q.answers.length === 0));
}
return isNot;
});
if (isNotAllAns) {
alert('Please complete the quiz.');
e.stopPropagation();
}
}
/*
saveQuizAll(_quizAll, _quiz) {
let allQuiz = [];
// , _quizAll, _quiz;
// if (true) {
// _quiz = this.quiz;
// _quizAll = this.quizAll;
// }
console.log(this, _quiz, _quizAll);
if (_quiz.questions.length !== 0) {
if (_quizAll.length !== undefined) {
console.log('Not Initial Setup Splice', _quiz.id);
allQuiz = _quizAll;
const qIndex = this.getQuizIndex(_quiz.id.toString());
if (qIndex > -1) {
allQuiz.splice(qIndex, 1, _quiz);
}
else {
allQuiz.splice(_quizAll.length, 0, _quiz);
// allQuiz.splice(this.props.quizAll.length-1, 0, this.props.quizAll, this.props.quiz);
}
}
else {
allQuiz[0] = _quiz;
}
return allQuiz;
// if (true) {
// this.onQuizChange(allQuiz);
// }
}
}
*/
onChange = (e) => {
// console.log(this.props.quizAll, this.props.quizAll.length);
let allQuiz = [];
allQuiz = saveQuizAll(this.props.quizAll, this.props.quiz);
//below code converted into saveQuizAll funstion
/*
if (this.props.quizAll.length !== undefined) {
console.log('Not Initial Setup Splice', this.props.quiz.id);
allQuiz = this.props.quizAll;
const qIndex = this.getQuizIndex(this.props.quiz.id.toString());
if (qIndex > -1) {
allQuiz.splice(qIndex, 1, this.props.quiz);
}
else {
allQuiz.splice(this.props.quizAll.length, 0, this.props.quiz);
// allQuiz.splice(this.props.quizAll.length-1, 0, this.props.quizAll, this.props.quiz);
}
}
else {
allQuiz[0] = this.props.quiz;
}
*/
// console.log('allQuiz Out - ', allQuiz);
this.props.onQuizChange(allQuiz);
console.log('Check QuizAll - ', this.props.quizAll);
const aQuiz = JSON.parse(JSON.stringify(this.props.quizAll));
this.setState({ quizId: e.target.value });
if (aQuiz.length !== undefined && getQuizIndex(this.props.quizAll, e.target.value) > -1) {
// console.log(aQuiz.findIndex(a => e.target.value.indexOf(`${a.id}.`) !== -1));
this.load(e.target.value, true);
}
else {
this.setState({ quizId: e.target.value });
this.load(e.target.value, false);
}
}
// getQuizIndex(qID) {
// return this.props.quizAll.findIndex(a => (qID.indexOf(`${a.id}.`) !== -1 || qID.indexOf(`${a.id}`) !== -1));
// }
render() {
return (
<div className="container">
<header className="p-2">
<div className="row">
<div className="col-6">
<h3>DADt Application</h3>
</div>
<div className="col-6 text-right">
<label className="mr-1">Select Quiz:</label>
<select onChange={this.onChange} onClick={this.onClassClick}>
{this.state.quizes.map(q => <option key={q.id} value={q.id}>{q.name}</option>)}
</select>
</div>
</div>
</header>
<Timer duration={900}/>
<Quiz quiz={this.state.quiz} quizId={this.state.quizId} saveAll={saveQuizAll} mode={this.state.mode} />
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(QuizContainer);
Here is my Timer Component
import React, { Component } from 'react'
class Timer extends Component {
constructor(props) {
super(props);
this.state = {
seconds: 0
};
}
tick() {
this.setState((prevState) => ({
seconds: prevState.seconds + 1
}));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const { duration } = this.props;
let timeLeft = duration - this.state.seconds;
timeLeft = Number(timeLeft);
let minutes = Math.floor(timeLeft % 3600 / 60);
let seconds = Math.floor(timeLeft % 3600 % 60);
let minutesDisplay = minutes > 0 ? minutes + (minutes === 1 ? " : " : " : ") : "";
let secondsDisplay = seconds > 0 ? seconds + (seconds === 1 ? "" : "") : "";
return <p className="badge badge-success">Time Left: {minutesDisplay}{secondsDisplay}</p>;
}
}
export default Timer;
Quiz Component:
import React, { Component } from 'react';
import { ActionTypes } from '../redux/constants/actionTypes';
import Review from './Review';
import Questions from './Questions';
import Result from './Result';
import { connect } from 'react-redux';
// import { saveQuizAll } from '../commonjs/common.js';
const mapStateToProps = state => { return { ...state.quiz, ...state.mode, ...state.pager, ...state.quizAll } };
const mapDispatchToProps = dispatch => ({
onSubmit: payload => dispatch({ type: ActionTypes.QuizSubmit, payload }),
onQuizChange: payload => dispatch({ type: ActionTypes.QuizAnswerAll, payload }),
onPagerUpdate: payload => dispatch({ type: ActionTypes.PagerUpdate, payload })
});
class Quiz extends Component {
move = (e) => {
let id = e.target.id;
let index = 0;
if (id === 'first')
index = 0;
else if (id === 'prev')
index = this.props.pager.index - 1;
else if (id === 'next') {
index = this.props.pager.index + 1;
}
else if (id === 'last')
index = this.props.pager.count - 1;
else
index = parseInt(e.target.id, 10);
if (index >= 0 && index < this.props.pager.count) {
let pager = {
index: index,
size: 1,
count: this.props.pager.count
};
this.props.onPagerUpdate(pager);
}
}
saveStore(e) {
let allQuiz = [];
console.log(this, e);
allQuiz = this.props.saveAll(e.props.quizAll, e.props.quiz);
console.log(allQuiz);
this.props.onQuizChange(allQuiz);
}
setMode = (e) => this.props.onSubmit(e.target.id);
// setMode(e) {
// console.log('in mode',e);this.props.onSubmit(e.target.id);
// }
renderMode() {
console.log('Inside here', this.props.mode);
if (this.props.mode === 'quiz') {
return (<Questions move={this.move} />)
} else if (this.props.mode === 'review') {
return (<Review quiz={this.props.quiz} move={this.move} />)
} else {
console.log('Before Results');
const divSel = document.querySelector('div.col-6.text-right');
// console.log('divSel', divSel);
if (divSel) {
divSel.style.display = "none";
}
return (<Result questions={this.props.quizAll || []} />)
}
}
render() {
return (
<div>
{this.renderMode()}
{(this.props.mode !== 'submit') &&
<div>
<hr />
<button id="quiz" className="btn btn-primary" onClick={this.setMode}>Quiz</button>
<button id="review" className="btn btn-primary" onClick={this.setMode}>Review</button>
<button id="submit" className="btn btn-primary" onClick={(e) => {this.setMode(e); this.saveStore(this)}}>Submit Quiz</button >
</div >}
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Quiz);
I think you can have two approaches.
1. The "react" way
In the Parent component:
// ...
constructor(props) {
// ...
this.state = {
timeExpired: false
};
}
const onTimeExpired = () => {
this.setState({timeExpired: true});
}
// ...
render() {
return (
<div className="container">
{ // ... }
<Timer duration={900} onTimeExpired={onTimeExpired}/>
<Quiz quiz={this.state.quiz} quizId={this.state.quizId} saveAll={saveQuizAll} mode={this.state.mode} triggerSubmit={this.state.timeExpired} />
</div>
);
}
In the Timer component:
// ...
componentDidUpdate() {
if (this.state.seconds === this.props.duration) {
this.props.onTimeExpired();
}
}
// ...
In the Quiz component:
// ...
componentDidUpdate() {
if (this.props.triggerSubmit) {
// Do whatever you do on submit
}
}
// ...
2. The "quick and dirty" way:
In the Timer component
// ...
componentDidUpdate() {
if (this.state.seconds === this.props.duration) {
const quizForm = document.getElementById('quizFormId');
quizForm && quizForm.submit();
}
}
// ...
Provide a prop method onTimeFinished in your Timer component. Then in your render function you can add
{ !(this.props.duration-this.state.seconds) && this.props.onTimeFinished() }
Reference: React Conditional Rendering
try this:
Parent Component:
// state
state = {
triggerSubmit: false
}
// functions
doSubmit = () => {
this.setState({ triggerSubmit: true });
}
resetSubmit = () => {
this.setState({ triggerSubmit: false });
}
// jsx
<Timer duration={900} doSubmit={this.doSubmit} />
<Quiz
quiz={this.state.quiz}
quizId={this.state.quizId}
saveAll={saveQuizAll}
mode={this.state.mode}
resetSubmit={this.resetSubmit}
triggerSubmit={this.state.triggerSubmit} />
Timer Component:
// function
doSubmit = (timeLeft) => {
if (timeLeft === 0) {
this.props.doSubmit();
}
}
// jsx
<p className="badge badge-success"
onChange={() => {this.doSubmit(timeLeft)}>
Time Left: {minutesDisplay}{secondsDisplay}
</p>
Quiz Component:
// state
state = {
triggerSubmit: this.props.triggerSubmit
}
// function
triggerSubmit = () => {
if (this.state.triggerSubmit) {
your trigger submit code here...
this.props.resetSubmit();
}
}
React Newbie here,
import React, { Component } from "react";
class AudioList extends Component {
constructor(props) {
super(props);
this.audios = [];
this.buttonText = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios.push(new Audio(this.props.songs[i].song_url));
this.buttonText.push(String(i));
}
this.state = {
songs: "",
buttonText: this.buttonText
};
}
componentWillMount() {
const songs = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].addEventListener("play", () => {
let stateArray = [...this.state.buttonText];
let stateArrayElement = { ...stateArray[i] };
stateArrayElement = "playing";
stateArray[i] = stateArrayElement;
console.log(stateArray);
this.setState({ buttonText: stateArray });
console.log(this.state.buttonText[i]);
});
songs.push(
<div className="song-preview">
<button
className="preview"
onClick={() => this.toggle(this.audios[i])}
>
{this.state.buttonText[i]}
</button>
</div>
);
}
this.setState({
songs: songs
});
}
componentWillUnmount() {
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].pause();
}
}
getCurrentAudio() {
return this.audios.find(audio => false === audio.paused);
}
toggle(nextAudio) {
const currentAudio = this.getCurrentAudio();
if (currentAudio && currentAudio !== nextAudio) {
currentAudio.pause();
nextAudio.play();
}
nextAudio.paused ? nextAudio.play() : nextAudio.pause();
}
render() {
if (this.state.songs) {
return <div className="song-list">{this.state.songs}</div>;
} else {
return <div className="song-list"></div>;
}
}
}
export default AudioList;
I am using this code from a previous solution that I found on Stackoverflow (https://stackoverflow.com/a/50595639). I was able to implement this solution to solve my own challenge of needing to have multiple audio sources with one audio player and multiple buttons. However, I am now faced with a new challenge - I want a specific button's text to change when an event is fired up.
I came up with this implementation where the button text is based on an array in the state called buttonText. The buttons are rendered correctly on startup, but when the event listener picks up the event and changes the state, the text in the button is not re-rendering or changing, even though it is based on an element in an array in the state that is changing.
Does anyone have any suggestions about why it may be failing to re-render?
EDIT: Changing an individual array element in the state is based on React: how to update state.item[1] in state using setState?
I have restructured your code a bit (but it's untested it's tested now):
const songs = [
{
title: "small airplane long flyby - Mike_Koenig",
song_url: "http://soundbible.com/mp3/small_airplane_long_flyby-Mike_Koenig-806755389.mp3"
},
{
title: "Female Counts To Ten",
song_url: "http://soundbible.com/mp3/Female%20Counts%20To%20Ten-SoundBible.com-1947090250.mp3"
},
];
class AudioList extends React.Component {
audios = new Map();
state = {
audio: null,
song: null
};
componentDidUpdate() {
// stop playing if the song has been removed from props.songs
if (this.state.song && !this.props.songs.some(song => song.song_url === this.state.song.song_url)) {
this.toggle(this.state.audio, this.state.song);
}
}
componentWillUnmount() {
if (this.state.audio) {
this.state.audio.pause();
}
this.audios.clear();
}
toggle(audio, song) {
this.setState(state => {
if (audio !== state.audio) {
if (state.audio) {
state.audio.pause();
}
audio.play();
return { audio, song };
}
audio.pause();
return { audio: null, song: null };
});
}
getAudio(song) {
let audio = this.audios.get(song.song_url);
if (!audio) {
this.audios.set(song.song_url, audio = new Audio(song.song_url));
}
return audio;
}
render() {
return <div className="song-list">{
this.props.songs.map((song, i) => {
const audio = this.getAudio(song);
const playing = audio === this.state.audio;
return <div className="song-preview">
<button
className="preview"
onClick={this.toggle.bind(this, audio, song)}
>
{playing ? "playing" : (song.title || i)}
</button>
</div>
})
}</div>;
}
}
ReactDOM.render(<AudioList songs={songs} />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Edit: added a title to the song-objects and display them on the buttons
I simply moved all of the code from componentWillMount() to render(). I also removed 'songs' as a state variable and set it to a variable that exists only in render as songs is simply just a set of divs.
import React, { Component } from "react";
const audio1 =
"http://soundbible.com/mp3/small_airplane_long_flyby-Mike_Koenig-806755389.mp3";
const audio2 =
"http://soundbible.com/mp3/Female%20Counts%20To%20Ten-SoundBible.com-1947090250.mp3";
class AudioList extends Component {
constructor(props) {
super(props);
this.audios = [];
this.buttonText = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios.push(new Audio(this.props.songs[i]));
this.buttonText.push(String(i));
}
this.state = {
buttonText: this.buttonText
};
}
componentWillUnmount() {
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].pause();
}
}
getCurrentAudio() {
return this.audios.find(audio => false === audio.paused);
}
toggle(nextAudio) {
const currentAudio = this.getCurrentAudio();
if (currentAudio && currentAudio !== nextAudio) {
currentAudio.pause();
nextAudio.play();
}
nextAudio.paused ? nextAudio.play() : nextAudio.pause();
}
render() {
const songs = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].addEventListener("play", () => {
console.log("playing");
let stateArray = [...this.state.buttonText];
let stateArrayElement = { ...stateArray[i] };
stateArrayElement = "playing";
stateArray[i] = stateArrayElement;
console.log(stateArray);
this.setState({ buttonText: stateArray });
console.log(this.state.buttonText);
});
songs.push(
<div className="song-preview">
<button
className="preview"
onClick={() => this.toggle(this.audios[i])}
>
{this.state.buttonText[i]}
</button>
</div>
);
}
return (
<div>{songs}</div>
)
}
}
export default () => <AudioList songs={[audio1, audio2]} />;
The code now runs as expected.
I am simply looking to save and restore a search term(form data) when a page is refreshed/reloaded. I have tried several solutions to no avail.
Flow: A user submits a search term and is taken to Spotify to retrieve an accessToken, if it is not already available. The initial page is refreshed once the accessToken is retrieved, but the search must be re-entered. This is not good UX.
I concluded that Web Storage was they way to go, of course it is not the only route. I am not sure if this is something that should be relegated to Lifecycle methods: componentDidMount() & componentDidUpdate(). Perhaps that is overkill? In any event, I attempted to employ both localStorage and sessionStorage. My implementation is obviously off as I am not getting the expected result. React dev tools displays the state of the SearchBar term, but it is not being saved. Also of note is the following: React dev tools shows that the onSubmit event handler is registering as bound () {} instead of the expected bound handleInitialSearchTerm() {}. The console also shows that there are no errors.
No third-party libraries please.
SearchBar.js
import React from 'react';
import "./SearchBar.css";
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
term: this.handleInitialSearchTerm
};
this.search = this.search.bind(this);
this.handleInitialSearchTerm = this.handleInitialSearchTerm.bind(this);
this.setSearchTerm = this.setSearchTerm.bind(this);
this.handleSearchOnEnter = this.handleSearchOnEnter.bind(this);
this.handleTermChange = this.handleTermChange.bind(this);
}
handleInitialSearchTerm = (event) => {
if (typeof (Storage) !== "undefined") {
if (localStorage.term) {
return localStorage.term
} else {
return this.setSearchTerm(String(window.localStorage.getItem("term") || ""));
}
}
};
setSearchTerm = (term) => {
localStorage.setItem("term", term);
this.setState({ term: term });
}
search() {
this.props.onSearch(this.state.term);
}
handleSearchOnEnter(event) {
if (event.keyCode === 13) {
event.preventDefault();
this.search();
}
}
handleTermChange(event) {
this.setState({
term: event.target.value
});
}
render() {
return (
<div className="SearchBar">
<input
placeholder="Enter A Song, Album, or Artist"
onChange={this.handleTermChange}
onKeyDown={this.handleSearchOnEnter}
onSubmit={this.handleInitialSearchTerm}
/>
<button className="SearchButton" onClick={this.search}>
SEARCH
</button>
</div>
);
}
}
export default SearchBar;
Motify.js
let accessToken;
const clientId = "SpotifyCredentialsHere";
const redirectUri = "http://localhost:3000/";
const CORS = "https://cors-anywhere.herokuapp.com/"; // Bypasses CORS restriction
const Motify = {
getAccessToken() {
if (accessToken) {
return accessToken;
}
// if accessToken does not exist check for a match
const windowURL = window.location.href;
const accessTokenMatch = windowURL.match(/access_token=([^&]*)/);
const expiresInMatch = windowURL.match(/expires_in=([^&]*)/);
if (accessTokenMatch && expiresInMatch) {
accessToken = accessTokenMatch[1]; //[0] returns the param and token
const expiresIn = Number(expiresInMatch[1]);
window.setTimeout(() => accessToken = "", expiresIn * 1000);
// This clears the parameters, allowing us to grab a new access token when it expires.
window.history.pushState("Access Token", null, "/");
return accessToken;
} else {
const accessUrl = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=token&scope=playlist-modify-public&redirect_uri=${redirectUri}`;
window.location = accessUrl;
}
},
search(term) {
const accessToken = Motify.getAccessToken();
const url = `${CORS}https://api.spotify.com/v1/search?type=track&q=${term}`;
return fetch(url, { headers: { Authorization: `Bearer ${accessToken}` }
}).then(response => response.json()
).then(jsonResponse => {
if (!jsonResponse.tracks) {
return [];
}
return jsonResponse.tracks.items.map(track => ({
id: track.id,
name: track.name,
artist: track.artists[0].name,
album: track.album.name,
uri: track.uri,
preview_url: track.preview_url
}));
})
}
...
Please check the code I have added.
Changes I did are below:
1)
this.state = {
term: JSON.parse(localStorage.getItem('term')) || '';
};
setSearchTerm = (term) => {
this.setState({
term: term
},
() => {
localStorage.setItem('term', JSON.stringify(this.state.term)));
}
import React from 'react';
import "./SearchBar.css";
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
term: JSON.parse(localStorage.getItem('term')) || '';
};
this.search = this.search.bind(this);
this.handleInitialSearchTerm = this.handleInitialSearchTerm.bind(this);
this.setSearchTerm = this.setSearchTerm.bind(this);
this.handleSearchOnEnter = this.handleSearchOnEnter.bind(this);
this.handleTermChange = this.handleTermChange.bind(this);
}
handleInitialSearchTerm = (event) => {
if (typeof(Storage) !== "undefined") {
if (localStorage.term) {
return localStorage.term
} else {
return this.setSearchTerm(String(window.localStorage.getItem("term") || ""));
}
}
};
setSearchTerm = (term) => {
this.setState({
term: term
},
() => {
localStorage.setItem('term', JSON.stringify(this.state.term)));
}
search() {
this.props.onSearch(this.state.term);
}
handleSearchOnEnter(event) {
if (event.keyCode === 13) {
event.preventDefault();
this.search();
}
}
handleTermChange(event) {
this.setState({
term: event.target.value
});
}
render() {
return ( <
div className = "SearchBar" >
<
input placeholder = "Enter A Song, Album, or Artist"
onChange = {
this.handleTermChange
}
onKeyDown = {
this.handleSearchOnEnter
}
onSubmit = {
this.handleInitialSearchTerm
}
/> <
button className = "SearchButton"
onClick = {
this.search
} >
SEARCH <
/button> <
/div>
);
}
}
export default SearchBar;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
If it is in hooks i would have done like below:
import React, {
useEffect,
useState,
useRef,
} from 'react';
function App() {
const [value, setValue] = useState(() => {
if (localStorage.getItem('prevCount') === null) {
return 0;
} else {
return localStorage.getItem('prevCount');
}
});
const countRef = useRef();
useEffect(() => {
countRef.current = value;
if (countRef.current) {
localStorage.setItem('prevCount', countRef.current);
} else {
localStorage.setItem('prevCount', 0);
}
});
const handleIncrement = () => {
setValue((value) => +value + 1);
};
const handleDecrement = () => {
if (value === 0) {
return;
} else {
setValue((value) => value - 1);
}
};
return (
<div className="card">
<label className="counterLabel">Simple Counter</label>
<button
className="button"
onClick={handleIncrement}
>
Increment
</button>
<span className="count">{value}</span>
<button
className="button"
onClick={handleDecrement}
>
Decrement
</button>
</div>
);
}
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
So what the above code is doing is that when we inititalize the state value we first check the localStorage , if "term" has value in localStorage we will use that value or else an empty string is initialized.
Using callback of setState inside the method setSearchTerm we set the term value immediately
Try the useLocalStorage hook to save search client side.
// useLocalStorage Hook to persist values client side
function useLocalStorage(key, initialValue) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
if (typeof window === "undefined") {
return initialValue;
}
try {
// Get from local storage by key
const item = window.localStorage.getItem(key);
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
// Save to local storage
if (typeof window !== "undefined") {
window.localStorage.setItem(key, JSON.stringify(valueToStore));
}
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error);
}
};
return [storedValue, setValue];
}
credit: Brandon Baars
i want to cancel the load_items request on componentWillUnmount. I am not sure how to do it? could someone help me with this. Thanks.
What i have tried to do?
I have a parent component ViewItems and child component ItemsList. In the ViewItems component i retrieve the items list from the server using load_items method which in turn uses client.get_file method. and store those items list in state named "items". I call this load_items method in componenDidMount method.However doing so it sometimes displays items for previous item. This happens sometimes. I would like to have the proper item details in the state.
I think i have to cancel the load_items request before updating the state again.
Below is the code ,
class ViewItems extends React.PureComponent {
constructor(props) {
super(props);
this.default_root_item = {
name: 'Items',
index: 0,
children: [],
};
this.state = {
root_items: this.default_root_item,
items: [],
};
}
componentDidMount() {
this.load_items();
}
componentDidUpdate(PrevProps) {
if (PrevProps.item_id !== this.props.item_id) {
this.load_items();
}
}
componentWillUnmount() {
this.unlisten_path_change();
}
load_items = () => {
const file_name = 'file_name.json';
client.get_file(this.props.item_id, file_name, 'json')
.then((request) => {
const items = request.response;
this.setState({items: [this.default_root_item]});}
this.handle_path_change(this.props.location.pathname);})};
return (
<ChildComponent
on_close={this.handle_item_close}
root_item={this.state.root_item}/>)}
export default class ChildComponent extends React.PureComponent {
<Items
items={root_item}/>
function Items(props) {
return (
<ul className="Items_list">
<div className="items">
{props.items.map((item, index) => {
return (
<Item
key={index}
item={item}
/>
);
})}
</div>
</ul>
);
}
}
export function get_file(qrcode, file_name, response_type,on_progress, cancel) {
const local_override_defined = item_files[qrcode] && item_files[qrcode][file_name];
if (local_override_defined) {
const file = item_files[qrcode][file_name];
const reader = new FileReader();
return new Promise(resolve => {
if (response_type === 'blob') {
resolve({response: file});
} else {
reader.onload = () => {
if (response_type === 'json') {
resolve({response: JSON.parse(reader.result)});
} else {
resolve({response: reader.result});
}
};
reader.readAsText(file);
}
});
}
return new Promise((resolve, reject) => {
item_file_get_url(qrcode, file_name).then(({response}) => {
const request = new XMLHttpRequest();
request.addEventListener('progress', on_progress);
request.open('GET', response.url);
request.responseType = response_type;
send_request(request, undefined, cancel, response.url).then(resolve).catch(reject);
}).catch(reject);
});
}
Consider for a moment the following code:
import React from 'react';
import ReactDOM from 'react-dom/dist/react-dom';
import chunk from 'lodash/array/chunk';
class Tweets extends React.Component {
constructor(props) {
super(props);
this.state = {
tweets: null,
error: null,
info: null,
source: null
}
}
componentDidMount() {
this._isMounted = true;
this.startPolling();
}
componentDidUpdate() {
if (this._timer) {
clearInterval(this._timer);
this._timer = null;
}
}
componentWillUnmount() {
this._isMounted = false;
if (this._timer) {
clearInterval(this._timer);
this._timer = null;
}
}
shouldComponentUpdate(nextProps, nextState) {
for (var key in this.state) {
for (var nextKey in nextState) {
if (this.state[key] !== nextState[nextKey]) {
return true
}
}
}
return false;
}
startPolling() {
var self = this;
setTimeout(function() {
self.poll();
if (!self._isMounted) {
return;
}
self._timer = setInterval(self.poll, 10000);
}, 1000);
}
poll() {
console.log('I should be called ....');
var self = this;
var source = null;
if (this.props !== undefined) {
source = this.props.source
}
$.get(source, function(result) {
if (self._isMounted) {
self.setState({
error: null,
tweets: result.data,
source: source
});
}
}).fail(function(response) {
self.setState({
error: 'Could not fetch tweets. Looks like something went wrong.',
});
});
}
checkIfLoading() {
if (this.state.tweets === null &&
this.state.error === null &&
this.state.info === null) {
return true;
}
return false;
}
buildTweets () {
let rows = [];
let currRow;
this.state.tweets.forEach((tweet, i) => {
if(i % 3 === 0) {
currRow = i;
rows[currRow] = [];
}
rows[currRow].push(
<div className='col-lg-4 col-md-6 col-sm-6 col-xs-12' key={tweet.id}>
<div className="white-panel tweet-panel">
<span><strong>{tweet.attributes.user_name}</strong><em>Posted: {tweet.attributes.time_ago}</em></span>
<p><span dangerouslySetInnerHTML={{__html: tweet.attributes.tweet}} /></p>
</div>
</div>
);
});
return rows;
}
render() {
if (this.checkIfLoading()) {
return (<div><i className="fa-li fa fa-spinner fa-spin"></i> One second please, while I fetch some tweets ....</div>);
}
var key = 0;
var tweets = this.buildTweets().map(function(tweetRows){
key += 1;
return (
<div key={key} className="row">
{tweetRows}
</div>
)
});
return (
<div id='tweetRowContainer'>
{tweets}
</div>
);
}
}
var tweetsElement = document.getElementById("tweets");
if (tweetsElement !== null) {
ReactDOM.render(
<Tweets source={"//" + location.hostname + "/api/v1/fetch-tweets"} />,
tweetsElement
);
}
module.exports = Tweets;
What we care about the most is the:
poll() {
console.log('I should be called ....');
...
This gets console logged the first time when the data gets fetched and then is displayed to the page, but as we can see just before this method:
startPolling() {
var self = this;
setTimeout(function() {
self.poll();
if (!self._isMounted) {
return;
}
self._timer = setInterval(self.poll, 10000);
}, 1000);
}
In theory this should be polling .... no?