I am trying to create a wordcloud inside a React container using this library. The wordcloud works when passed the array test.
I would like to call an api to get the words for the wordcloud and then pass it to the wordcloud library. Below is the code I am using to fetch from the api an array of words and then pass to the wordcloud library.
function getWords() {
return fetch('http://bio-wordcloud-dev.us-east-1.elasticbeanstalk.com/getWords/?format=json')
.then((response) => response.json())
.then((responseJson) => {
return responseJson;
})
.catch((error) => {
console.error(error);
});
}
When debugging the return value in chrome, I see that elements in the response value are undefined. Here is the component:
import WordCloud from 'wordcloud'
class WordCloudComponent extends React.Component {
componentWillMount () {
var words = getWords();
var wordList = []
words.then(function(value) {
value.map(function(object){
wordList.push ([object.word,object.weight]);
});
});
this.setState({words: wordList});
}
componentDidMount() {
var canvas = ReactDOM.findDOMNode(this.refs.canvas)
var test = [["foo", 12], ["bar", 6]];
var words = this.state.words;
console.log(words);
console.log(test)
WordCloud(canvas, { list: test, color: "random-dark", shape: "circle", color:"green", wait: 0, backgroundColor:"black"});
}
render() {
return (
<div className={styles.cloudCanvasContainer}>
<canvas ref="canvas"></canvas>
</div>
);
}
}
My console logs show that both words and tests are arrays containing arrays of words and weight. As this is my first time using React/ Promises, I am unsure as to what is going wrong. Could someone please explain?
Your call to this.setState({words: wordList}); happens instantaneously after you asynchronously invoke getWords. In other words, wordList isn't yet populated with the returned results of the API when you setState. Instead, you should update your state only after you callback from the async API.
componentWillMount () {
getWords().then(function(value) {
this.setState({
words: value.map((obj) => [obj.word, obj.weight])
});
});
}
Modifying your logic like this also eliminates stateful mutation of the wordsList variable defined outside of the scope of your promise callback.
Related
I have a button called download, it is a component. The functionality is written in a container.
I want the button to be hidden as default, unless the "(i === number)" condition is true in the download function.
However, it is tricky, since this condition will only be validated when I click "download". I am not sure how do I validate this logic beforehand to determine if the button needs to be displayed or not.
Can you please help? I am trying to set a state for showing button, but it doesn't work.
My code basically looks like this -
container:
state = {
showButton: false,
};
componentDidMount() {
this.download();
};
download = async () => {
const data = await this.props.client
.query({
query: numberQuery,
fetchPolicy: "no-cache",
})
// retrive data from number query ....
const contentData = data.data.content;
// retrieve and format numbers
const numbers = this.getNumbers(contentData);
// call get number and get the individual number here
const number = await this.getNumber();
numbers.forEach((i) => {
// this is to check if numbers contain the number from getNumber(), if
// number matches number in numbers
if (i === number) {
this.setState({
showButton: true,
});
// call functions, start downloading
};
});
};
render() {
return (
{this.state.showButton ?
<Download
onStartDownload={() => this.download()}
/> : null}
);
};
component:
class Download extends Component {
state = {
startDownload: false,
};
startDownload = () => {
this.props.onStartDownload();
};
render() {
return (
<Fragment>
<Button
id="download"
onClick={this.startDownload}
>
Download
</Button>
</Fragment>
);
};
};
If I understand correctly, your problem is that the numbers are fetched (and, in turn, the logic for showing/hiding the button is run) only after the download button was clicked, but you want it to run as soon as possible (that is, on the component's mount).
Generally, the fetching of data in React is separated from the render/event handler logic. In your case, a solution is to fetch the data when the component mounts, save it in state (such as a numbers and number fields), then, when rendering, check if number is in the numbers array.
For example:
// Container component; child component remains the same
state = {
number: null,
numbers: []
};
componentDidMount() {
this.fetchNumbers();
this.fetchNumber();
};
async fetchNumbers() {
const data = await this.props.client
.query({
query: numberQuery,
fetchPolicy: 'no-cache',
});
// retrive data from number query ....
const contentData = data.data.content;
// retrieve and format numbers
const numbers = this.getNumbers(contentData);
this.setState({ numbers });
}
async fetchNumber() {
// Assuming this is another HTTP request or something similar
const number = await this.getNumber();
this.setState({ number });
}
download = async () => {
// *only* downloading logic
};
render() {
const { number, numbers } = this.state;
const showDownload = numbers.includes(number);
return showDownload
? <Download onStartDownload={() => this.download()}/>
: null;
};
}
Notes:
Using Array.includes() instead of an Array.forEach() loop simplifies the code (and will probably save you some bugs in the future!)
I separated fetchNumbers(), which fetches this.state.numbers, from fetchNumber(), which fetches this.state.number, simply because they seem like two separate pieces of state (and therefore fetching them independently is most efficient); you could improve it even further by having the two functions return the data (instead of changing the state), then using Promise.all() in componentDidMount() (I left it out for simplicity's sake).
Generally in web dev, a best practice when fetching core data asynchronously is to indicate that the component is not loaded until the data arrives (using a loader, for example). For practice or prototypes it might not be your top priority, but keep that in mind for production.
you need a constructor in your class component and to update the state you need to use setState(),
review the code to follow
:
class Download extends react.Component {
constructor() {
super();// required
this.state = {
startDownload: false
};
}
startDownload = () => {
this.setState((prevState) => ({
// I use prevState, to bring the previous state value
startDownload: !prevState.startDownload // here the value is inverted
}));
};
render() {
return (
<> // this is equivalent to the fragment
<button id="download" onClick={this.startDownload}>
Download
</button>
</>
);
}
}
I just started learning React and so far I'm liking it. But the problem is that most of the tutorials are old and still using old states instead of hooks.
Now when I'm following a tutorial, I'm trying to convert the code I see into hooks. For now I'm really stuck and could use some help for this code. I'm trying to convert the code bellow for a functionnal component. I tried using useEffect but it didn't work.
handleChangeOrder(oldIndex, newIndex){
if (oldIndex == newIndex) {return;}
const [todos] = this.state;
let newSequence =[];
todos.forEach((todos,index)=> {
newSequence.push({id: todo.id, order: index + 1 });
});
}
-------------------------------------------
static changeTodoOrderURL(){
return apiDomain + "api/todo/reorder";
}
----------------------------------------
async changeTodoOrder(order){
const url = UrlService.changeTodoOrderUrl();
try{
const response = await HttpService.post(url,{order});
return response.data;
} catch (error){console.error("Not able to change order of the todos")};
}
What I tried so far in converting it :
function handleChangeOrder(oldIndex, newIndex) {
if (oldIndex == newIndex) {
return;
}
const todos = Array.from(todolist);
let newSequence = [];
todos.forEach((todos, index) => {
newSequence.push({id: todo.id, order: index + 1 });
});
useEffect(()=>{
axios.post('http://localhost:8000/api/todo/reorder')
.then(response=>{
setList(response.data);
console.log(response)
})
.catch(err => console.log(err));
},[])
}
I'm struggling mainly with async changeTodoOrder..
You don't need to use useEffect to send API requests based on event change, useEffect used to send API requests/perform side effect based on state change or on the mounting of the component.
Also, you can't call the hooks inside a function, by using it this way you are breaking the rules of hooks.
please review the rules of Hooks here
based on your code above from the class component you can use it as it is without using useEffect
Don't understand what is wrong with my statement here, I keep looking up the error message on google but i really don't understand the problem.
componentDidMount()
{
fetch('http://192.168.1.33:8080/getprojects/')
.then(response => response.json())
.then(data => {
this.props.state({
projects: data.name
});
});
}
according to chrome's console it says that
Uncaught (in promise) TypeError: _this2.props.state is not a function
and points to this:
this.props.state({
projects: data.name
});
I am lost here. New to React JS trying to create a website that fetches data constantly (here i'm trying to fill a list under the format ul li by getting names on my Node Express server)
EDIT
here is the complete code before the Return inside the Render function :
class ProjectList extends Component {
constructor (props) {
super (props);
this.state = {
projects: [],
};
}
componentDidMount()
{
fetch('http://192.168.1.33:8080/getprojects/')
.then(response => response.json())
.then(data => {
this.setState({projects: data.name})
});
}
render () {
let projects = this.state.projects;
let liItems = projects.map((project) =>
<li key={project.name}>{project.name}</li>
);
after that it's just basic HTML
You are getting property undefined, because the render function runs before the componentDidMount function. So when it first try to run, the first thing it encounters is this: let projects = this.state.projects; and you defined it initially, it's an empty array. So within your render function, remove the line above and replace with:
if (this.state.projects.length === 0){
return <div>Loading</div>
}
So when initially your component will run it will render for a split second the Loading div, and when your array is populated your component with your array will be rendered.
let liItems = projects.length && projects.map((project) =>
<li key={project.name}>{project.name}</li>
);
Change like this and in render
render(){
return(
<ul>{liItems}</ul>
)
}
If you dont have any projects it renders empty.
I am trying to separate my axios calls from my main vue instance by importing them instead of calling them directly in the created hook.
I have this in a separate file called data.js
import axios from 'axios'
export default{
myData() {
return axios.get(`http://localhost:8080/data.json`)
.then(response => {
// JSON responses are automatically parsed.
return response.data;
})
.catch(e => {
return this.myErrors.push(e)
});
},
And in my vue instance I have the following:
import myDataApi from '#/api/data.js'
export default {
name: 'app',
components: {
myDataApi, // not sure if this is correct
},
data: function () {
return {
myInfo: '',
}
},
created() {
this.myInfo = myDataApi.myData();
console.log('this.myInfo= ', this.myInfo)
},
I am trying to populate myInfo with the json called by myData. This returns [object Promise] in Vue devtools and the as PromiseĀ {<pending>} in the console.
All the data I need is inside that PromiseĀ {<pending>} in an array called [[PromiseValue]]:Object so I know it is working, I just need to know the correct way implementing this.
I don't have a development environment enabled to test this at the moment, but I do notice that you are trying to assign a variable the moment that the component is initialized. This object is a promise, but you're not handling the promise after it is resolved inside the component where you have imported it.
I would recommend trying to handle the promise inside of the actual component, something like:
import myDataApi from '#/api/data.js'
export default {
name: 'app',
components: {
myDataApi, // not sure if this is correct
},
data: function () {
return {
myInfo: '',
}
},
created() {
myDataApi.myData()
.then((data) => {
this.myInfo = data
console.log('this.myInfo= ', this.myInfo);
});
.catch((e) => handleError) // however you want to handle it
},
Just to add to #LexJacobs answer. I omitted the parenthesis around data in .then() as seen below. Vue was squawking about data not being available even though it was. This solved that problem, although to be honest I don't know why.
myDataApi.myData()
.then(data => {
this.dataHasLoaded = true;
this.myInfo = data;
})
.catch(e => {
this.myErrors.push(e)
});
I am building an Angular 1.5 app using the component structure. After the promise comes back from the $http call in the service, I am trying to call another function to filter the dataset before it is displayed on the UI.
However, the filterApps function is not getting called.
Also...in the filterApps function I am trying to compare to arrays of objects and return back the ones that have the same name. Is this the best way to go about this or is there a cleaner way?
Controller :
import allApps from '../../resources/data/application_data.js';
class HomeController {
/*#ngInject*/
constructor(ItemsService) {
this.itemsService = ItemsService;
this.displayApps = [];
}
$onInit() {
this.itemsService
.getItems()
.success((apps) => this.filterApps(apps));
}
filterApps(siteApps) {
this.displayApps = allApps.applications.filter((app) => {
siteApps.applications.map((siteApp) => {
if(siteApp.name === app.name) {
return app;
}
})
});
}
}
export default HomeController;
I don't see any reason that filterApps method isn't geting call(as you already commented that success function is getting called). I guess you're just checking nothing has been carried in displayApps variable. The real problem is you have not return internal map function result to filter. So that's why nothing gets return.
Code
filterApps(siteApps) {
this.displayApps = allApps.applications.filter((app) => {
//returning map function result.
return siteApps.applications.map((siteApp) => {
if(siteApp.name === app.name) {
return app;
}
})
});
}